基于TCP网络通信
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
简易版网络通信模拟xshell 服务端# 服务端serverimport subprocessimport socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 拿到一个socket对象# 调用sock.setsockopt设置这个socket选项,本例中把socket.SO_REUSEADDR设置为1,表示服务器端进程终止后,# 操作系统会为它绑定的端口保留一段时间,以防其他进程在它结束后抢占这个端口。phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)phone.bind(("127.0.0.1", 8080)) # 绑定唯一一个软件的地址# 调用sock.listen通知系统开始侦听来自客户端的连接,参数是在队列中的最大连接数。phone.listen(5)print("starting...")while True: # 服务器循环 conn,addr=phone.accept() # 卡在这里直到,客户端返回一个元组,里面包含了socket对象以及客户端的地址 # print("客户端对象", conn) print("客户端的ip地址", addr) while True: # 通信循环 try: data = conn.recv(1024) print("客户端发送的消息是", data) # conn.send(data.upper()) res = subprocess.Popen(data.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) data1 = res.stderr.read() data2 = res.stdout.read() conn.send(data1) conn.send(data2) except Exception: break conn.close() # 关闭客户端套接字对象phone.close() # 关闭服务端套接字对象
客户端
# 客户端Clientimport socketphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建客户端套接字对象phone.connect(("127.0.0.1", 8080)) # 尝试连接服务器while True: # 通讯循环,没有监听循环 msg = input(">>:").strip() if not msg: continue phone.send(msg.encode("utf-8")) data2 = phone.recv(1024) # 指定从缓存中取数据的最大字节 print(data2.decode("gbk"))phone.close() # 关闭客户端套接字对象
基于UDP网络通信
UDP服务端
# UDP通信之服务端import socketudpserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# udpserver.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)udpserver.bind(("127.0.0.1",8080))while True: # 通信循环 data, client_addr = udpserver.recvfrom(1024) print(data.decode("utf-8")) # dada print(client_addr) # ('127.0.0.1', 60167) udpserver.sendto(data.upper(), client_addr)
UDP客户端
# UDP通信之客户端import socketudpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)server_ip_port = ("127.0.0.1", 8080)while True: inp = input(">>:") udpclient.sendto(inp.encode("utf-8"), server_ip_port) data,server_addr = udpclient.recvfrom(1024) print(data.decode("utf-8"))
粘包现象
只有TCP有粘包现象,UDP永远不会粘包
在上述简易通信模型里,在客户端执行ipconfig,在执行cls。发现并未一次取完ipconfig的执行结果,执行cls时,仍打印ipconfig的结果,那是因为ipconfig执行结果大于1024字节,而客户端收数据时,只收取1024个字节。切客户段和服务端都是从操作系统的缓存中拿数据。 粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的用json和struct解决方案struct模块 该模块可以把一个类型,如数字,转成固定长度的bytes# 把数字转换成四个字节的bytes,数字须小于int最大长度struct.pack('i',12345678) # b'Na\xbc\x00'
解决粘包
服务端# 服务端(解决粘包问题)import subprocessimport socketimport structimport jsonphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买手机phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)phone.bind(("127.0.0.1", 8080)) # 绑定手机卡phone.listen(5)print("starting...")while True: conn,addr=phone.accept() # c端连接成功返回一个元组,里面包含了socket对象以及客户端的地址 # print("电话线路是", conn) print("客户端的手机号是", addr) while True: try: data = conn.recv(1024) print("客户端发送的消息是", data) # conn.send(data.upper()) res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) data1 = res.stderr.read() # dos错误的运行结果 data2 = res.stdout.read() # dos正确的运行结果 data_size = len(data1)+len(data2) # 得到原始数据总大小 print(data_size) data_dic = {"size": data_size} # 为避免粘包,自定制报头 data_json = json.dumps(data_dic) # 序列号报头 data_json_bytes = data_json.encode("utf-8") # 序列化并转成bytes,用于传输 print(data_json) data_len = struct.pack("i", len(data_json_bytes)) # 打包得到报头长度 # part1: 先发送报头的长度 conn.send(data_len) # part2: 发送报头 conn.send(data_json_bytes) # part3: 发送原始数据 conn.send(data1) conn.send(data2) except Exception: break conn.close()phone.close()
客户端
# 客户端(解决粘包问题)import socketimport structimport jsonphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.connect(("127.0.0.1", 8080))while True: msg = input(">>:").strip() if not msg: continue phone.send(msg.encode("utf-8")) head_dic_len = phone.recv(4) # part1: 接受报头的长度 head_dic_size = struct.unpack("i", head_dic_len)[0] # 取出报头长度 head_json = phone.recv(head_dic_size) # part2: 接受报头 head_dic = json.loads(head_json.decode("utf-8")) # 反序列化拿到报头数据 data_size = head_dic["size"] # 拿出原始数据大小 recv_size = 0 recv_data = b"" while data_size > recv_size: data = phone.recv(1024) # part3: 接受原始数据 recv_size += len(data) recv_data += data print(recv_data.decode("gbk"))phone.close()
socketserver实现并发
TCP服务端
# 基于socketserver并发TCP通信# 服务端import socketserverclass FtpServer(socketserver.BaseRequestHandler): def handle(self): # TCP下的request就是conn,客户端套接字 print(self.request) # return self.socket.accept() while True: data=self.request.recv(1024) print(data.decode("utf-8")) self.request.send(data.upper())if __name__ == '__main__': obj = socketserver.ThreadingTCPServer(("127.0.0.1",8080),FtpServer) print(obj.server_address) # print(obj.RequestHandlerClass) # print(obj.socket) obj.serve_forever() # 链接循环
TCP客户端
# 于socketserver并发TCP通信# 客户端import socketserver_addr = ("127.0.0.1",8080)phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.connect(server_addr)while True: # 通讯循环 inp = input(">>:") phone.send(inp.encode("utf-8")) data = phone.recv(1024) print(data)
并发UDP服务端
# UDP通信并发# 服务端import socketserverclass UdpServer(socketserver.BaseRequestHandler): def handle(self): # UDP协议下的request=(rec_data, self.socket) # data, client_addr = self.socket.recvfrom(self.max_packet_size) # return (data, self.socket), client_addr print(self.request[0]) # (data, self.socket) self.request[1].sendto(self.request[0].upper(), self.client_address)if __name__ == '__main__': server_ip_port = ("127.0.0.1", 8080) obj = socketserver.ThreadingUDPServer(server_ip_port, UdpServer) print(obj.socket) obj.serve_forever()
并发UDP客户端
# UDP通信并发# 客户端import socketudpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)server_ip_port = ("127.0.0.1", 8080)while True: inp = input(">>:") udpclient.sendto(inp.encode("utf-8"), server_ip_port) data,server_addr = udpclient.recvfrom(1024) print(data.decode("utf-8"))
作业
简易FTP实现
服务端# ftp上传和下载(面向对象)# 服务端import socketimport jsonimport osimport structclass FtpServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding = "utf-8" request_queue_size = 5 server_dir = r"C:\\Users\\Zou\\PycharmProjects\\py_fullstack_s4\\day37\\粘包" def __init__(self, server_address, bind_and_activate=True): """构造函数""" self.server_address = server_address self.phone = socket.socket(self.address_family,self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """绑定socket对象""" if self.allow_reuse_address: self.phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1) self.phone.bind(self.server_address) # 绑定地址和端口号 self.server_address = self.phone.getsockname() def server_activate(self): """服务端对象listen""" self.phone.listen(self.request_queue_size) def server_close(self): """关闭服务端socket对象""" self.phone.close() def get_request(self): """服务端开始监听accept""" return self.phone.accept() def close_request(self, request): """关闭客户socket对象""" request.close() def run(self): while True: # 连接循环 self.conn, self.client_addr = self.get_request() print("客户端地址:", self.client_addr) while True: # 通信循环 try: head_struct = self.conn.recv(4) print(head_struct) if not head_struct:break head_len = struct.unpack("i",head_struct)[0] # 拿到报头长度 head_json = self.conn.recv(head_len).decode(self.coding) # 拿到json字符串 head_dic = json.loads(head_json) print(head_dic) # 打印用户字典 # head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345} cmd = head_dic["cmd"] if hasattr(self, cmd): func = getattr(self,cmd) func(head_dic) except Exception: break def put(self, dic): """文件上传函数""" file_path = os.path.normpath(os.path.join( self.server_dir, dic["file_name"] )) file_size = dic["file_size"] recv_size = 0 print("-------",file_path) with open(file_path,"wb") as f: while recv_size < file_size: recv_data = self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size += len(recv_data) print("recvsize:%s filesize:%s"% (recv_size, file_size))tcpserver1 = FtpServer(("127.0.0.1",8080))tcpserver1.run()
客户端
# FTP客户端(面向对象)import socketimport structimport jsonimport osclass FtpClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding = "utf-8" request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address = server_address self.phone = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except Exception: self.client_close() def client_connect(self): self.phone.connect(self.server_address) def client_close(self): self.phone.close() def run(self): """客户端主逻辑""" while True: inp = input(">>:").strip() # put a.txt if not inp:continue l = inp.split() cmd = l[0] if hasattr(self, cmd): func = getattr(self, cmd) func(l) def put(self,l): cmd = l[0] filename = l[1] if not os.path.isfile(filename): print("%s文件不存在" % filename) return filesize = os.path.getsize(filename) # head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345} head_dict = {"cmd":cmd, "file_name":filename, "file_size":filesize} head_json = json.dumps(head_dict).encode(self.coding) head_len = struct.pack("i", len(head_json)) self.phone.send(head_len) self.phone.send(head_json) send_size = 0 with open(filename, "rb") as f: for line in f: self.phone.send(line) send_size += len(line) print(send_size) else: print("文件上传成功")client = FtpClient(("127.0.0.1",8080))client.run()