单线程socketTCP套接字是利用TCP协议提供的传输服务实现网络通信的编程接口。在Python中,您可以通过创建套接字对象并将类型属性指定为SOCK\_STREAM来使用TCP套接字。由于一台主机可能有多个IP地址,可能配置多个不同的服务,作为服务端程序,需要在创建socket对象后将其绑定到指定的IP地址和端口上。这里的端口不是物理设备而是IP地址的扩展,用来区分不同的服务。比如我们通常将HTTP服务绑定到80端口,而MySQL数据库服务默认绑定到3306端口,这样当服务器收到用户请求时,可以根据端口号判断是否是用户请求HTTP服务器或数据库服务器提供的服务。端口的取值范围为0~65535,1024以下的端口通常称为“著名端口”(为FTP、HTTP、SMTP等“著名服务”保留的端口,有的地方也称为“嗯-knownports”),自定义服务通常不使用这些端口,除非自定义是众所周知的服务,例如HTTP或FTP。下面的代码实现了一个提供时间和日期的服务器。fromsocketimportsocket,SOCK_STREAM,AF_INETfromdatetimeimportdatetimedefmain():#1.创建套接字对象并指定要使用的传输服务#family=AF_INET-IPv4address#family=AF_INET6-IPv6address#type=SOCK_STREAM-TCPSocket#type=SOCK_DGRAM-UDPsocket#type=SOCK_RAW-rawsocketserver=socket(family=AF_INET,type=SOCK_STREAM)#2.绑定IP地址和端口(端口用来区分不同的服务)#只能一个服务同时绑定同一个端口,否则会报错server.bind(('192.168.1.2',6789))#3.开启监听-监听客户端连接服务器#参数512可以理解为连接队列的大小server.listen(512)print('服务器开始监听...')whileTrue:#4.通过循环接收客户端的连接并做相应的处理(提供服务)#accept方法是一个阻塞方法ifNoclientconnectedtotheserver代码不会向下执行s#accept方法返回一个元组,其中第一个元素是客户端对象#第二个元素是客户端连接到服务器的地址(由IP和端口两部分组成)client,addr=server.accept()print(str(addr)+'连接到服务器。')#5.发送数据client.send(str(datetime.now()).encode('utf-8'))#6.断开client.close()if__name__=='__main__':main()运行服务器程序后,我们可以通过Windows系统的telnet访问服务器,结果如下图所示。telnet192.168.1.26789也可以通过Python程序实现TCP客户端功能。与实现服务器程序相比,实现客户端程序要简单得多。代码如下。fromsocketimportsocketdefmain():#1.默认使用IPv4和TCP协议创建socket对象client=socket()#2.连接服务器(需要指定IP地址和端口)client.connect(('192.168.1.2',6789))#3.从服务器接收数据print(client.recv(1024).decode('utf-8'))client.close()if__name__=='__main__':main()2.UsemultipleThreadorasynchronousI/O不使用多线程或异步I/O处理,即服务器在与一个客户端通信时,其他客户端只能排队等待。这样的服务器不能满足我们的需求。我们需要的服务器能够同时接收和处理多个用户请求。下面是一个使用多线程处理多个用户请求的服务器,服务器向连接到服务器的客户端发送一张图片。服务器端代码:fromsocketimportsocket,SOCK_STREAM,AF_INETfrombase64importb64encodefromjsonimportdumpsfromthreadingimportThreaddefmain():#自定义线程类classFileTransferHandler(Thread):def__init__(self,cclient):super().__init__()self.cclient=cclientdefrun(self):my_dict={}my_dict['filename']='guido.jpg'#JSON是纯文本,不能携带二进制数据#所以图片的二进制数据应该处理成base64编码my_dict['filedata']=data#通过dumps函数将字典处理成JSON字符串json_str=dumps(my_dict)#发送JSON字符串self.cclient.send(json_str.encode('utf-8'))self.cclient.close()#1.创建一个socket对象并指定使用哪个传输服务server=socket()#2.绑定IP地址和端口(区分不同的服务)server.bind(('192.168.1.2',5566))#3.开启监听——监听客户端连接到服务器server.listen(512)print('服务器开始监听...')withopen('guido.jpg','rb')asf:#将二进制数据处理成base64再解码成字符串data=b64encode(f.read()).decode('utf-8')whileTrue:client,addr=server.accept()#启动一个线程来处理客户端的请求FileTransferHandler(client).start()if__name__=='__main__':main()客户端代码:fromsocketimportsocketfromjsonimportloadsfrombase64importb64decodedefmain():client=socket()client.connect(('192.168.1.2',5566))#定义一个对象来存储二进制数据in_data=bytes()#由于不知道服务器发送的数据有多大,所以每次接收1024字节data=client.recv(1024)whiledata:#将接收到的数据拼接在一起in_data+=datadata=client.recv(1024)#将接收到的二进制数据解码成json字符串并转成字典#loads函数的作用是将json字符串转成字典对象my_dict=loads(in_data.decode('utf-8'))filename=my_dict['filename']filedata=my_dict['filedata'].encode('utf-8')withopen('/Users/Hao/'+filename,'wb')asf:#解码base64格式的数据转化为二进制数据And写入文件f.write(b64decode(filedata))print('图像已保存。')if__name__=='__main__':main()
