zoukankan      html  css  js  c++  java
  • Python网络编程(2)-粘包现象及socketserver模块实现TCP并发

    1、 基于Tcp的远程调用命令实现

      很多人应该都使用过Xshell工具,这是一个远程连接工具,通过上面的知识,就可以模拟出Xshell远程连接服务器并调用命令的功能。

      Tcp服务端代码如下:

     1 import socket,subprocess
     2 ip_port = ("127.0.0.1",8000)
     3 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 tcp_server.bind(ip_port)
     5 tcp_server.listen(5)
     6 print("the server has started")
     7 while True:
     8     try:
     9         conn,addr = tcp_server.accept()
    10         cmd = conn.recv(1024)
    11         if not cmd:break
    12         res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
    13                                stdout=subprocess.PIPE,
    14                                stdin=subprocess.PIPE,
    15                                stderr=subprocess.PIPE)
    16         error = res.stderr.read()
    17         if error:
    18             cmd_res = error
    19         else:
    20             cmd_res = res.stdout.read()
    21         if not cmd_res:
    22             cmd_res = "the reply is".encode("gbk")
    23         conn.send(cmd_res)
    24     except Exception:
    25         break

      Tcp客户端代码如下:

     1 import socket,subprocess
     2 ip_port = ("127.0.0.1",8000)
     3 tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 tcp_client.connect(ip_port)
     5 while True:
     6     cmd = input(">>>>").strip()
     7     if not cmd:continue
     8     if cmd ==quit:break
     9     tcp_client.send(cmd.encode("utf-8"))
    10     cmd_res = tcp_client.recv(1024)
    11     print("the reply is ",cmd_res.decode("gbk"))
    12 tcp_client.close()

       但在用上面两个代码进行模拟Xshell远程调用的过程中,可能会出现一个问题:粘包

    2、 粘包

      在数据信息传输过程中,发动数据的速度可能是3kb/s,但是接收数据的速度可能是10kb/s,甚至更快,应用程序看到的数据都是一个整体或可以说是一个流(stream),一条数据信息有多少字节,应用程序是不可见的。Tcp是面向连接,面向流的协议,这也就成为Tcp容易出现粘包现象的原因。基于Tcp的套接字客户端像服务端上传文件的时候,文件的内容是按照一个个小的字节段传输的,接收方根本不知道什么时候开始,什么时候结束。

      而Udp将数据信息分成一个个小的数据段,在提取数据的时候,不能以字节为单位任意提取数据,只能以数据段为单位进行提取。所以只有Tcp才会出现粘包现象,而Udp就不会。

      粘包问题主要造成原因就是接收方不知道数据消息之间的界限,(即开始点和终止点),不知道一次所需提取的数据是多少,所以造成的问题。

      Tcp是基于数据流、面向连接的协议,因为收发的消息不能为空,在服务端和客户端都必须添加空消息的处理机制,防止程序运行过程中卡住。Udp是基于数据报、无连接的协议,即使发送的是空消息,但实质上它会将发送的空消息封装上消息头,不会收到影响。

      在如下两种情况下很容易发生粘包现象:

      (1)   发送数据时间的间隔太短,数据量很小,就会粘合在一种,产生粘包现象。

      (2)   客户端发送数据后,服务端只接受一部分,再次发送其它数据的时候,服务端会先从缓冲区提取上一次的遗留数据,产生粘包。

      解决粘包问题的根本就在于:要让接收方知道将要接收数据消息的长度。基于上面的Tcp远程调用命令的程序,该如何解决粘包问题?

      服务端:

     1 import socket,subprocess,struct
     2 ip_port = ("127.0.0.1",8000)
     3 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 tcp_server.bind(ip_port)
     5 tcp_server.listen(5)
     6 print("the server has started")
     7 while True:
     8     while True:
     9         conn,addr = tcp_server.accept()
    10         try:
    11             cmd = conn.recv(1024)
    12             if not cmd:break
    13             res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
    14                                    stdout=subprocess.PIPE,
    15                                    stdin=subprocess.PIPE,
    16                                    stderr=subprocess.PIPE)
    17             error = res.stderr.read()
    18             if error:
    19                 cmd_res = error
    20             else:
    21                 cmd_res = res.stdout.read()
    22             if not cmd_res:
    23                 cmd_res = "the reply is".encode("gbk")
    24             length = len(cmd_res)
    25             send_length = struct.pack("i",length)
    26             conn.send(send_length,cmd_res)
    27         except Exception:
    28             break

      客户端:

     1 import socket,subprocess,struct
     2 from functools import partial
     3 ip_port = ("127.0.0.1",8000)
     4 tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 tcp_client.connect(ip_port)
     6 while True:
     7     cmd = input(">>>>").strip()
     8     if not cmd:continue
     9     if cmd ==quit:break
    10     tcp_client.send(cmd.encode("utf-8"))
    11  
    12 
    13     recv_length = tcp_client.recv(4)
    14     length = struct.unpack("i",recv_length)[0]
    15     recv_msg = "".join(iter(partial(tcp_client.recv,1024),b""))
    16     print("the reply is",recv_msg.decode("gbk"))
    17 tcp_client.close()

    3、 socketserver模块

      在使用上面的程序进行通信时,是没有达到并发的。也就意味着,当第一个客户端占用服务端的资源的时候,第二个客户端再向服务端发起请求,是无法进行通信的。通过sockserver可以实现该功能。

      sockserver是标准库中的一个高级模块,通过socketserver,将可以使用类来编写网络应用程序,以面向对象的方式的处理方式将更具有组织性和逻辑性。

      socketserver包含了4个基本的类:基于TCP流式套接字的TCPServer;基于UDP数据报套接字的UDPServer,以及基于文件的基础同步UnixStreamServer和UnixDatagramServer。使用最多的TCPServer,后面三个很少用到。

      在socketserver服务器框架中,基本所有的处理逻辑代码会放在一个请求处理程序中,(即一个类),服务端每当收到一个请求,就会实例化一个处理程序,并且调用相应的方法来响应客户端发来的请求,BaseRequestHandler类把所有的操作都放在handle方法中,这个方法会访问属性self.request中的客户端套接字。

      服务端做如下改写:

     1 import socketserver,struct
     2 class Server2(socketserver.BaseRequestHandler):
     3     def handle(self):
     4        while True:
     5            try:
     6                data = self.request.recv(1024)
     7                if not data:break
     8                self.request.send(data.title())
     9            except Exception:
    10                break
    11 if __name__ == "__main":
    12     s = socketserver.ThreadingTCPServer(("127.0.0.1",8080),Server2)
    13     s.serve_forever()

       在这个程序中,self.request就等同于前面创建简单TCP服务端的conn,通过重写handle方法,该方法在默认情况下为空。当接收到一个客户端请求的时候,他就会调用handle()方法,然后通过socketserver.ThreadingTCPServer方法,传入给定的主机ip地址、端口号和所定义的类,最后使用serve_forever()方法来启动TCP服务器。无限循环的等待并服务于客户端请求。

  • 相关阅读:
    [51Nod] 配对
    [Poj] Roads in the North
    【Java学习笔记之二十六】深入理解Java匿名内部类
    【Java学习笔记之二十五】初步认知Java内部类
    【Java学习笔记之二十四】对Java多态性的一点理解
    【Java学习笔记之二十三】instanceof运算符的用法小结
    【Java学习笔记之二十二】解析接口在Java继承中的用法及实例分析
    图论--拓扑排序模板
    hdu 5384 AC自动机
    java大数
  • 原文地址:https://www.cnblogs.com/Chen-Zhipeng/p/8488901.html
Copyright © 2011-2022 走看看