socket分包粘包

socket在发送数据的时候,如果数据返回过大,会分批次发送,所以设计循环发送(while True),有个问题什么时候才算发送结束?

解决办法:可以事先发送数据大小,然后按照数据大小判断结束时间

但是多次发送的过程有可能导致粘包的产生,即连续两次send时间过短,导致客户端收到粘包数据,为此你需要在每一次发送过程都跟客户端做一次握手,服务器发送给客户端,客户端会跟服务器说一声收到,然后服务器接着发送,客户端接着收到,这样一来一回、常来常往就不会出现问题。

<2022-02-13 周日 00:38>下面程序未测试,目测os.popen需要进行更换。

服务端实现

import socket ,os,time
server = socket.socket()
server.bind(('localhost',9999) )

server.listen()

while True:
    conn, addr = server.accept()
    print("new conn:",addr)
    while True:
        print("等待新指令")
        data = conn.recv(1024)
        if not data:
            print("客户端已断开")
            break
        print("执行指令:",data)
## 有可能高版本得使用subprocess实现, 未测试,只是讲原理!
        cmd_res = os.popen(data.decode()).read() #接受字符串,执行结果也是字符串
        print("before send",len(cmd_res))
        if len(cmd_res) ==0:
            cmd_res = "cmd has no output..."

        conn.send( str(len(cmd_res.encode())).encode("utf-8")    )     #先发大小给客户端
        time.sleep(0.5)  ## 这不是一个好办法,需要改进,可以通过conn.recv()改进
        conn.send(cmd_res.encode("utf-8"))
        print("send done")
        os.path.isfile()
        os.stat("sock")
server.close()

import hashlib
m = hashlib.md5()

改进版本:

  import socket ,os,time
  server = socket.socket()
#服务器端0.0.0.0的话可以访问多个服务端的ip
#如果只有一个网卡,那么0.0.0.0和指定ip一样

  #server.bind(('localhost',9999) )
  server.bind(('0.0.0.0',9999) )

  server.listen()

  while True:
      conn, addr = server.accept()
      print("new conn:",addr)
      while True:
          print("等待新指令")
          data = conn.recv(1024)
          if not data:
              print("客户端已断开")
              break
          print("执行指令:",data)
  ## 有可能高版本得使用subprocess实现, 未测试,只是讲原理!
          cmd_res = os.popen(data.decode()).read() #接受字符串,执行结果也是字符串
          print("before send",len(cmd_res))
          if len(cmd_res) ==0:
              cmd_res = "cmd has no output..."

          conn.send( str(len(cmd_res.encode())).encode("utf-8")    )     #先发大小给客户端
          ##time.sleep(0.5)  ## 这不是一个好办法,需要改进,可以通过conn.recv()改进
          client_ack=conn.recv(1024)  ##收到客户端的响应!!很重要,不然两次send会存在粘包现象!
          print(f'from clinet ack {client_ack}')
          conn.send(cmd_res.encode("utf-8"))
          print("send done")
          os.path.isfile()
          os.stat("sock")
  server.close()

  import hashlib
  m = hashlib.md5()

client客户端实现

import socket
client = socket.socket()

#client.connect(('192.168.16.200',9999))
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0: continue
    client.send(cmd.encode("utf-8"))
    cmd_res_size = client.recv(1024) ##接受命令结果的长度
    print("命令结果大小:",cmd_res_size)
    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()) :
        data = client.recv(1024)
        received_size += len(data) #每次收到的有可能小于1024,所以必须用len判断
        #print(data.decode())
        received_data += data
    else:
        print("cmd res receive done...",received_size)
        print(received_data.decode())


client.close()

服务器端额外需要接受一条回复。

客户端程序改进:

import socket
client = socket.socket()

#client.connect(('192.168.16.200',9999))
client.connect(('localhost',9999))

while True:
    cmd = input(">>:").strip()
    if len(cmd) == 0: continue
    client.send(cmd.encode("utf-8"))
    cmd_res_size = client.recv(1024) ##接受命令结果的长度
    print("命令结果大小:",cmd_res_size)
    client.send("准备好接收了,服务器可以发送了".encode("utf-8"))
    received_size = 0
    received_data = b''
    while received_size < int(cmd_res_size.decode()) :
        data = client.recv(1024)
        received_size += len(data) #每次收到的有可能小于1024,所以必须用len判断
        #print(data.decode())
        received_data += data
    else:
        print("cmd res receive done...",received_size)
        print(received_data.decode())


client.close()

socketServer使用

socketServer的最主要作用就是实现一个并发处理(socketserver.TCPServer改为socketserver.ThreadingTCPServer就可以变为并发程序!! 很简单! TCPServer依然是1对1程序,多个client链接服务器需要等待,而ThreadingTCPServer实现了真正的并发socket),参考下面的socketServer使用,然后再改造你的socket ssh和FTP https://jueqingsizhe66.github.io/post/simplehttpserver-python3-version/

socketServer的感觉的有点类似于线程池

socketServer相对于socket,正如线程池相对于thread.

  class MyTCPHandler(socketserver.BaseRequestHandler):
      def handle(self):
          while True:
              try:
                  self.data = self.request.recv(1024).strip()
                  print("{} wrote:".format(self.client_address[0]))
                  print(self.data)
                  self.request.send(self.data.upper())
              except ConnectionResetError as e:
                  print("err",e)
                  break
  if __name__ == "__main__":
      HOST, PORT = "localhost", 9999
      # Create the server, binding to localhost on port 9999
      server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
  ## TCPHandler有点类似于工作线程,Server分派一个一个的Handler和客户端进行交互,
## 进而实现并发socket过程!
      server.serve_forever()
## 注意ThreadingTCPServer

ThreadingTCPServer的默认实现: 多并发实现指的是多个线程执行 而还有多进程,指的是ForkTCPServer(linux有用)

def process_request(self, request, client_address):
    """Start a new thread to process the request."""
    if self.block_on_close:
        vars(self).setdefault('_threads', _Threads())
    t = threading.Thread(target = self.process_request_thread,
                         args = (request, client_address))
    t.daemon = self.daemon_threads
    self._threads.append(t)
    t.start()

https://jueqingsizhe66.github.io/post/socket%E6%9D%A5%E6%BA%90/

Related
叶昭良
叶昭良
Engineer of offshore wind turbine technique research

My research interests include distributed energy, wind turbine power generation technique , Computational fluid dynamic and programmable matter.