python 套接字编程的大致流程如下:
server端:
client端 :
在此基础上我们建立一个最基本的服务端,客户端(也就是所谓的cs模型)
server:
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 8081)) server.listen(5) conn, client_addr = server.accept() msg = conn.recv(1024) print(msg) conn.close() server.close()
client
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8081)) client.send("hello, server".encode("utf-8")) client.close()
这大概是最简单的cs模型了,事实上这样只完成了一次从客户端向服务端发送了一条消息,然后就关闭了连接。在实际情况中,服务端应该始终保持链接,通信也就是
上图中的链接循环,通信循环。
我们对上面的代码进行修改:
server:
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket BUFF_SIZE = 1024 IP_PORT = ("127.0.0.1", 8081) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(IP_PORT) server.listen(5) while True: conn, client_addr = server.accept() while True: msg = conn.recv(BUFF_SIZE) if not msg: break else: conn.send(msg) msg = msg.decode("utf-8") print(client_addr, msg) conn.close() server.close()
client:
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket BUFF_SIZE = 1024 IP_PORT = ("127.0.0.1", 8081) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(IP_PORT) while True: msg = input(">>:").strip().encode("utf-8") if not msg: break else: client.send(msg) response = client.recv(BUFF_SIZE) if response: print(response.decode("utf-8")) client.close()
这样就实现了简单的链接循环,通信循环。
但还有一个问题,就是可能出现粘包,
1.由于tcp的机制,假如我一次只发送了5bytes的数据,它可能只是缓存起来了,并没有直接发送,
等到我再次发送了若干数据(如100bytes)后它将这些数据一起发送出去,这样数据在客户端已经粘包。
2.同样,在服务端解码时也可能出现,也可能出现粘包现象,即将前后多个包一次解码,数据在服务端出现粘包。
那么,要怎么解决呢?
可以看到问题就出在解包时不知道某个包的具体长度是多少,如果我们知道某个包的长度,只需要按照这个包的长度
去解包,即使它粘包了,我们仍然能解出正确的数据来。
那么就需要我们来定制报头,来告知解码的一方这个包的长度:
下面以模拟ssh来做这个例子,
我们约定报头的长度为四个字节。
server:
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket,json, struct, subprocess BUFF_SIZE = 1024 IP_PORT = ("127.0.0.1", 8081) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)# 重用端口 server.bind(IP_PORT) server.listen(5)# 设置可以接受的连接数量 while True:# 外层循环为链接循环 conn, client_addr = server.accept() while True:# 内层循环为通信循环 msg = conn.recv(BUFF_SIZE) if not msg: break pipes = subprocess.Popen(msg.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) error = pipes.stderr.read() if error: print("Error:",error) response_msg = error else: response_msg = pipes.stdout.read() header = {'data_size':len(response_msg)}# 数据长度 header_json = json.dumps(header)#序列化 header_json_byte = bytes(header_json,encoding="utf-8") conn.send(struct.pack('i',len(header_json_byte))) #先发送报头长度,仅包含数据长度, 这里的i指int类型 conn.send(header_json_byte)# 再发送报头 conn.sendall(response_msg)# 正式的信息 print("Request from:",client_addr, "Command:",msg) conn.close() server.close()
client:
#!/usr/bin/env python #coding:utf-8 #Created by Andy @ 2017/9/16 import socket, json, struct BUFF_SIZE = 1024 IP_PORT = ("127.0.0.1", 8081) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(IP_PORT) while True: msg = input(">>:").strip().encode("utf-8") if not msg: break client.send(msg) header = client.recv(4) print("Header:",struct.unpack("i", header)) header_length = struct.unpack('i', header)[0] print("Header_length:", header_length) header_json = json.loads(client.recv(header_length).decode("utf-8")) data_size = header_json['data_size'] print("Data_size:",data_size) recv_size = 0 recv_data = b'' while recv_size < data_size: recv_data += client.recv(BUFF_SIZE) recv_size += len(recv_data) print(recv_data.decode("gbk")) client.close()
大致流程是这样的
事实上,上面的例子只完成了一个客户端与服务器进行通信的功能,并没有实现如
server端中写的server.listen(5),同时与5个客户端通信,要看如何实现可以参考: