socket编程之黏包

时间:2022-12-15 17:10:04

原理概述

socket编程之黏包

  上图是我在学习python的socket编程中遇到的黏包问题所画,以实例来说明这个高大上的黏包问题。

  我们知道socket()实例中sendall()方法是无论数据有多大,一次性提交写入缓冲区(应用层);再来看接收端,recv()方法有个参数为buffsize,没错buffsize就是套接口的发送缓冲区的大小了。所以数据大于SO_SNDBUF的就会被分块传输,问题就来了,当两次提交的数据都比较大,刚好第一次尾与第二次的首同一时间待在了SO_SNDBUF里,被接收到了,这就是黏包。

  一句话:黏包最本质的原因就是接收方不知道接收的包有多大!

解决方法(应用层维护消息和消息边界):

  1. 定长包
  2. 包尾加上\r\n标记(FTP)
  3. 包头增加包体长度。
  4. 复杂的应用层协议。

实例

  本实例多线程实例,实现的是客户端向服务端输入系统命令,服务器返回命令在本机上的执行结果。因为有些命令返回的结果是远大于1024的,所以可能出现黏包的问题。本实例的解决方案是第三条,server端每次向client端返回命令执行结果前,先发送包体大小并得到client端返回的确认信息,再发送数据,程序结构上避免了黏包问题。

TCPSocket服务端

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import SocketServer
import os

class Myserver(SocketServer.BaseRequestHandler):

    def handle(self):
        conn = self.request
        print "Client from:",self.client_address
        conn.sendall("请输入您要查询的命令")
        flag = True
        while flag:
            data = conn.recv(1024)
            print "receive cmd: %s"%data
            if data == "exit":
                flag = False
            else:
                ret = os.popen(data).read().decode("gbk").encode("utf-8")
         #发送包大小 conn.sendall(str(len(ret)))
#收到客户端确认消息 scOK = conn.recv(1024)
         #发送包体内容 conn.sendall(ret)
if __name__ == "__main__": server = SocketServer.ThreadingTCPServer(("localhost",8000),Myserver) server.serve_forever()

TCPSocket客户端

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(("127.0.0.1",8000))
sk.settimeout(5)
data = sk.recv(1024)
print "SocketServer: %s" % data
while True:
    reSize = 0
    msg = raw_input("Input:")
    sk.sendall(msg)
  #接收包的大小 totleSize
= int(sk.recv(1024))
  #收到包的大小后,给server端发送确认信息。 sk.sendall(
"It is ok") while True: data = sk.recv(1024) reSize += len(data)
    #当接收数据等于包的size后,跳出循环,停止 接收。
if reSize == totleSize: print data break print data if msg == "exit": break sk.close()