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取出来 |
再编码报头内容然后发送 | 根据取出的长度收取报头内容,然后解码,反序列化 |
最后发真实内容 | 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容 |