• python---socket套接字,粘包问题


    目录

    socket层简述 

    socket工作流程

    基于TCP的socket服务端与客户端编程

    基础版本

    加入连接循环

    加入通信循环

    支持并发的TCP服务端

    常见问题


    scoket套接字

    基于文件类型的套接字家族名字:AF_UNIX

    基于网络类型的套接字家族名字:AF_IN

    socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
    例如我们每天浏览网页、QQ 聊天、收发 email 等等

    1.socket介绍
    1.什么是socket?
    socket套接字,是进程通信的一种实现方式
    2.主要特点
    主要针对不同主机之间的通信。即网络进程的通信,
    几乎所有的网络都是通et流程,实现网络通信

    在这里插入图片描述

     socket工作流程

    在这里插入图片描述

     


    TCP服务端

    首先拿到一个socket对象,socket对象与IP+PORT绑定才能进行端口监听.

    然后socket对象监听端口是否有请求,同时调用accept方法,一直阻塞等待客户端的链接;

    如果有客户端的请求发来,那么服务端接收到请求之后调用read方法读取数据;

    之后再处理请求,写回数据使用write方法(python中是send方法)

    当需要断开连接时服务端发送请求,客户端读取之后在调用close方法断开请求,

    服务端read收到断开请求,服务端再调用close方法即可.

    TCP客户端

    首先拿到一个socket对象,调用connect方法,传入IP与PORT,发送建立链接的请求, 

    之后就可以进行数据交互, 如果需要结束链接,直接调用close方法即可.

    基于TCP 的SOCKET服务端与客户端

    基础版本

    TCP是基于链接的, 必须先启动服务端,然后再启动客户端去链接服务器

    1. import socket
    2. import time
    3. server = socket.socket()
    4. # 注意: 传入的参数必须是一个元组或者列表
    5. # ip '127.0.0.1'是本地回环地址,只能自己玩
    6. # 如果是别的IP地址,可以在同一个局域网里一起使用
    7. server.bind(('127.0.0.1',8080))
    8. # 监听 半连接池为5,相当于队列中最多有5个, 超出5个就会报错
    9. # 同时智能服务一个人
    10. server.listen(5)
    11. # 等待客户端的链接
    12. # socket 是连接对象, address是客户端地址
    13. # 以后这个服务端和客户端使用sock这个连接对象
    14. sock,address = server.accept()
    15. # 接收客户端发了的数据
    16. # 传入的参数为一次接受的字节
    17. data = sock.recv(1024)
    18. print(f'收到了来自{address}的数据{data.decode("utf8")}')
    19. # 服务端给客户端发送消息
    20. data_send = f'收到了你发送的{data}'.encode('utf8')
    21. # 注意必须传送二进制格式数据
    22. sock.send(data_send)
    23. time.sleep(2)
    24. # 关闭连接对象,此时服务器没有关闭
    25. sock.close()
    26. # 关闭服务端
    27. server.close()

    客户端

    1. import socket
    2. client = socket.socket()
    3. # 链接服务端的地址加端口
    4. client.connect(('127.0.0.1,8080))
    5. # 连上以后就可以发送消息了
    6. client.send(b'good evening')
    7. # 收到服务端返回的消息
    8. data = client.recv(1024)
    9. print(data.decode('utf8'))
    10. client.close()

    加入连接循环

    只加入连接循环, 此时服务端会一直接收消息看相当于死循环, 服务器不会自动断开

    服务端

    1. import socket
    2. server = socket.socket()
    3. server.bind(('127.0.0.1', 8008))
    4. server.listen(5)
    5. print('等待客户端的链接')
    6. # 等待客户端的链接
    7. # 链接循环
    8. while True:
    9. sock, addr = server.accept()
    10. # 接受客户端发了的数据
    11. data = sock.recv(1024)
    12. print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
    13. # 服务端给客户端发送消息
    14. data_send = f'收到了你发送的 {data}'.encode('utf-8')
    15. # 注意:必须传送二进制的格式
    16. sock.send(data_send)
    17. # 关闭连接对象,此时服务端并没有关闭!!!
    18. sock.close()
    19. # 关闭服务端
    20. server.close()

    客户端

    1. import socket
    2. client = socket.socket()
    3. # 连接服务端的地址加端口
    4. client.connect(('127.0.0.1', 8008))
    5. # 连上以后就可以发送消息了
    6. client.send(b'good evening')
    7. # 收到服务端返回的消息
    8. data = client.recv(1024)
    9. print(data.decode('utf-8'))
    10. client.close()

    加入通信循环

    加入通信循环,此时客户端可以多次发送数据

    服务端

    1. import socket
    2. server = socket.socket()
    3. server.bind(('127.0.0.1', 8008))
    4. server.listen(5)
    5. print('等待客户端的链接')
    6. while True:
    7. sock, addr = server.accept()
    8. print(f'客户端{addr}已连接')
    9. # 等待接受客户端发的数据
    10. # 如果客户端没法,就会一直等待下去
    11. while True:
    12. try:
    13. # 同一个客户端,不停地发送
    14. data = sock.recv(1024)
    15. # 当客户端主动断开后,客户端会发送一个空的数据过来
    16. # 此时服务端必须对这种情况进行处理,否则会出现死循环
    17. if len(data) == 0:
    18. print(f'客户端{addr}已断开', '\n')
    19. break
    20. print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
    21. data_send = f'收到了你发送的 {data}'.encode('utf-8')
    22. sock.send(data_send)
    23. except Exception as e:
    24. print(e)
    25. break
    26. sock.close()
    27. # 关闭服务端
    28. server.close()

    客户端

    1. import socket
    2. client = socket.socket()
    3. client.connect(('127.0.0.1', 8008))
    4. # 连上以后就可以发送消息了
    5. while True:
    6. data = input('输入发送给服务端的消息(q to quit):')
    7. if data == 'q': break
    8. client.send(data.encode("utf-8"))
    9. data = client.recv(1024)
    10. print(data.decode('utf-8'))
    11. client.close()

    支持并发的TCP服务端

    在这里是开启多进程完成的并发,可以换成多线程, 只要将Process类换成Thread类

    1. import socket
    2. from multiprocessing import Process
    3. def talk(sock,addr):
    4. print('客户端连接成功', addr)
    5. while True:
    6. try:
    7. data = sock.recv(1024)
    8. if len(data) == 0:
    9. print(f'客户端{addr}已断开', '\n')
    10. break
    11. print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
    12. data_send = f'收到了你发送的 {data}'.encode('utf-8')
    13. sock.send(data_send)
    14. except Exception as e:
    15. print(e)
    16. break
    17. sock.close()
    18. if __name__ == '__main__':
    19. server = socket.socket()
    20. server.bind(('127.0.0.1', 81))
    21. server.listen(5)
    22. while True:
    23. sock,addr = server.accept()
    24. print(f'客户端{addr}已连接')
    25. # 连接上一个客户端之后开启一个进程,对该客户端进行服务
    26. p = Process(target=talk, args=(sock, addr))
    27. p.start()
    28. server.close()

    常见问题:

    1.发送消息不能为空 >>>> 统计长度饼判断即可

    2. 反复重启服务端可能会报错 >>> address in use

            这个错在苹果本上会比较频繁出现,windows频率较少

    from socket import SOL_SOCKET,SO_REUSEADDR
    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加

    3. 连接循环

    如果是Windows电脑 客户端异常推出之后服务端会直接报错

    处理方式 >>> 异常处理

    如果是mac或者linux服务端会接收到一个空消息

    处理方式 >>> len判断

    客户端如果异常断开,服务端代码应该重新回到accept等待新的客人

    目前的服务端只能实现一次服务一个人 不能做到同时服务多人, [并发状态下可以实现]

    4. 加入通信循环之后,想要同一个py文件启动多个客户端, pycharm会出现以下提示,不允许启动多个

    在这里插入图片描述

     解决办法

     

     

     

     

    半连接池

    1. listen(5)
    2. # py文件默认同一时间只能运行一次
    3. # 半连接池
    4. # 设置最大等待人数 >>> 节省资源 提高CPU利用率

    粘包问题

    1. data1 = conn.recv(1024)
    2. print(data1)
    3. data2 = conn.recv(1024)
    4. print(data2)
    5. data3 = conn.recv(1024)
    6. print(data3)
    7. client.send(b'hello')
    8. client.send(b'jason')
    9. client.send(b'kevin')
    10. """
    11. 三次打印的结果
    12. b'hellojasonkevin'
    13. b''
    14. b''
    15. """

    TCP协议的特点

    会将数据量比较小 并且时间间隔比较短的数据整合到一起发送

    并且还会受制于recv()内数字的大小,此现象我们称之为''六十协议''

    >>> 问题产生的原因是因为recv括号内我们不知道即将要接收的数据到底有多大, 如果每次接受的收据我们都能够精准的知道他的大小, 那么肯定不会有粘包问题出现<<<<

    如果我们能知道即将要接收的数据量大小那么粘包问题就解决了~

    解决粘包问题

    1. # struct模块
    2. import struct
    3. data1 = 'hello world!'
    4. print(len(data1)) # 12
    5. res1 = struct.pack('i', len(data1)) # 第一个参数是格式 写i就可以了
    6. print(len(res1)) # 4
    7. ret1 = struct.unpack('i', res1)
    8. print(ret1) # (12,)
    9. data2 = 'hello baby baby baby baby baby baby baby baby'
    10. print(len(data2)) # 45
    11. res2 = struct.pack('i', len(data2))
    12. print(len(res2)) # 4
    13. ret2 = struct.unpack('i', res2)
    14. print(ret2) # (45,)

    pack可以讲任意长度的数字打包成固定长度

    unpack可以将固定长度的数字解包成打包之前的数据真实长度

    >>> 将真实数据打包成固定长度的包

    >>> 将固定长度的包发送给对方

    >>> 对方接收到包之后再解包获取真实数据长度

    >>> 接受真是长度的数据

    <<<<<<< 1.先接收固定长度的报头 
            2.再根据报头解压出真实长度 
            3.根据真实长度接收即可<<<<<<<<<<<<<<

    struct模块针对数据量特别大的数字没有办法打包

    UDP协议

    服务端

    1. # 服务端
    2. import socket
    3. server = socket.socket(type=socket.SOCK_DGRAM)
    4. server.bind(('127.0.0.1', 8080))
    5. msg, address = server.recvfrom(1024)
    6. print('msg>>>:%s' % msg.decode('utf8'))
    7. print('address>>>:',address)
    8. server.sendto('我是服务端 你好啊'.encode('utf8'), address)

    客户端

    1. # 客户端
    2. import socket
    3. client = socket.socket(type=socket.SOCK_DGRAM)
    4. server_address = ('127.0.0.1', 8080)
    5. client.sendto('我是客户端 想我了没'.encode('utf8'), server_address)
    6. msg, address = client.recvfrom(1024)
    7. print('msg>>>:%s' % msg.decode('utf8'))
    8. print('address>>>:',address)

    服务端不需要考虑客户端是否异常退出

    UDP不存在粘包问题, UDP多用于短消息交互,如QQ

  • 相关阅读:
    如何实现 Es 全文检索、高亮文本略缩处理(封装工具接口极致解耦)
    GO语言:文件操作之写入文件及iota生成常量
    面试官:设计模式之简单工厂模式
    【学习笔记】ABC265/AGC012
    Mybatis&MybatisPlus 操作 jsonb 格式数据
    Diffusion Model论文/DALL E 2
    超简单的视差滚动网站
    Ajax解析
    IndexTree以及应用
    【Linux】如何判断RS-232串口是否能正常使用
  • 原文地址:https://blog.csdn.net/weixin_67531112/article/details/126290485