关于黏包问题:
假设某场景,客户端A在利用socket模块的方法recv(1024)时,一次只收1024个字符,但服务器将结果全发至客户端的数据可能不止1024个字符,那么剩余待发字符的还在服务器端的socket发送缓冲队列(先进先出)里,此时服务器端如果用sendall发送,那么发送缓冲队列里的前一次剩下的结果,会首先黏送发出,这就是所谓的黏包。所以此时解决的办法是将判定客户端会预计接收多少数据发送给客户端,然后再循环接收待发送的数据,直到接收字符量达到预计数量,这个预计接收数量的数值由服务器第一次用send命令发出即可。如果用sendall,由于缓冲队列有个既定的缓冲发送超时时间(此段时间很短,10ms估计),到达超时时间,服务器端的指令sendall会将队列数据全部发出,不管是否黏包(缓冲区这么设计也是为了减少IO,攒够指定字符数统一发送),所以建议在发送预计和认证发送tag时尽量用send,在发送大段数据时可以用sendall。
解决方案:
商定发送协议 发送端在发送数据之前,先把发送数据的长度告诉接收端,要发送多少数据,然后接收端根据这个数据的长度循环接收就OK协议过程:
发送端
1.send #数据长度,发送发送端的确认消息
4.recv #收到接收端的确认信息
5.send #发送数据
接收端
2.recv #获取数据长度
3.认证发送端的发送确认消息
3.send #发送接收端的确认信息
6 recv #循环接收
实例:
服务器端
import os
import SocketServer
class Mysocket(SocketServer.BaseRequestHandler):
def handle(self):
msglen = 0
msgcon = ''
print '--got connection from', self.client_address
while True:
#接收端部分
#接收发送端的发送确认消息head和待接收数据的长度size
recv = self.request.recv(1024)
print recv
head, size = str(recv).split('|')
print head,size
#认证发送端的发送确认消息ready,并发送接收端的接收确认消息start
if head == 'ready':
self.request.send('start')
while True:
#循环接收数据
msgcon += self.request.recv(1024)
msglen += len(msgcon)
print msglen,msgcon
if msglen == int(size):
print 'end this time'
break
res = os.popen(msgcon).read()
#print res
#发送端部分
#发送端发送发送确认消息,发待发数据的大小
ready_ask_tag = '|'.join(['ready',str(len(res))])
self.request.send(ready_ask_tag)
ready_ans_tag = str(self.request.recv(1024))
#认证接收端的接收确认消息
if ready_ans_tag == 'start':
#循环发送
self.request.send(res)
if __name__ == '__main__':
server = SocketServer.ThreadingTCPServer(('127.0.0.1', 8009), Mysocket)
server.serve_forever()
客户端
import socket
sock = socket.socket()
sock.connect(('127.0.0.1',8009))
if __name__ == '__main__':
while True:
msgcon = b''
msglen = 0
inp = raw_input(">>>")
#发送端模块
ready_ask_tag = '|'.join(['ready',str(len(inp))])
print ready_ask_tag
sock.send(ready_ask_tag)
read_ans_tag = str(sock.recv(1024))
print read_ans_tag
if read_ans_tag == 'start':
sock.send(inp)
#接收端模块
head,size = str(sock.recv(1024)).split('|')
if head == 'ready':
sock.send('start')
print '----=-==-=-'
while True:
msg = sock.recv(1024)
msgcon += msg
msglen += len(msg)
if msglen == int(size):
print msglen,msgcon
print 'end this time'
break
ps:发送端模块和接收端模块实际都是可以抽象成容器形式,模块化认证