I have implemented a "broken" client/server to show how socket.shutdown can be more useful than a simple socket.close operation. (I rewrote this to hopefully be more clear)
The close operation is not atomic. It implicitly tries to send any remaining data in _addition_ to closing a descriptor. Splitting this close operation up with the aid of the shutdown command can help avoid bugs. It gives the server one final way to say, "something went wrong". The server would also know that the client did not end correctly, since the socket should remain open when the client finished sending data. For example, if the function exits unexpectedly and python closes the socket for you, the server would not be able to send any data back.
In the server below, the client and server have different ideas about what the end marker should be. The rev_end function is written so as to look for an end marker. And, as long as they agree it should work. The socket.shutdown is for when something goes wrong.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
def recv_end(the_socket): End='SERVER WRONG MARKER' total_data=;data='';got_end=False while True: data=the_socket.recv(8192) if not data: break if End in data: total_data.append(data[:data.find(End)]) got_end=True break total_data.append(data) if len(total_data)>1: #check if end_of_data was split last_pair=total_data[-2]+total_data[-1] if End in last_pair: total_data[-2]=last_pair[:last_pair.find(End)] total_data.pop() got_end=True break return (got_end,''.join(total_data)) def basic_server(sock): got= got_end,data = recv_end(sock) if not got_end: sock.send('ERROR:no end!') #<--- not possible w/close() else: sock.sendall(data*2) sock.shutdown(1) sock.close() import socket Port=4444 def start_server(): sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.bind(('',Port)) sock.listen(5) print 'started on',Port while True: newsock,address=sock.accept() basic_server(newsock) def send_data(data): sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect(('localhost',Port)) print 'connected' sock.sendall(data+'CLIENT WRONG MARKER') print 'sent',data sock.shutdown(1) print 'shutdown' result= while True: got=sock.recv(2) if not got: break result.append(got) sock.close() return ''.join(result) if __name__=='__main__': start_server()
What can you rely on TCP for: 1)no duplication 2)packets arrive in order What you cannot rely on TCP for: How the data is broken up. What this means is, you always have to loop, since the data could arrive in little bits. Hence, both the server and client loop. What this also means is that you have to take care of how to end the conversation which is where shutdown comes into play.
In bi-directional communication, by default, a client can know when it is done sending, but it cannot know if it is done receiving. And, also the server cannot know whether the client is done sending.
You could do something like put a byte count in front of the data, or have an end marker so the server can know if it got all of the bytes. However, that introduces a problem. What if the byte count is wrong or the end marker never arrives? With a socket.close() the server cannot tell the client, "Strange. You are done sending data to me, but I didn't get all the data", since the client connection is not left open after the client is done sending.
With a socket.shutdown(1) the client can still be told by the server that something was wrong and take appropriate measures.
The shutdown command has three options: 0 = done receiving, 1 = done sending, 2 = both
In the code above focuses on 1, to get rid of the implict send in a close operation. Notice how in send_data the close operation is (relatively) far away from the shutdown. This allows the server to tell the client any parting comment.
Just run the code to start the server. The server is set to recv only 2 bytes at a time for demonstration purposes (it should be something like 8192). To send data to it import it (call it shut_srv or whatever) and call send_data for the client side.
data=('a1234','b1234','c1234','d1234','e1234') for d in data: print shut_srv.send_data(d)
You will get a response like: connected sent a1234 shutdown ERROR:no end! connected sent b1234 shutdown ERROR:no end! connected sent c1234 shutdown ERROR:no end! connected sent d1234 shutdown ERROR:no end! connected sent e1234 shutdown ERROR:no end!
If you make the markers the same. The response should be: connected sent a123456789 shutdown a1234a1234 connected sent b1234 shutdown b1234b1234 connected sent c1234 shutdown c1234c1234 connected sent d1234 shutdown d1234d1234 connected sent e1234 shutdown e1234e1234