• socket通信模块


    1 原理

    1.1 模型

      应用层协议需要必须传输数据,需要把数据封装为TCP/UDP包来传输,这个对TCP/UDP的封装就是socket通信。在socket里,包括send和receive。

      一个服务器上最多开通的port为65535个,一个ServerAPP监听在它的ip:port上,然后client 给这个ip:port发送socket send通信,信息里封装自己的ip:port(client的port是随机分配的),ServerAPP收到后, 返回信息给client的ip:port。clinet就收到了消息。

      

    1.2 socket families

      socket.AF_UNIX  本机之间通信

      socket.AF_INET  ipv4网络通信

    1.3 socket types

      socket.SOCK_STREAM  tcp通信,最常用。

      socket.SOCK_DGRAM   udp通信

      socket.SOCK_RAW    原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。就是说前两个是在传输层,这个是在网络层,甚至可以伪造ip地址。

    1.4 基本实现

    客户端:

    先声明一个socket实例

    然后连接到一个server

    然后发数据。

    然后接受server发过来的数据(是bytes类型,需要解码)

    import socket
    
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('localhost',3939))
    client.send(b'in client,to server')
    data = client.recv(1024)
    print(data.decode())
    client.close()
    

    服务端:

    先声明socket实例(地址族和类型)

    accept后就处于阻塞状态,等待client。conn就是为连接过来的client开启的实例。

    (这时客服端断开,server会收到一个null,会进入死循环。)

    然后使用recv方法,将接受的数据赋值给data。recv(n)中n的值,官方建议最大8192(bytes)。

    服务端后面把相关数据send给client的conn实例。

    >>>

    这样的服务端只能并发接受一个client的连接,并且这个连接断开后服务端自己也停止运行了。

    import socket
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('localhost',3939))
    server.listen()
    conn,addr = server.accept()
    print(conn,addr)
    data = conn.recv(1024)
    print('receive: %s' %(data))
    send_data = '在服务器,给客户端'.encode()
    conn.send(send_data)
    

      

    1.5 服务端响应多个client(还是单并发)

    二层循环中,if not data是为了应对client断开时发送过来的null导致的死循环

    外层循环是为了当一个client断开后,服务端重新accept阻塞,然后把新client请求复制给conn。

    在bind时,0.0.0.0代表本机的ip和127.0.0.1。如果bind ip,则客户端listen 127.0.0.1无法访问。如果bind 127.0.0.1,则网络中其他ip无法访问。0.0.0.0则都能。

    服务端接受到的数据,用subprocess.Popen调用shell。

    服务端:

    import socket
    import subprocess
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('0.0.0.0',3937))
    server.listen()
    while True:
        conn,addr = server.accept()
        while True:
            data = conn.recv(1024)
            if not data:
                print('conn lost')
                break
            print('执行指令: %s' % (data))
            p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True)
            stdout = p.stdout.read()
            if len(stdout) == 0:
                send_data = 'cmd has no output...'
            else:
                send_data = stdout
            print('data size: %d' %(len(send_data)))
            conn.send(str(len(send_data)).encode())
            conn.send(send_data)
    

      

    1.6 根据数据包长度接受多包完整数据

    客户端:

    #!/usr/bin/env python
    import socket
    
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('192.168.10.12',3937))
    
    while True:
        msg = input('>>').strip()
        if len(msg) == 0:continue    #防止空输入产生卡死
        client.send(msg.encode())   #bytes类型发送
        rece_total_size = int(client.recv(1024).decode('gbk')) #返回数据的长度
        #print(rece_total_size)
        rece_current_size = 0  #计算已经收到的长度
        rece_current_data = b'' #收集已经接受的数据段
    
        while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。
            data = client.recv(1024)
            rece_current_data += data
            rece_current_size += len(data)
            #print('rece_current_size: %d' %(rece_current_size))
    
        print(rece_current_data.decode('gbk'))  #打印最终接受的数据
    

      

    1.7 粘包

      1.5中这两条send语句,在win没有问题,但在linux上会被合并,然后从缓冲区里一次性发给client。这种紧挨着的send语句被一次性发送的现象叫粘包。

      一种解决办法是加入防粘包信号

    服务端:

    import socket
    import subprocess
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('localhost',3937))
    server.listen()
    while True:
        conn,addr = server.accept()
        while True:
            data = conn.recv(1024)
            if not data:
                print('conn lost')
                break
            print('执行指令: %s' % (data))
            p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True)
            stdout = p.stdout.read()
            if len(stdout) == 0:
                send_data = 'cmd has no output...'
            else:
                send_data = stdout
            print('data size: %d' %(len(send_data)))
            conn.send(str(len(send_data)).encode())
            conn.recv(1024) #接受防粘包信息
            conn.send(send_data)

     客户端:

    #!/usr/bin/env python
    import socket
    
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('192.168.10.12',3935))
    
    while True:
        msg = input('>>').strip()
        if len(msg) == 0:continue    #防止空输入产生卡死
        client.send(msg.encode())   #bytes类型发送
        rece_total_size = int(client.recv(1024).decode()) #返回数据的长度
        client.send(b'1')   #发送防粘包信号
        #print(rece_total_size)
        rece_current_size = 0  #计算已经收到的长度
        rece_current_data = b'' #收集已经接受的数据段
    
        while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。
            data = client.recv(1024)
            rece_current_data += data
            rece_current_size += len(data)
            #print('rece_current_size: %d' %(rece_current_size))
    
        print(rece_current_data.decode())  #打印最终接受的数据
    

      

    1.8 传文件

      socket文件传输效率低,容易丢包,实际上传文件用RabbitMQ消息队列。

    2 socketserver模块

      实际上就是对socket的封装,这里模块里包含一些简化TCP、UDP操作的类。

      class socketserver.TCPServer()   #使用tcp协议,在服务端和客户端之间提供连续的数据流。单线程。

      class socketserver.ForkingTCPServer()  #多进程

      class socketserver.ThreadingTCPServer()   #多线程

      

    2.1 TCPServer

    import socketserver
    
    class MyTCPHandler(socketserver.BaseRequestHandler):    #首先要定义一个处理基类
        def handle(self):                                   #对这个基类进行重写
            while True:
                try:
                    self.data = self.request.recv(1024).strip()     #self是一个实例,下面有requset这个子类,它里面有recv和send方法。
                    print(self.client_address[0])                   #实例的连接客户端地址,client_address[0]是ip,[1]是port
    
                    print(self.data)                                #recv的内容赋值给data
                    self.request.send(self.data.upper())
                except:                                            #当客户端断开时会抛出一个异常,所以要进行异常处理
                    print('%s disconnected..' %(self.client_address[0]))
                    break
    
    
    if __name__ == '__main__':
        HOST,PORT = '0.0.0.0',3934
        conn = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #每个请求过来,TCPServer就会去实例化MyTCPHandler这个基类,然后用它的handler去和客户端交互。
        conn.serve_forever()                                    #serve_forver表示不停息的响应。
    

      

    2.2 ForkingTCPServer

    if __name__ == '__main__':
        HOST,PORT = '0.0.0.0',3934
        conn = socketserver.ForkingTCPServer((HOST,PORT),MyTCPHandler) 
        conn.serve_forever()                                    
    

     没有启动客户端时:

    # ps aux | grep python3 | grep -v grep
    root      40981  0.0  0.7 132580  7340 pts/1    S+   14:38   0:00 python3 ts.py
    

    启动每个客户端,就会多一条进程

    [root@yhzk01 scripts]# ps aux | grep python3 | grep -v grep
    root      40981  0.0  0.7 132580  7340 pts/1    S+   14:38   0:00 python3 ts.py
    root      41069  0.0  0.4 132580  4820 pts/1    S+   14:46   0:00 python3 ts.py
    root      41070  0.0  0.4 132580  4820 pts/1    S+   14:46   0:00 python3 ts.py
    root      41071  0.0  0.4 132580  4820 pts/1    S+   14:46   0:00 python3 ts.py
    

      

    2.3 ThreadingTCPServer

    if __name__ == '__main__':
        HOST,PORT = '0.0.0.0',3934
        conn = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)
        conn.serve_forever()                                   
    

     

  • 相关阅读:
    java数组的相关方法
    spring boot 文件目录
    mysql 数据库安装,datagrip安装,datagrip连接数据库
    linux maven 的安装与配置
    java String字符串常量常用方法
    java 命名规范
    deepin 安装open jdk
    jetbrains(idea,webstorm,pycharm,datagrip)修改背景,主题,添加特效,汉化
    JVM学习(九)volatile应用
    JVM学习(八)指令重排序
  • 原文地址:https://www.cnblogs.com/jabbok/p/8991499.html
Copyright © 2020-2023  润新知