Chapter 3
NETWORK
PROGRAMMING
The Client-Server
Paradigm
The paradigm of arranging for one application
program to wait passively for another application
to initiate communication pervades so much of
distributed computing that it has been given a
The Client-Server
name
• The client-server paradigm interaction
Paradigm
The terms client and server refer to the two
applications involved in a communication
• The application that actively initiates contact is called a “client”
• While the application that passively waits for contact is called a
“server”
Formally, the term “server” refers to a program that waits
passively for communication
• and not to the computer on which it executes
However, when a computer is dedicated to running one or
more server programs
• the computer itself is sometimes (incorrectly) called a server
Server Hardware vendors contribute to the confusion
Programs • because they classify computers that have fast CPUs, large
memories, and powerful OS as server machines
And Server-
Class Better to adhere to scientifically accurate terminology
Computers • use the term server to refer to the running program and not the
computer
The term server-class computer refers to a powerful
computer used to run server SW
A sufficiently powerful computer system can run multiple clients and
servers at the same time
Multiple Services On
Two capabilities are required
One Computer (1) • First, the computer must have sufficient HW resources
• Second, the computer must have an OS that allows multiple
application to execute concurrently (e.g., UNIX or Windows)
On such systems, one server program runs for each service being
offered
• E.g.: a computer can run a FTP server as well as a WWW
server
Figure illustrates one possible arrangement
• Although a computer can operate multiple servers
• the computer needs only a single physical connection to the
internet
9
A computer system that permits multiple application
programs to execute at the same time
• is said to support “concurrency” and a program that
Multiple Copies Of A
Server For A Single has more than one thread of control
• Some systems use the term “process” “task” or
“thread” execution to denote a thread of control
Concurrency is fundamental to the client-server model
• A concurrent server serves multiple clients at the same
time, without requiring each client to wait for previous
clients to finish
When a request arrives, the server assigns the request to a
Service
thread of control that can execute concurrently with
existing threads
• In essence, a separate copy of the server handles each
request
• Thus, short requests can be satisfied quickly
Most concurrent servers operate dynamically
• The server creates a new thread for each request that arrives
In fact, the server program is constructed in two parts:
Dynamic Server
• one that accepts requests and creates a new thread
• and another code to handle an individual request
When a concurrent server starts executing, only the first
part runs
Creation
• That is, the main server thread waits for a request to arrive
• The main thread keeps the server alive
When a request arrives, the main thread creates a new
service thread to handle the request
• The service thread handles one request and then terminate
Transport Protocols If multiple copies of a server exist, how can
And Unambiguous a client interact with the correct copy?
Communication • How can a request be passed to the correct copy of a
server?
Most transport protocols assign client a
unique identifier
• and require the client to include its identifier when
making a request
• It uses both the client and server identifiers to
choose the copy of the server that has been created
to handle the client
TCP Connection Creation
Client Server
Active Participant Passive Participant
SYN 1
SN=X
SYN 2
SN=Y ACK=X+1
3
ACK=Y+1
TCP 3-Way Handshake
• A client starts by sending a SYN segment with the following 1
information:
• Client’s SN (generated pseudo-randomly) = X
• Maximum Receive Window for client.
• Only TCP headers
• When a waiting server sees a new connection request, the
server sends back a SYN segment with: 2
• Server’s SN (generated pseudo-randomly) = Y
• Acknowledgement Number is Client SN+1 = X+1
• Maximum Receive Window for server.
• Only TCP headers
• When the Server’s SYN is received, the client sends back an 3
ACK with:
• Acknowledgement Number is Server’s SN+1 = Y+1
TCP
• Reliable Byte-Stream
• Connection-oriented
• Byte-stream
• sending process writes some number of bytes
• TCP breaks into segments and sends via IP
• receiving process reads some number of bytes
Application process Application process
W rite Read
bytes bytes
…
…
TCP TCP
Send buffer Receive buffer
• Full duplex Segment Segment … Segment
• Flow control: keep sender from overrunning receiver T ransmit segments
• Congestion control: keep sender from overrunning network
TCP Connection Termination
App1 App2
FIN 1
SN=X
2
ACK=X+1
...
FIN 3
SN=Y
4
ACK=Y+1
Client Server Communication
Port
65536
65536
65535
65535
65534
65534
65533
65533
IP 82
82 Network Host
81
81 Network
80 122.34.45.67
80
79
79
Network Host 78
78
123.45.67.89
SOCKETS
122.34.45.67: 65534
3
3
123.45.67.89:80 2
2
1
1
Client Server Communication
Port
65536
IP Host
65535
65534
65533 HTTP Server with three
active connections (sockets).
IP Network
Active
Active
82 Active
81 IP Host
Listening
80
IP Host/ 79 The HTTP server listens for
Server 78 future connections.
3
2
IP Host
1
• A socket address is the triplet:
• {protocol, local-IP, local-port}
• {tcp, 130.245.1.44, 23}
• An association is the 5-tuple that
Associations completely specifies the two end-
points that comprise a connection:
• {protocol, local-IP, local-port,
remote-IP, remote-port}
• {tcp, 130.245.1.44, 23, 130.245.1.45,
1024}
Sockets And Socket
Libraries (1)
• Before an application can use protocols to
communicate
• The application must request the OS to
create a socket that will be used for
communication
• The system returns a small integer
“descriptor” that identifies the socket
Sockets, • The application then passes the descriptor
as an argument when it calls procedures to
Descriptors, And transfer data across the NW
Network I/O (1) • the application does not need to specify
details about the remote destination
each time it transfers data
• The OS provides a single set of descriptors
for files, devices, interprocess
communication (IPC)
• UNIX provides a pipe mechanism for IPC
• An application can use the same procedure to send data
to
• another program, a file, or across a NW
• The descriptor represents an object, and the “write”
procedure represents a method applied to that object
• The underlying object determines how the method is
applied
• The chief advantage of an integrated system lies in its
Sockets, flexibility
• a single application can be written that transfers data
Descriptors, And to an arbitrary location
• If the application is given a descriptor that corresponds to
Network I/O (2) a device
• The application sends data to the device
• If the application is given a descriptor that corresponds to
a file
• the application stores data in the file.
• If the application is given a descriptor that corresponds to
a socket
• the application sends data across an internet to a
remote machine
• Socket programming differs from conventional I/O
• because an application must specify many details to use a
socket
• For example,
• an application must choose a particular transport protocol,
• provide the protocol address of a remote machine,
• and specify whether the application is a client or a server
• To accommodate all the details
Parameters And • each socket has many parameters and options
• an application can supply values for each
The Socket API • How should options and parameters be represented in an
API?
• To avoid having a single socket function with separate
parameters for each option, designers of the socket API
chose to define many functions.
• An application creates a socket and then invokes functions
to specify in detail
• The advantage is that most functions have few parameters
• The disadvantage is that, we must remember to call multiple
functions
• The Socket Procedure
• The Close Procedure
• The Bind Procedure
Procedures That • The Listen Procedure
• The Accept Procedure
Implement The • The Connect Procedure
Socket API • The Send, Sendto, And Sendmsg
Procedures
• The Recv, Recvfrom, And Recvmsg
Procedures
• Sockets allow applications to use read and write
• Like send and recv , read and write do not have
arguments that permit the caller to specify a destination
• Instead, read and write each have three arguments:
• a socket descriptor
• the location of a buffer in memory used to store the
data
• and the length of the memory buffer
Read And Write • The chief advantage of using read and write is generality
With Sockets • an application program can be created that transfers
data to or from a descriptor
• without knowing whether the descriptor corresponds
to a file or a socket
• thus, a programmer can use a file on a local disk to
test a client or server before attempting to
communicate across a NW
• The chief disadvantage is that a socket library
implementation may introduce additional overhead in the
file I/O
• The socket API contains other useful procedures
• For example,
• after a server calls procedure accept
• to accept an incoming connection request
• the server can call procedure
“getpeername”
• to obtain the complete address of the remote
Other Socket client that initiated the connection
• A client or server can also call “gethostname”
Procedures • to obtain information about the computer on
which it is running
• Two general-purpose procedures are used to set
socket options or obtain a list of current values, an
application calls procedure
• “setsockopt” to store values in socket options
• “getsockopt” to obtain current option values
• Options are used mainly to handle special cases
• Because many servers are
concurrent, the socket API is
designed to work with concurrent
programs
Sockets, Threads, • Although the details depend on the
And Inheritance underlying OS implementations of
the socket API adhere to the
following principle:
• Each new thread that is created
inherits a copy of all open sockets
from the thread that created it
Socket Domain Families
• There are several significant socket domain families:
• Internet Domain Sockets (AF_INET)
• implemented via IP addresses and port numbers
• Unix Domain Sockets (AF_UNIX)
• implemented via filenames (similar to IPC “named pipe”)
Creating a Socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
• domain is one of the Protocol Families (AF_INET, AF_UNIX, etc.)
• type defines the communication protocol semantics, usually defines
either:
• SOCK_STREAM: connection-oriented stream (TCP)
• SOCK_DGRAM: connectionless, unreliable (UDP)
• protocol specifies a particular protocol, just set this to 0 to accept the
default
INET Address
struct in_addr {
in_addr_t s_addr; /* 32-bit IPv4 address */
}
The in_addr_t s_addr;
in_addr_t is a data type that represents a 32-bit integer used
to store an IPv4 address.
Socket In most systems, in_addr_t is typically an unsigned long or
uint32_t (depending on the platform), which means it is a 32-
bit unsigned integer.
Structure This 32-bit value represents an IPv4 address in binary form
(i.e., four 8-bit numbers).
Member s_addr is the name of the member within the
in_addr structure that holds the 32-bit IPv4 address.IPv4
addresses are usually represented in dotted-decimal notation
(e.g., 192.168.1.1), but inside this structure, the address is
stored as a 32-bit binary number.
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
The */
in_port_t sin_port; /* 16-bit TCP/UDP port number
Socket struct in_addr sin_addr; /* 32-bit IPv4 address */
char sin_zero[8]; /* unused */
Structure }
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP port number
The */
struct in_addr sin_addr; /* 32-bit IPv4 address */
Socket }
char sin_zero[8]; /* unused */
Structure Type: uint8_t (8-bit unsigned integer)
Purpose: Represents the length of the structure, typically 16 bytes
for sockaddr_in. This field is not always present in all systems, but
when present, it indicates the size of the sockaddr_in structure.
It helps the operating system know how much memory the
structure occupies.
Usage: Mostly in BSD-based systems (like FreeBSD), not typically
used in Linux systems.
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP port number
*/
The struct in_addr sin_addr; /* 32-bit IPv4 address */
char sin_zero[8]; /* unused */
Socket }
Structure Type: sa_family_t (usually an unsigned 16-bit integer)
Purpose: Specifies the address family for the socket.
Common Values:
• AF_INET: IPv4 addresses
• AF_INET6: IPv6 addresses (in the case of sockaddr_in6)
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP port number
*/
The struct in_addr sin_addr; /* 32-bit IPv4 address */
char sin_zero[8]; /* unused */
Socket }
Type: in_port_t (16-bit unsigned integer)
Structure Purpose: Stores the 16-bit port number for the TCP or UDP connection in
network byte order (big-endian format). This is the port number that the socket
will use for communication.
Usage:The port number specifies the endpoint of communication for a service.
For example, web servers commonly use port 80 for HTTP or 443 for HTTPS.
It’s essential to use htons() (Host to Network Short) to convert the port number
from host byte order (native byte order) to network byte order when setting
this field.
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP port number
*/
The struct in_addr sin_addr; /* 32-bit IPv4 address */
char sin_zero[8]; /* unused */
Socket }
Structure
INET Socket
Struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP/UDP port number
*/
The struct in_addr sin_addr; /* 32-bit IPv4 address */
char sin_zero[8]; /* unused */
Socket }
Structure Type: char sin_zero[8]
Purpose: This array of 8 bytes is unused and reserved for padding to
make the size of the sockaddr_in structure match the size of the
sockaddr structure (which is required for compatibility with other
address structures). It should be set to zero during initialization but
has no other functional use.
struct sockaddr_in {
Setup for
sa_family_t sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
an };
unsigned char pad[...];
Internet • sin_family is set to Address Family AF_INET
• sin_port is set to the port number you want to
bind to
Domain • sin_addr is set to the IP address of the machine
you are binding to (struct in_addr is a wrapper
Socket struct for an unsigned long)
• ignore padding
Stream Socket Transaction (TCP Connection)
Server socket()
Client
bind()
socket()
listen()
connect() 3-way handshake
accept()
write() data
read()
data
write()
read()
EOF
close() read() close()
SERVER
Create socket
bind a port to the
socket • Connection-oriented
CLIENT socket connections
listen for incoming
Create socket
• Client-Server view
connections
accept an
connect to server's
incoming
port
connection
read from the write to the
connection connection
loop loop
write to the read from the
connection connection
close connection
Server Side Socket Details
SERVER
Create socket int socket(int domain, int type, int protocol)
sockfd =socket(AP_INET, SOCK_STREAM, 0);
bind a port to int bind(int sockfd, struct sockaddr *server_addr, socklen_t length)
the socket
bind(sockfd, &server, sizeof(server));
listen for incoming int listen( int sockfd, int num_queued_requests)
connections listen( sockfd, 5);
accept an
incoming int accept(int sockfd, struct sockaddr *incoming_address, socklen_t length)
connection newfd = accept(sockfd, &client, sizeof(client)); /* BLOCKS */
read from the int read(int sockfd, void * buffer, size_t buffer_size)
connection read(newfd, buffer, sizeof(buffer));
write to the int write(int sockfd, void * buffer, size_t buffer_size)
connection write(newfd, buffer, sizeof(buffer));
Client Side Socket Details
CLIENT
int socket(int domain, int type, int protocol)
Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect to Server int connect(int sockfd, struct sockaddr *server_address, socklen_t length)
socket connect(sockfd, &server, sizeof(server)); /* Blocking */
write to the int write(int sockfd, void * buffer, size_t buffer_size)
connection write(sockfd, buffer, sizeof(buffer));
read from the int read(int sockfd, void * buffer, size_t buffer_size)
connection read(sockfd, buffer, sizeof(buffer));
• Sockets, are inter-process-communication mechanism, similar
with files:
• low level IO:
Reading From and
Writing To Stream • read() system call
• write() system call
• higher level IO:
• int recv(int socket, char *buf, int len, int flags);
• blocks on read
• returns 0 when other connection has terminated
• int send(int socket, char *buf, int len, int flags);
Sockets
• returns the number of bytes actually sent
• where flags may be one of:
• MSG_DONTROUTE (don’t route out of localnet)
• MSG_OOB (out of band data (causes interruption))
• MSG_PEEK (examine, but don’t remove from
stream)
• int close(int socket);
• closes read/write IO, closes socket file
descriptor
Closing a Socket
• int shutdown( int socketfd, int mode);
• where mode is:
• 0: no more receives allowed “r”
• 1: no more sends are allowed “w”
Session
• 2: disables both receives and sends
(but doesn’t close the socket, use
close() for that) “rw”
Byte Ordering
• Different computer architectures use different byte ordering to
represent/store multi-byte values (such as 16-bit/32-bit
integers)
• 16 bit integer:
Little-Endian (Intel) Big-Endian (RISC-Sparc)
Low Byte Address A High Byte
High Byte Address A+1 Low Byte
Byte Order and Networking
• Suppose a Big Endian machine sends a 16 bit integer with the value 2:
00000000 00000010
• A Little Endian machine will understand the number as 512:
00000010 00000000
• How do two machines with different byte-orders communicate?
• Using network byte-order
• Network byte-order = big-endian order
• Conversion of application-level data is left up
Network Byte Order to the presentation layer.
• Lower level layers communicate using a fixed
byte order called network byte order for all
control data.
• TCP/IP mandates that big-endian byte ordering
be used for transmitting protocol information
• All values stored in a sockaddr_in must be in
network byte order.
• sin_port a TCP/IP port number.
• sin_addr an IP address.
Network Byte Order • Several functions are provided to allow conversion
between host and network byte ordering,
• Conversion macros (<netinet/in.h>)
• to translate 32-bit numbers (i.e. IP addresses):
• unsigned long htonl(unsigned long hostlong);
• unsigned long ntohl(unsigned long netlong);
Functions
• to translate 16-bit numbers (i.e. Port numbers):
• unsigned short htons(unsigned short hostshort);
• unsigned short ntohs(unsigned short netshort);
• Creating a passive mode (server) socket.
• Establishing an application-level connection.
Programming
• send/receive data.
TCP Sockets
• Terminating a connection.
Summary
Creating a TCP socket
int socket(int family, int type, int proto);
int mysockfd;
mysockfd = socket(AF_INET, SOCK_STREAM, 0);
if (mysockfd<0) { /* ERROR */ }
struct sockaddr_in {
sa_family_t sin_family;
unsigned short int sin_port;
Binding to well known
struct in_addr sin_addr;
unsigned char pad[...];
int mysockfd; };
int err;
struct sockaddr_in myaddr;
mysockfd = socket(AF_INET,SOCK_STREAM,0);
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons( 80 );
address
myaddr.sin_addr = htonl( INADDR_ANY );
err= bind(mysockfd, (sockaddr *) &myaddr, sizeof(myaddr));
Clients typically don’t care
what port they are
assigned.
Bind – What
Port Number? When you call bind you can
tell it to assign you any
available port:
• myaddr.port = htons(0);
How can you find out what your IP address is
so you can tell bind() ?
Bind - What IP There is no realistic way for you to know the
right IP address to give bind() - what if the
address ? computer has multiple network interfaces?
Specify the IP address as: INADDR_ANY, this
tells the OS to handle the IP address
specification.
From ASCII to numeric
• “130.245.1.44” ➔ 32-bit network byte ordered
Converting value
• inet_aton(…) with IPv4
Between IP • inet_pton(…) with IPv4 and IPv6
Address From numeric to ASCII
formats • 32-bit value ➔ “130.245.1.44”
• inet_ntoa(…) with IPv4
• inet_ntop(…) with IPv4 and IPv6
• Note – inet_addr(…) obsolete
• cannot handle broadcast address
“255.255.255.255” (0xFFFFFFFF)
int inet_aton( char *, struct in_addr *);
Convert ASCII dotted-decimal IP address to network
byte order 32 bit value. Returns 1 on success, 0 on
IPv4 Address failure.
Conversion
char *inet_ntoa(struct in_addr);
Convert network byte ordered value to ASCII dotted-
decimal (a string).
• Passive mode:
• Address already determined.
Establishing a
passive mode • Tell the kernel to accept incoming connection
requests directed at the socket address.
TCP socket • 3-way handshake
• Tell the kernel to queue incoming connections
for us.
int listen( int mysockfd, int backlog);
mysockfd is the TCP socket (already bound to an
address)
listen() backlog is the number of incoming connections the
kernel should be able to keep track of (queue for us).
listen() returns -1 on error (otherwise 0).
Once we call listen(), the O.S. will queue
incoming connections
Handles the 3-way Queues up multiple
Accepting an handshake connections.
incoming
connection
When our application is ready to handle a
new connection, we need to ask the O.S. for
the next connection.
int accept( int mysockfd, struct sockaddr* cliaddr, socklen_t
*addrlen);
mysockfd is the passive mode TCP socket.
cliaddr is a pointer to allocated space.
addrlen is a value-result argument
• must be set to the size of cliaddr
accept() • on return, will be set to be the number of used bytes
in cliaddr.
accept() return value
• accept() returns a new socket descriptor (positive
integer) or -1 on error.
• After accept returns a new socket descriptor, I/O can
be done using the read() and write() system calls.
• Either end of the connection can
call the close() system call.
• If the other end has closed the
Terminating a connection, and there is no
TCP connection buffered data, reading from a
TCP socket returns 0 to indicate
EOF.
• TCP clients can call connect() which:
• takes care of establishing an endpoint
address for the client socket.
• don’t need to call bind first, the O.S.
will take care of assigning the local
Client Code endpoint address (TCP port number,
IP address).
• Attempts to establish a connection to
the specified server.
• 3-way handshake
int connect( int sockfd, const struct sockaddr
*server, socklen_t addrlen);
connect() sockfd is an already created TCP socket.
server contains the address of the server (IP
Address and TCP port number)
connect() returns 0 if OK, -1 on error
int read( int fd, char *buf, int max);
Reading from a TCP By default read() will block until data is
socket available.
Reading from a TCP socket may return less
than max bytes (whatever is available).
int write( int fd, char *buf, int num);
write might not be able to write all
num bytes (on a nonblocking socket).
Writing to a TCP
socket Other functions (API)
readn(), writen() and readline() -
see man pages definitions
(https://man7.org/linux/man-
pages/).
Example [ from R. Stevens text]
Client Server communication
Client Network Server
Machine A Machine B
• Web browser and server
• FTP client and server
• Telnet client and server
Example – Daytime Server/Client
Application protocol
Daytime client (end-to-end logical connection) Daytime server
Socket API Socket API
TCP protocol
TCP (end-to-end logical connection) TCP
IP protocol
IP (physical connection ) IP
MAC-level protocol
MAC driver MAC driver
(physical connection )
Actual data flow MAC = media
Network access control
Daytime client
#include "unp.h"
▪ Connects to a daytime server
int main(int argc, char **argv) ▪ Retrieves the current date and
{ time
int sockfd, n; % gettime 130.245.1.44
char recvline[MAXLINE + 1]; Thu Oct 05 12:50:00 2024
struct sockaddr_in servaddr;
if( argc != 2 )err_quit(“usage : gettime <IP address>”);
/* Create a TCP socket */
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
/* Specify server’s IP address and port */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13); /* daytime server port */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
Daytime client
/* Connect to the server */
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
/* Read the date/time from socket */
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
printf(“%s”, recvline);
}
if (n < 0) err_sys("read error");
close(sockfd);
}
Simplifying error-handling [ R. Stevens ]
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return n;
}
#include "unp.h"
#include <time.h> ▪ Waits for requests from Client
▪ Accepts client connections
int main(int argc, char **argv) ▪ Send the current time
{ ▪ Terminates connection and goes back
int listenfd, connfd; waiting for more connections.
struct sockaddr_in servaddr;
char buff[MAXLINE];
Daytime Server
time_t ticks;
/* Create a TCP socket */
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
/* Initialize server’s address and well-known port */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
/* Bind server’s address and port to the socket */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
/* Convert socket to a listening socket */
Listen(listenfd, LISTENQ);
for ( ; ; ) {
/* Wait for client connections and accept them */
connfd = Accept(listenfd, (SA *) NULL, NULL);
Daytime Server
/* Retrieve system time */
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n",
ctime(&ticks));
/* Write to socket */
Write(connfd, buff, strlen(buff));
/* Close the connection */
Close(connfd);
}
}