Loco
07-24-2002, 05:38 PM
2.6 - When should I use shutdown()?
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.
From: brent verner
I believe the code examples are incorrectly using the return
value of the fork()s
excerpt from `man 2 fork`
DESCRIPTION
fork creates a child process that differs from the parent
process only in its PID and PPID, and in the fact that
resource utilizations are set to 0. File locks and pend*
ing signals are not inherited.
RETURN VALUE
On success, the PID of the child process is returned in
the parent's thread of execution, and a 0 is returned in
the child's thread of execution. On failure, a -1 will be
returned in the parent's context, no child process will be
created, and errno will be set appropriately.
From: Ken Whaley
For folks new to shutdown(), it's helpful to remind them that shutdown() is NOT a replacement for close(). You still must close() sockets when you're done with them (after the calls to shutdown()) in order to not leak a file descriptor. Many examples of shutdown() omit the close() after shutdown() because they're test programs where the process exits after the shutdown() (and the process exit of course cleans up all open descriptors). Stevens section on shutdown() doesn't make this clear (his wording implies to me an "either/or" between shutdown() and close()).
From: george elgin
the use of shutdown(1) circumvents what the kernel SHOULD accomplishes automatically. when the open file descriptor count reaches 0 a FIN is sent to the partner which begins the sequence of wait states FIN_WAIT, FIN_WAIT_2 etc. for the reader. When both sides readers
have then completed this orderly shutdown the file descriptor is returned to the operating system.
i have noticed on some (mostly non-unix) operating systems though a
close by all processes (threads) is not enough to correctly flush data,
(or threads) is not. A shutdown must be done, but on many systems
it is superfulous.
From: Newbie
>I believe the code examples are incorrectly using the >return value of the fork()s
Agree.
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.
From: brent verner
I believe the code examples are incorrectly using the return
value of the fork()s
excerpt from `man 2 fork`
DESCRIPTION
fork creates a child process that differs from the parent
process only in its PID and PPID, and in the fact that
resource utilizations are set to 0. File locks and pend*
ing signals are not inherited.
RETURN VALUE
On success, the PID of the child process is returned in
the parent's thread of execution, and a 0 is returned in
the child's thread of execution. On failure, a -1 will be
returned in the parent's context, no child process will be
created, and errno will be set appropriately.
From: Ken Whaley
For folks new to shutdown(), it's helpful to remind them that shutdown() is NOT a replacement for close(). You still must close() sockets when you're done with them (after the calls to shutdown()) in order to not leak a file descriptor. Many examples of shutdown() omit the close() after shutdown() because they're test programs where the process exits after the shutdown() (and the process exit of course cleans up all open descriptors). Stevens section on shutdown() doesn't make this clear (his wording implies to me an "either/or" between shutdown() and close()).
From: george elgin
the use of shutdown(1) circumvents what the kernel SHOULD accomplishes automatically. when the open file descriptor count reaches 0 a FIN is sent to the partner which begins the sequence of wait states FIN_WAIT, FIN_WAIT_2 etc. for the reader. When both sides readers
have then completed this orderly shutdown the file descriptor is returned to the operating system.
i have noticed on some (mostly non-unix) operating systems though a
close by all processes (threads) is not enough to correctly flush data,
(or threads) is not. A shutdown must be done, but on many systems
it is superfulous.
From: Newbie
>I believe the code examples are incorrectly using the >return value of the fork()s
Agree.