美文网首页Python全栈
31.Python之网络编程(socket模块)

31.Python之网络编程(socket模块)

作者: 免跪姓黄 | 来源:发表于2020-03-16 11:26 被阅读0次

    Python之网络编程(socket模块)

    1. 什么是socket?

      • Socket是应用层与TCP / IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个“门面模式”,它把复杂的TCP / IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
      • 基于文件类型的套接字家族:
        • 套接字家族的名字:AF_UNIX
          Unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
      • 基于网络类型的套接字家族
        • 套接字家族的名字:AF_INET
          还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,Python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET。

    1. 为什么要用socket?

      • 使用socket,我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规则去编程,写出的程序自然就是遵循TCP/UDP标准的。

    1. 基于TCP协议的socket(无并发)

      • 服务端

        # tcp是基于可靠链接的,必须先启动服务端,然后再启动客户端去链接服务端
        import socket
        
        # 由于 socket 模块中有太多的属性。因此可以使用'from module import *'语句。也就是 'from socket import *',这样我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短代码。
        # from socket import *
        # socket_server = socket(AF_INET,SOCK_STREAM)
        socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建服务器套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。
        socket_server.bind(('127.0.0.1', 8080))  # 把地址绑定到套接字,只有服务端需要绑定,IP地址填写服务器IP,端口是数字类型,1025-65530任选,端口0-1024系统占用
        socket_server.listen(5)  # 监听链接,backlog = 5 表示同一时间能接受5个请求,并不是最大连接数
        
        # 等待连接
        conn, client_address = socket_server.accept()  # 程序阻塞,等待连接,有两个参数,一个连接对象conn,一个客户端地址client_address(包含IP和端口),对象conn是tcp三次握手的产物,用来收发消息,而socket_server对象是专门用来建立连接的
        
        # 收发消息
        msg = conn.recv(1024)  # 收消息,有个返回值给msg,1024是一个最大的限制,表示最多能收 1024 bytes
        conn.send('Hello!!!'.encode('utf-8'))  # 发消息,网络中只能传输bytes类型
        
        conn.close()  # 断开(关闭)客户端套接字,回收系统资源。完成TCP四次挥手。
        
        socket_server.close()  # 断开(关闭)服务器套接字,回收系统资源
        
      • 客户端

        import socket
        
        socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建客户端套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。
        
        socket_client.connect(('127.0.0.1', 8080))  # 与服务器建立连接,地址为服务器的IP地址和端口号
        
        socket_client.send('Hello!'.encode('utf-8'))  # 发消息,注意字符串不能直接直接发,需要转换成二进制
        msg = socket_client.recv(1024)  # 收消息
        
        socket_client.close()
        

    1. 加上通信循环和连接循环的socket(无并发)(解决服务端不可以循环接收客户端信息的问题)

      • 服务端

        import socket
        
        socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 如果什么参数都不传,默认就是这个
        socket_server.bind(('127.0.0.1', 8080))
        socket_server.listen(5)
        
        while True:  # 加连接循环
            conn, client_address = socket_server.accept()
        
            while True:  # 加通信循环
                try:
                    msg = conn.recv(1024)
                    if not msg: break  # 针对Linux操作系统
                    print('客户端:', client_address)
                    conn.send(msg + b'_SB')
                except ConnectionResetError:
                    break
        
            conn.close()
        
        socket_server.close()
        
      • 客户端

        import socket
        
        socke_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        socke_client.connect(('127.0.0.1', 8080))
        
        while True:
            msg = input('>>>:')
            socke_client.send(msg.encode('utf-8'))
            msg = socke_client.recv(1024)
            print(msg)
        
        socke_client.close()
        

    1. 远程执行命令的小程序(无并发)

      • 服务端

        from socket import *
        import subprocess
        import struct  # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串
        
        socket_server = socket(AF_INET, SOCK_STREAM)
        socket_server.bind(('127.0.0.1', 8080))
        socket_server.listen(5)
        
        while True:
            conn, client_address = socket_server.accept()
            print('正在监听……')
            while True:
                try:
                    cmd = conn.recv(1024)
                    if not cmd: break
                    print('开始接收文件……')
                    obj = subprocess.Popen(cmd.decode('utf-8'),
                                           shell=True,
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.PIPE
                                           )
                    res_stdout = obj.stdout.read()
                    res_stderr = obj.stderr.read()
                except ConnectionResetError:
                    break
        
                # -*- 解决TCP粘包问题 -*-#
                # 制作固定长度的报头
                total_size = len(res_stdout) + len(res_stderr)
                header = struct.pack('i', total_size)  # 制作固定长度的报头
        
                # 发送报头
                conn.send(header)
        
                # conn.send(res_stdout+res_stderr)
                # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。
                conn.send(res_stdout)
                conn.send(res_stderr)
        
            conn.close()
        
        socket_server.close()
        
      • 客户端

        from socket import *
        import struct
        
        socket_client = socket(AF_INET, SOCK_STREAM)
        socket_client.connect(('127.0.0.1', 8080))
        
        while True:
            cmd = input('>>>:')
            if not cmd: continue  # 判断cmd不为空,if x is not None:continue 是最好的写法
            socket_client.send(cmd.encode('utf-8'))
        
            # -*- 解决TCP粘包问题 -*-#
            # 先收固定长度的报头
            header = socket_client.recv(4)
        
            # 解析报头
            total_size = struct.unpack('i', header)[0]
        
            # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取
            recv_size = 0
            res = b''
            while recv_size < total_size:
                recv_date = socket_client.recv(1024)
                res += recv_date
                recv_size += len(recv_date)
            # info = socket_client.recv(1024)
            print(res.decode('gbk'))
        
        socket_client.close()
        

    1. 远程执行命令的小程序(自定义报头,无并发)

      • 服务端

        from socket import *
        import subprocess
        import struct  # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串
        import json
        
        socket_server = socket(AF_INET, SOCK_STREAM)
        socket_server.bind(('127.0.0.1', 8081))
        socket_server.listen(5)
        
        while True:
            conn, client_address = socket_server.accept()
            print('正在监听……')
            while True:
                try:
                    cmd = conn.recv(1024)
                    if not cmd: break
                    print('开始接收文件……')
                    obj = subprocess.Popen(cmd.decode('utf-8'),
                                           shell=True,
                                           stdout=subprocess.PIPE,
                                           stderr=subprocess.PIPE
                                           )
                    res_stdout = obj.stdout.read()
                    res_stderr = obj.stderr.read()
                except ConnectionResetError:
                    break
        
                # 制作报头
                header_dic = {'total_size': len(res_stdout) + len(res_stderr), 'md5': 'xxxxxxxxxxxx', 'filename': 'xxx.py'}
                header_json = json.dumps(header_dic)  # 将字典转换成字符串
                header_bytes = header_json.encode('utf-8')  # 将字符串转换成
        
                # 获取报头长度
                header_size = len(header_bytes)
        
                # 发送报头
                conn.send(struct.pack('i', header_size))
        
                # conn.send(res_stdout+res_stderr)
                # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。
                # 发送数据
                conn.send(res_stdout)
                conn.send(res_stderr)
        
            conn.close()
        
        socket_server.close()
        
      • 客户端

        from socket import *
        import struct
        import json
        
        socket_client = socket(AF_INET, SOCK_STREAM)
        socket_client.connect(('127.0.0.1', 8081))
        
        while True:
            cmd = input('>>>:')
            if not cmd: continue  # 判断cmd不为空,if x is not None:continue 是最好的写法
            socket_client.send(cmd.encode('utf-8'))
        
            # 先收报头的长度
            header_size = struct.unpack('i', socket_client.recv(4))[0]
        
            # 接收报头
            header_bytes = socke_client.recv(header_size)
        
            # 解析报头
            header_json = header_bytes.decode('utf-8')
            header_dic = json.loads(header_json)
            total_size = header_dic['total_size']
        
            # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取
            recv_size = 0
            res = b''
            while recv_size < total_size:
                recv_date = socke_client.recv(1024)
                res += recv_date
                recv_size += len(recv_date)
            # info=socket_client.recv(1024)
            print(res.decode('gbk'))
        
        socket_client.close()
        
    1. 基于UDP的socket

      • 数据报协议(UDP)没有粘包问题,UDP协议面向无连接,发送数据,无需对方确认,发送效率高,但UDP协议有效传输数据大小为 512 bytes,超过这个大小就非常容易丢包,DNS服务使用UDP协议,由于这个限制,导致全球根服务器数量限制在13台。

      • 服务端

        import socket
        
        socket_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        socket_server.bind(('127.0.0.1', 8080))
        
        while True:
            client_msg, client_address = socket_server.recvfrom(1024)
            socket_server.sendto(client_msg.upper(), client_address)
        
      • 客户端

        import socket
        
        socket_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        
        while True:
            msg = input('>>>:').strip()
            socket_client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
            server_msg, server_address = socket_client.recvfrom(1024)
            print(server_msg.decode('utf-8'))
        

    相关文章

      网友评论

        本文标题:31.Python之网络编程(socket模块)

        本文链接:https://www.haomeiwen.com/subject/pstgehtx.html