The tutorial by Jim Frost, "BSD Sockets: A Quick and Dirty Primer" makes an interesting analogy between using sockets and using a phone. This is the idea, modified slightly to include select():
Server Routines | Telephone Analogy | |
socket() | installing a phone line | |
bind() | assign a phone # to your phone | |
listen() | use a multi-line phone | |
select() | wait for the phone to ring | |
accept() | pick up the phone | |
read() / write() | talk | |
close() | hangup |
The client has fewer routines:
Client Routines | Telephone Analogy | |
socket() | installing a phone line | |
connect() | dial | |
read() / write() | talk | |
close() | hangup |
Now for a chronological look at how these system calls are typically used.
TCP Client TCP Server ---------- ---------- socket() | V bind() [well-known port] | V listen() | V accept() | V socket() blocks until connection | attempt from client | | V | connect() <--(connection established)--> | | | V | |->--> write() ->\ V | | \---data (request)---------> read() <----<----| | | | | | | V | ^ | process request ^ | | | | | | V | | | <-- write() >---->----| | V / | --<--< read() <------data (reply)-------/ | | | V | close() ->\ V \----EOF notification------> read() | V close()
This diagram doesn't show select(), but you can assume that select() is used in the server each time before accepting a client, and also before reading from a file descriptor.
[Credit: this diagram is based directly on the one in "Unix Network Programming 2nd edition" by Stevens]
Finally, we'll take a more in-depth look at these functions...
int socket(int family, int type, int protocol); | | | | | | | | | V V V | AF_UNIX SOCK_STREAM either is fine: | -user pathnames -stream socket a) 0 | identify sockets -TCP (use default) | -talk among -sequenced b) IPPROTO_TCP | processes on -connection (selected by | the same host oriented default for | -variable SOCK_STREAM) | length byte stream | | AF_INET SOCK_DGRAM | -internet protocols -datagram socket | -talk among -UDP (ie.email) | processes on -connectionless | different hosts -fixed-length bytes | | [Note: AF="address family"] V >= 0: socket descriptor < 0: error
4.3 BSD provides both a connection-oriented interface and a connectionless interface to the Unix domain protocols. Both can be considered reliable, since they exist only within the kernel and are not transmitted across external facilities such as a communication line between systems. Checksums and the like are not needed. As with other connection-oriented protocols (TCP and SPP, for example), the connection-oriented Unix version provides flow control. In a similar fashion, as with other connectionless protocols (UDP and IDP, for example), the Unix domain datagram facility does *not* provide flow control. This has implications for user programs, since it is possible for a datagram client to send data so fast that buffer starvation can occur. If this happens, the sender must try to send the data repeatedly. For this reason alone, it is recommended that you use the connection-oriented Unix domain protocol.
int bind(int sockfd, struct sockaddr *myaddr, int addrlen); | | | | V | V protocol specific address | 0 : success [for AS #3, we cast from V -1 : error struct sockaddr_in used for: -allocating the "_in" at the end space for structure of "sockddr_in" is -learning which address short for "internet" structure type being ] used
int listen(int sockfd, int backlog); | | | V V -specifies how many connection requests 0 : success can be queued/pending at the same time -1 : error -needed because: -in the time is takes for the server to handle a connection request, it is possible for additional connection requests to arrive from other clients -usually set to 5 [max value allowed?]
int accept(int sockfd, struct sockaddr *peer, int *addrlen); | | | | | | | V V V - returns address info - returns size of address < 0 : error of connected client >=0 : new socket - can be NULL - can also be NULL descriptor [Jim Frost uses NULL]
The copied/cloned descriptor which gets returned is what is used for I/O with the newly connected client. The sockfd itself is _always_ listening for new socket connections, and never gets tied up doing I/O.
int connect(int sockfd, struct sockaddr *servaddr, int addrlen); | | | | | | V V | 0 : success pointer to socket address V -1 : error size of socket address