网络编程(粘包问题)

一.socket

理解socket:

  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

 

其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

二.套接字的初使用

2.1基于TCP协议的socket

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

套接字初次使用:

#客户端
import socket

client = socket.socket()  # 买了个电话
client.connect(('127.0.0.1', 8080))  # 插上电话卡 (地址是以元组的形式书写的,前一个是IP后面是端口)
client.send(b'hello')  # 对别人说话
res = client.recv(1024)  # 听别人说话
print(res)
client.close()  # 挂电话

#b'HELLO'
#服务端
import socket
from socket import SOL_SOCKET,SO_REUSEADDR  # 和下面的组合使用,避免端口被占用问题
server = socket.socket()  # 买电话
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 端口占用2行一起使用,写在绑定ip前面
server.bind(('127.0.0.1', 8080))  # 绑定电话卡
server.listen()  # 开机 半连接池
conn, addr = server.accept()  # 在门扣等候着客人上门

data = conn.recv(1024)  # 听别人说话
print(data)
conn.send(data.upper())  # 给别人回话
conn.close()  # 挂电话

#b'hello'

半连接池:半连接池是对请求连接成功数进行限制,若半连接池数量为5,那就是当服务器在服务一个对象时,可以再有五个对象排队,当第六个进来时就不接受,您可以去下家了.

连接循环和通信循环:

#客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:  # 不停的输入值
    msg = input('请输入>>>:').encode('utf-8')
    if len(msg) == 0: continue
    client.send(msg)
    res = client.recv(1024)
    print(res)

import socket
from socket import SOL_SOCKET, SO_REUSEADDR

server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8080))
server.listen()
while True:
    conn, addr = server.accept()  # 若没人来会一直再此等待
    while True:  # 用于不断的从客户端接收信息
        try:  # 用于捕获异常,再此处为客户端直接退出
            data = conn.recv(1024)
            if len(data) == 0: break  # 针对mac和linux客户端异常退出后,接收的一致为b''
            print(data)
            conn.send(data.upper())
        except ConnectionResetError as e:  # 异常信息break
    conn.close()

 

 TCP的粘包问题:

#服务器
import socket


server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8082))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池


conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(4)  # 听别人说话 接收1024个字节数据          阻塞
print(data)

import socket


client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8082))  # 拨号   写的是对方的ip和port

client.send(b'hello')
client.send(b'world')
client.send(b'baby')

 解决粘包问题:

补充:struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

>>> struct.pack('i',1111111111111)

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
#客户端
import json
import socket
import struct

client=socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
    msg=input('请输入').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    head_dict = client.recv(4)
    dict_size=struct.unpack('i',head_dict)[0]
    dict_byte=client.recv(dict_size)
    dict_json=json.loads(dict_byte)
    print(dict_json)
    recv_size=0
    real_data=b''
    while recv_size < dict_json['file_size']:
        data=client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))
#服务端
import json
import socket
import struct
import subprocess

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd) == 0:break
            cmd=cmd.decode('utf-8')
            obj=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res=obj.stdout.read()+obj.stderr.read()
            d={'name':'zhang','file_size':len(res)}
            json_d=json.dumps(d)
            header = struct.pack('i',len(json_d))
       conn.send(header) conn.send(json_d.encode(
'utf-8')) conn.send(res) except ConnectionResetError as e: break conn.close()
解决粘包问题的步骤:

解决粘包问题
    服务端
        1.先制作一个发送给客户端的字典
        2.制作字典的报头
        3.发送字典的报头
        4.发送字典
        5.再发真实数据
    
    客户端
        1.先接受字典的报头
        2.解析拿到字典的数据长度
        3.接受字典
        4.从字典中获取真实数据的长度
        5.接受真实数据

ps:1.在这之中每个send都要和recv相对应,要不会出现两者都会再远处等待的情况

   2.recv是向内存要信息

原文地址:https://www.cnblogs.com/z929chongzi/p/11317237.html