Python网络编程学习笔记(二)——TCP
代码清单3-1 简单的TCP服务端和客户端
#coding=utf-8 import argparse, socket def recvall(sock, length): data = b'' while len(data) < length: more = sock.recv(length - len(data)) if not more: raise EOFError('was excepting %d bytes but only received %d bytes before the socket closed' % (length, len(data))) data += more return data def server(interface, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #建立监听套接字,服务器通过监听套接字设定某个端口用于监听连接请求 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置套接字参数 sock.bind((interface, port)) #声明端口,也可以用作客户端,指定端口发送消息 sock.listen(1) #程序调用该声明,希望套接字能够监听,此时真正决定了程序要作为服务器 # listen()方法传入的整形参数,指明了处在等待连接的最大数目 print('Listening at', sock.getsockname()) while True: sc, sockname = sock.accept() #接受连接请求,新建套接字,sc为新建套接字,sockname为连接套接字名称 print('We have accept a connection from', sockname) print('Socket name :', sc.getsockname()) print('Socket peer :', sc.getpeername()) # 可以从代码中看到,getsockname同时适用于接听套接字和连接套接字。如果想获取连接套接字对应的客户端地址,可以随时运行getpeername,也可以 # 存储accept方法的第二个返回值 message = recvall(sc, 16) print(' Incoming sixteen-octet message:', repr(message)) sc.sendall(b'Farewell, client') sc.close() #关闭套接字 print(' Reply sent, socket closed') def client(host, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) #连接远程服务器IP地址和端口 print('Client has been assigned socket name', sock.getsockname()) sock.sendall(b'Hi there, server') reply = recvall(sock, 16) print('The server said',repr(reply)) sock.close() #关闭套接字 if __name__ == '__main__': choices = {'client':client, 'server':server} parser = argparse.ArgumentParser(description='Send and receive over TCP') parser.add_argument('role', choices=choices, help='which role to play') parser.add_argument('host', help='interface the server listens at;''host the client sends to') parser.add_argument('-p', metavar='PORT', type=int, default=1060, help='TCP port (default 1060)') args = parser.parse_args() function = choices[args.role] function(args.host, args.p)
套接字设置中SO_REUSEADDR选项解释如下:应用程序认为某个TCP连接最终关闭了,操作系统的网络栈实际上会在一个等待状态中将该链接的记录最多保持4分钟,因此,当一个服务器试图声明某个几分钟前运行的链接所使用的端口时,实际上是在试图声明一个仍在使用的端口。这就是试图通过bind()绑定该地址时会返回错误的原因。通过设定套接字选项SO_REUSEADDR,可以指明,应用程序能够使用一些网络客户端之前的连接正在关闭的端口。
代码清单3-2 可能造成死锁的客户端和服务端
import argparse, socket, sys def server(host, port, bytecount): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) sock.listen(1) print('Listening at', sock.getsockname()) while True: sc, sockname = sock.accept() print('Processing up to 1024 bytes at a time from ', sockname) n = 0 while True: data = sc.recv(1024) if not data: break output = data.decode('ascii').upper().encode('ascii') sc.sendall(output) n += len(data) print('\r %d bytes processed so far' % (n,), end=' ') sys.stdout.flush() print() sc.close() print(' Socket closed') def client(host, port, bytecount): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) bytecount = (bytecount + 15) message = b'capitalize this' print('Sending', bytecount, 'bytes of data, in chuncks of 16 bytes') sock.connect((host, port)) sent = 0 while sent < bytecount: sock.sendall(message) sent += len(message) print('\r %d bytes sent' % (sent,), end=' ') sys.stdout.flush() print() sock.shutdown(socket.SHUT_WR) print('Receiving all the data the server sends back') received = 0; while True: data = sock.recv(42) if not received: print(' The first data received says', repr(data)) if not data: break received += len(data) print('\r %d bytes received' % (received,), end=' ') print() sock.close() if __name__ == '__main__': choices = {'client':client, 'server':server} parser = argparse.ArgumentParser(description='Get deadlocked over TCP') parser.add_argument('role', choices=choices, help='which role to play') parser.add_argument('host', help='interface the serever listen at;''host the client sends to') parser.add_argument('bytecount', type=int, nargs='?', default=16, help='number of bytes for client to send (default 16)') parser.add_argument('-p', metavar='PORT', type=int, default=1060, help='TCP port(default 1060)') args = parser.parse_args() function = choices[args.role] function(args.host, args.p, args.bytecount)
当通信双方写的数据量过大时,套接字缓冲区会被越来越多的数据填满,而这些数据从未被读取,那么可能发生死锁。