Socket

socket概念

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

服务端

'''
注意:
    客户端先一次发送,服务端得先一次接受,再发送消息。
'''
import socket
server = socket.socket()
server.bind(
    # 相当于手机号码
    # 127.0.0.1 == localhost 本机回环地址
    # 单机测试下: 127.0.0.1
    ('127.0.0.1', 9527)
)
# 半连接池
server.listen(5)  # 最多5个人坐椅子, 实际上==6
print('server is running...')
# 等待电话接入
# conn: 指的是服务端往客户端的管道
conn, addr = server.accept()

# 接听对方讲话的内容
# data客户端发送过来的消息
data = conn.recv(1024)  # 可接受一次1024bytes的数据
print(data)

# 服务端往客户端发送消息
conn.send(b'hi im server')

# 挂电话
conn.close()

客户端

'''
启动服务端后再启动客户端
'''
import socket
client = socket.socket()
client.connect(
    # 客户端的ip与port必须与服务端一致
    ('127.0.0.1', 9527)
)
print('client is running...')
# 必须发送bytes类型的数据
# client.send('hello'.encode('utf-8'))
# 讲话给对方听
client.send(b'hello im client...')
data = client.recv(1024)
print(data)

client.close()

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

黏包现象

import socket
server = socket.socket()
server.bind(
    ('127.0.0.1', 9000)
)
server.listen(5)
conn, addr = server.accept()

data = conn.recv(10)
print(data)  # b'hello'

data = conn.recv(1024)
print(data)  # b'hello'

data = conn.recv(1024)
print(data)  # b'hello'
import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)
client.send(b'hello')
client.send(b'hello')
client.send(b'hello')

结果:

b'hellohello'
b'hello'
b''

会发生黏包的两种情况

情况一 发送方的缓存机制

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

情况二 接收方的缓存机制

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

总结

黏包现象只发生在tcp协议中:

1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

黏包解决方案:

struct模块:

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

import struct
obj = struct.pack('i',123456)
print(len(obj))  # 4
obj = struct.pack('i',898898789)
print(len(obj))  # 4
# 无论数字多大,打包后长度恒为4

借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

发送时 接受时
先发送struct转换好的数据长度4字节 现接受4个字节使用struct转换成数字来获取要接受的数据长度
再发送数据 再按照长度接受数据

我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

发送时 接收时
先发报头长度 先收报头长度,用struct取出来
再编码报头内容然后发送 根据取出的长度收取报头内容,然后解码,反序列化
最后发真实内容 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
 posted on 2019-12-05 23:23  Rannie`  阅读(117)  评论(0编辑  收藏  举报
去除动画
找回动画