From Michael Hunter (mphunter@qnx.com):
shutdown() is useful for deliniating when you are done providing a
request to a server using TCP. A typical use is to send a request to
a server followed by a shutdown(). The server will read your request
followed by an EOF (read of 0 on most unix implementations). This
tells the server that it has your full request. You then go read
blocked on the socket. The server will process your request and send
the necessary data back to you followed by a close. When you have
finished reading all of the response to your request you will read an
EOF thus signifying that you have the whole response. It should be
noted the TTCP (TCP for Transactions -- see R. Steven's home page)
provides for a better method of tcp transaction management.
S.Degtyarev (deg@sunsr.inp.nsk.su) wrote a nice in-depth message to me
about this. He shows a practical example of using shutdown() to aid
in synchronization of client processes when one is the "reader"
process, and the other is the "writer" process. A portion of his
message follows:
Sockets are very similar to pipes in the way they are used for data
transfer and client/server transactions, but not like pipes they are
bidirectional. Programs that use sockets often fork() and each
process inherits the socket descriptor. In pipe based programs it is
strictly recommended to close all the pipe ends that are not used to
convert the pipe line to one-directional data stream to avoid data
losses and deadlocks. With the socket there is no way to allow one
process only to send data and the other only to receive so you should
always keep in mind the consequences.
Generally the difference between close() and shutdown() is: close()
closes the socket id for the process but the connection is still
opened if another process shares this socket id. The connection stays
opened both for read and write, and sometimes this is very important.
shutdown() breaks the connection for all processes sharing the socket
id. Those who try to read will detect EOF, and those who try to write
will reseive SIGPIPE, possibly delayed while the kernel socket buffer
will be filled. Additionally, shutdown() has a second argument which
denotes how to close the connection: 0 means to disable further
reading, 1 to disable writing and 2 disables both.
The quick example below is a fragment of a very simple client process.
After establishing the connection with the server it forks. Then
child sends the keyboard input to the server until EOF is received and
the parent receives answers from the server.
/*
* Sample client fragment,
* variables declarations and error handling are omitted
*/
s=connect(...);
if( fork() ){ /* The child, it copies its stdin to
the socket */
while( gets(buffer) >0)
write(s,buf,strlen(buffer));
close(s);
exit(0);
}
else { /* The parent, it receives answers */
while( (l=read(s,buffer,sizeof(buffer)){
do_something(l,buffer);
/* Connection break from the server is assumed */
/* ATTENTION: deadlock here */
wait(0); /* Wait for the child to exit */
exit(0);
}
What do we expect? The child detects an EOF from its stdin, it closes
the socket (assuming connection break) and exits. The server in its
turn detects EOF, closes connection and exits. The parent detects
EOF, makes the wait() system call and exits. What do we see instead?
The socket instance in the parent process is still opened for writing
and reading, though the parent never writes. The server never detects
EOF and waits for more data from the client forever. The parent never
sees the connection is closed and hangs forever and the server hangs
too. Unexpected deadlock! ( any deadlock is unexpected though :-)
You should change the client fragment as follows:
if( fork() ) { /* The child */
while( gets(buffer) }
write(s,buffer,strlen(buffer));
shutdown(s,1); /* Break the connection
for writing, The server will detect EOF now. Note: reading from
the socket is still allowed. The server may send some more data
after receiving EOF, why not? */
exit(0);
}
I hope this rough example explains the troubles you can have with
client/server syncronization. Generally you should always remember
all the instances of the particular socket in all the processes that
share the socket and close them all at once if you whish to use
close() or use shutdown() in one process to break the connection.