• 基于TCP协议的socket通信


    1. 单对单循环通信

    ​ send() 和recv()不是一一对应的。

    # 服务端 server.py  
    import socket
    phone = socket.socket()
    
    phone.bind = (('127.0.0.1', 8888))  # 绑定本地回环地址/端口号
    phone.listen()  # 开始监听
    conn, addr = phone.accept()  # 等待连接
    print(f"连接{addr}成功!")
    
    while 1:
        try:
        	from_client_data = conn.recv(1024)
        	if from_client_data.upper() == b'Q':   # 字节bytes Q
            	print('客户端已正常退出!')
            	break
            print(f"来自客户端{addr}的消息:{from_client_data.decode('utf-8')}")
            
            to_server_data = input('>>>').strip().encode('utf-8')
            phone.send(to_server_data)
        except ConnectionResetError:
            print('客户端连接中断!')
            break
    conn.close()
    phone.close()
        
    
    # 客户端 client.py
    
    import socket
    
    phone = socket.socket()
    phone.connect(('127.0.0.1',8888))
    
    while 1:
        to_server_data = input('>>>').strip().encode('utf-8')
        if not to_server_data:
            print('输入内容不能为空!')
            continue
        phone.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
        from_server_data = phone.recv(1024)
        print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
        
    phone.close()
    

    2. 循环连接通信:可连接多个客户端

    # 服务端循环接收连接
    import socket
    phone = socket.socket()
    phone.bind(('127.0.0.1',8848))
    phone.listen()
    while 1:
    	conn,addr = phone.accept()		# 循环可接收客户端
    	print(f'与客户端{addr}连接成功!')
        while 1:
            try:
                from_client_data = conn.recv(1024)
                if from_client_data.upper() == b'Q':
                    print('客户端已退出!')
                    break
                print(f"来自客户端的消息:{from_client_data.decode('utf-8')}")
                to_client_data = input('>>>').strip().encode('utf-8')
                conn.send(to_client_data)
            except ConnectionResetError:
                print('客户端连接中断!')
                break
        conn.close()
    phone.close()
                
    
    # 客户端 client
    import socket
    phone = socket.socket()
    phone.connect(('127.0.0.1',8848))
    while 1:
        to_server_data = input('>>>').strip().encode('utf-8')
        if not to_server_data:
            print('输入内容不能为空!')
            continue
        phine.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
        from_server_data = phone.recv(1024)
        print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
    phone.close
    

    3. 执行远程命令

    # 使用subproess模块 可像操作系统cmd一样执行命令
    import subprocess
    
    obj = subprocess.Popen('dir',
                     shell=True,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE,         
                    )
    
    print(obj.stdout.read().decode('gbk'))  # 正确命令
    print(obj.stderr.read().decode('gbk'))  # 错误命令
    # 如果是正确的命令,那么错误命令会输出空字符。
    
    # shell: 命令解释器,相当于调用cmd 执行指定的命令。
    # stdout:正确结果丢到管道中。
    # stderr:错了丢到另一个管道中。
    # windows操作系统的默认编码是gbk编码。
    
    # 在服务端 增加subprocess模块,
    
    import subprocess
    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    phone.bind(('127.0.0.1', 8888))
    
    phone.listen()
    while 1:
        conn, addr = phone.accept()
        print(f'连接{addr}成功!')
        while 1:
            try:
                from_client_data = conn.recv(1024)
                if from_client_data.upper() == b'Q':
                    print('客户端正常退出!')
                    break
                # print(f"来自客户端{addr}的消息:{from_client_data.decode('utf-8')}")
                obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       )
    
                to_client_data = obj.stdout.read() + obj.stderr.read()
                # subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
                conn.send(to_client_data) # 此时是gbk的bytes的类型
            except ConnectionResetError:
                print('客户端连接中断!')
                break
        conn.close()
    phone.close()
    
    # 客户端 client
    import socket
    
    phone = socket.socket()
    phone.connect(('127.0.0.1', 8888))
    
    while 1:
        to_server_data = input('>>>').strip().encode('utf-8')
        if not to_server_data:
            print('输入内容不能为空!')
            continue
        phone.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
    
        from_server_data = phone.recv(1024)
        print(f"{from_server_data.decode('gbk')}")  # 需gbk解码
    
    phone.close()
    

    4. 粘包现象

    4.1 socket缓冲区

    1565863361890

    ​ 每个socket,都会有两个缓冲区,输入/输出缓冲区。过程如上图,send()结束后,会马上进入recv()状态(有阻塞)。

    ​ 作用:1. 展示存储一些数据;2. 如果网络有波动,缓冲区会保护数据的收发稳定、匀速。

    ​ 缺点:是会造成粘包的现象之一。

    4.2 出现粘包的情况:

    ​ 1. 如果客户端send() 数据超过recv() 设定的字节数,会先接收最大限制的字节数,当第二次send() 数据时,recv() 会将上次遗留的数据接收,产生粘包现象。

    1. 连续短暂send() 多次(数据量较小),数据会统一发送出去,产生粘包。

      (Nagle算法)

    4.3 解决粘包现象

    #思路:
    	服务端发出send() 数据有10000字节,客户端接收数据时,循环recv()接收,每次(至多)接收1024字节,直至将所有的字节全部接收完毕,将接收的数据拼接在一起,编码打印出。
    	
    #遇到的问题:
    	1. recv() 次数无法确定。
    	客户端先接收总数据的一个长度,然后再循环recv 控制循环次数,只有接收的数据长度 < 总数据长度,就会一直接收。(先发总数据长度,再发总数据)
    	2.获取总数据的长度是 int 类型,先要转换成 bytes 类型,再发出,但是总数据的长度转化成的 bytes类型的字节数是不固定的。
    	int:376 ——> str(376):'376' ——> bytes:b'376'  (长度为3)
    	int:4558 ——> str(4558):'4558' ——> bytes:b'4558'  (长度为4)     解决:将不固定长度的int类型转换成固定长度的bytes,并且还可以反解回来。(用来制定固定长度的报头)
    
    
    # 将不固定长度的int类型转换成固定长度的bytes
    
    import struct
    ret = struct.pack('i',376) # 将int 376转换成固定4个长度的bytes字节
    # i 默认长度为 4; q 默认为8
    print(ret,type(ret),len(ret))
    
    ret1 = struct.unpack('i',ret)[0]    # 是元组形式, 反解成int型
    print(ret1,type(ret1))
    
    # 但是通过struct 处理不能处理太大数据
    ret = struct.pack('l', 4323241232132324)
    print(ret, type(ret), len(ret))  # 报错
    
    # low版解决粘包现象
    
    # 服务端server
    import socket
    import subprocess
    import struct
    
    phone = socket.socket()
    
    phone.bind(('127.0.0.1',8888))
    phone.listen()
    
    while 1:
        conn, addr = phone.accept()
        print(f'与客户端{addr}连接')
    
        while 1:
            try:
                from_client_data = conn.recv(1024)
                if from_client_data.upper() == b'Q':
                    print('客户端已退出!')
                    break
                obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       )
                total_data = obj.stdout.read() + obj.stderr.read()
    
                conn.send(struct.pack('i', len(total_data)))  # 发出 总数据长度的bytes类型
                print(len(total_data))
                conn.send(total_data)   # 发出总数据 gbk编码的字节
            except ConnectionResetError:
                print('与客户端连接中断!')
                break
        conn.close()
    phone.close()
    
    
    # 客户端client
    
    import socket
    import struct
    
    phone = socket.socket()
    
    phone.connect(('127.0.0.1', 8888))
    
    while 1:
    
        to_server_data = input('>>>').strip().encode('utf-8')
        if not to_server_data:
            print('内容不能为空!')
            continue
        phone.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
    
        head_bytes = phone.recv(4)   # 接收总数据长度的bytes
        head_int = struct.unpack('i', head_bytes)[0]  # 反解成int
        print(head_int)
        data = b''		# 定义一个空字节
        while len(data) < head_int:
            data = data + phone.recv(1024)
    
        print(len(data))
        print(f"{data.decode('gbk')}")  # 终端是 gbk 编码的
    
    phone.close()
    
    

    bytes

    ​ 用于网络传输、文件存储时。

    能够保持数据原本类型。

  • 相关阅读:
    单例和静态类
    Aggregate
    lc.exe已退出代码为1
    MVC 使用entity framework 访问数据库 发布IIS
    MVC 发布
    Nhiberate (三)测试
    Nhiberate (二) 搭项目
    初次安装git配置
    十大Intellij IDEA快捷键(转)
    Git强制覆盖master分支
  • 原文地址:https://www.cnblogs.com/liwenhu/p/11401619.html
Copyright © 2020-2023  润新知