python之路(12)网络编程
前言
基于网络通信(AF_INET)的socket(套接字)实现了TCP/UDP协议
目录
基于TCP协议的socket
服务端
#服务端 from socket import * #AF_INIT(基于网络通信) SOCK_STREAM(TCP协议)(买手机) tcp_server= socket(AF_INET,SOCK_STREAM) #防止端口FIN_WAIT状态,重用ip和端口(4次挥手断开连接需要时间) #tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #绑定ip和端口(插手机卡) tcp_server.bind(('127.0.0.1',8080)) #可以挂起几个连接(开机) tcp_server.listen(5) while True: #服务端等待连接(等电话) conn, addr = tcp_server.accept() while True: #通话中 try: # 接收buffer_size个字节的信息 (收消息) data = conn.recv(1024) # (发消息) conn.send(data.upper()) except Exception as e: #客户端错误方式关闭连接,退出 print(e,'客户端断开连接') break # 关闭连接 (挂电话) conn.close() # 关闭服务 (关机) tcp_server.close()
客户端
#客户端 from socket import * #开机 tcp_client= socket(AF_INET,SOCK_STREAM) #拨通电话 tcp_client.connect(('127.0.0.1',8080)) while True: msg = input('>>:').strip() #发消息 tcp_client.send(msg.encode('utf-8')) #收消息 data = tcp_client.recv(1024) print(data.decode('utf-8')) #关机 tcp_client.close()
#服务端 from socket import * ip_port = ('127.0.0.1',8080) back_log = 5 buffer_size = 1024 tcp_server= socket(AF_INET,SOCK_STREAM) #AF_INIT(基于网络通信) SOCK_STREAM(TCP协议)(买手机) tcp_server.bind(ip_port) #绑定ip和端口(插手机卡) tcp_server.listen(back_log) #可以挂起几个连接(开机) while True: conn, addr = tcp_server.accept() # 服务端等待连接(等电话) print('双向链接', conn) print('客户端地址', addr) while True: try: data = conn.recv(buffer_size) # 接收buffer_size个字节的信息 (收消息) print("客户端发来的消息", data.decode('utf-8')) conn.send(data.upper()) # (发消息) except Exception: break conn.close() #关闭连接 (挂电话) tcp_server.close() #关闭服务 (关机) ''' 输出: 双向链接 <socket.socket fd=516, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 47586)> 客户端地址 ('127.0.0.1', 47586) 客户端发来的消息 chen '''
#客户端 from socket import * ip_port = ('127.0.0.1',8080) back_log = 5 buffer_size = 1024 tcp_client= socket(AF_INET,SOCK_STREAM) tcp_client.connect(ip_port) #拨通电话 while True: msg = input('>>:').strip() tcp_client.send(msg.encode('utf-8')) #发消息 print('客户端已经发送消息') data = tcp_client.recv(buffer_size) #收消息 print('收到服务端发送的消息',data.decode('utf-8')) tcp_client.close() #关机 ''' 输出: >>:chen 客户端已经发送消息 收到服务端发送的消息 CHEN '''
基于UDP协议的socket
服务端
#服务端 from socket import * #AF_INIT(基于网络通信) SOCK_DGRAM(数据报式) udp_server= socket(AF_INET,SOCK_DGRAM) #防止端口FIN_WAIT状态,重用ip和端口(4次挥手断开连接需要时间) #udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #绑定ip和端口(插手机卡) udp_server.bind(('127.0.0.1',8080)) while True: #接收消息 data,addr = udp_server.recvfrom(1024) #按客户端ip和端口发送 udp_server.sendto(data.upper(),addr) udp_server.close()
客户端
#客户端 from socket import * #开机 udp_client= socket(AF_INET,SOCK_DGRAM) while True: msg = input('>>:').strip() #向指定ip和端口发消息 udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) #收消息,返回数据和服务器ip data,addr = udp_client.recvfrom(1024) udp_client.close()
TCP协议下粘包现象及处理
tcp协议下会产生粘包现象,即当前的命令结果与上一条的命令结果粘在了一起。
产生粘包现象的原因有两个:
1. tcp是面向流的协议, tcp_client.recv() 设置的一次接收的size小于服务器传输过来的字节大小,当客户端从缓存的拿去接收的数据时,没有全部取走,而缓存中是以队列的形式存储,当再一次执行命令的时候会收到上一次命令结果的后半部分与当前命令结果连接在一起的结果
2. tcp下使用Nagle算法优化,会将间隔较小且数据较小的数据,合成一个较大的数据块一起发送
解决方法:粘包现象的根本是,不知道发送数据的大小
from socket import * import subprocess import struct ip_port=('127.0.0.1',8080) back_log=5 buffer_size=1024 tcp_server=socket(AF_INET,SOCK_STREAM) tcp_server.bind(ip_port) tcp_server.listen(back_log) while True: conn,addr=tcp_server.accept() print('新的客户端链接',addr) while True: #收 try: cmd=conn.recv(buffer_size) if not cmd:break print('收到客户端的命令',cmd) #执行命令,得到命令的运行结果cmd_res res=subprocess.Popen(cmd.decode('utf-8'),shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE) err=res.stderr.read() if err: cmd_res=err else: cmd_res=res.stdout.read() #发 if not cmd_res: cmd_res='执行成功'.encode('gbk') length=len(cmd_res) #给数据设置消息头表明数据的大小 data_length=struct.pack('i',length) conn.send(data_length) conn.send(cmd_res) except Exception as e: print(e) break
from socket import * import struct from functools import partial ip_port=('127.0.0.1',8080) back_log=5 buffer_size=1024 tcp_client=socket(AF_INET,SOCK_STREAM) tcp_client.connect(ip_port) while True: cmd=input('>>: ').strip() if not cmd:continue if cmd == 'quit':break tcp_client.send(cmd.encode('utf-8')) #解决粘包:根据服务端传输的消息头表明数据大小,反复读取缓存缓存数据 length_data=tcp_client.recv(4) length=struct.unpack('i',length_data)[0] recv_size = 0 recv_msg = b'' while recv_size < length: recv_msg += tcp_client.recv(buffer_size) recv_size = len(recv_msg) print('命令的执行结果是 ',recv_msg.decode('gbk')) tcp_client.close()
使用socketserver模块实现高并发
tcp服务端
class MyServer(socketserver.BaseRequestHandler): #通信循环在在内部会调用handlle方法 def handle(self): #在打电话中 print('conn is: ',self.request) #conn print('addr is: ',self.client_address) #addr while True: try: #收消息 data=self.request.recv(1024) if not data:break print('收到客户端的消息是',data,self.client_address) #发消息 self.request.sendall(data.upper()) except Exception as e: print(e) break if __name__ == '__main__': s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer) #多线程 s.serve_forever()
udp服务端
import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print(self.request) print('收到客户端的消息是',self.request[0]) self.request[1].sendto(self.request[0].upper(),self.client_address) if __name__ == '__main__': s=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer) s.serve_forever()