socket基本用法,通信/连接循环,解决沾包的方法
1、socket套接字
2、通信循环、连接循环
3、TCP协议的特性
4、粘包现象
5、解决粘包现象
socket(套接字)基本代码示例
# 服务端将接收端输入的值转换为大写发送给接收端
# server端:
import socket
sk = socket.socket() # 创造一个套接字对象
sk.bind(('127.0.0.1', 8080)) # 服务端绑定本机的IP地址
sk.listen(5) # 半连接池,第一个连接的人直接接受服务,并不会存在于半连接池中
conn, addr = sk.accept()
data = conn.recv(1024) # 规定的一次接受的字符长度
print(data.decode('utf-8'))
conn.send(data.upper())
conn.close() # 关闭网络连接
sk.close() # 关闭服务端程序
# client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080)) # 访问IP地址及其端口
sk.send('info'.encode('utf-8'))
data = sk.recv(1024)
print(data.decode('utf-8'))
sk.close() # 关闭接收端程序
# 先运行server端,再运行client端==>
server: # info
client: # INFO
TCP协议特点
# server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
conn, addr = sk.accept()
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
# client端
import socket
sk.socket.socket()
sk.send(b'hello')
# time.sleep(1) # 若是这样就不会一次性发送了
sk.send(b'hello')
# time.sleep(1) # 若是这样就不会一次性发送了
sk.send(b'hello')
# 先运行server端,再运行client端==>
server:
'''
b'hellohellohello'
b'' # 后面的recv就会接收到空字符
b''
'''
这是因为TCP协议中的流式协议
他会把数据比较小并且时间间隔短的数据一次性发送给服务端
粘包
# server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
conn, addr = sk.accept()
data = conn.recv(3)
print(data)
data = conn.recv(5)
print(data)
data = conn.recv(5)
print(data)
# client端
import socket
sk.socket.socket()
sk.send(b'hello')
sk.send(b'hello')
sk.send(b'hello')
# 先运行server端,再运行client端==>
server:
'''
b'hel' # 第一次接受只能读3位
b'lohel' # 第二次读取时会接收到上次没有接受完的数据
b'lohel'
'''
这种现象就叫粘包,数据过大,没有在规定读取大小范围内读取完
解决粘包
# server端
import socket
import subprocess
import struct
import json
"""
服务端:
要有固定的ip和port
24小时不间断提供服务
"""
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5) # 半连接池
while True:
conn, addr = server.accept() # 阻塞
while True:
try: # 异常捕获
data = conn.recv(1024).decode('utf-8') # 阻塞
if len(data) == 0: break # 针对linux和mac系统 客户端异常断开反复收空的情况
obj = subprocess.Popen(data,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
print(len(stdout + stderr))
header_dic = {
'filename': 'cls.av',
'len': len(stdout + stderr)
}
header_bytes = json.dumps(header_dic).encode('utf-8')
# 制作报头
# 将需要发送给客户端的数据打包成固定4个字节
header = struct.pack('i', len(header_bytes))
conn.send(header)
conn.send(header_bytes)
conn.send(stdout + stderr)
except ConnectionResetError:
break
conn.close()
server.close()
# client端
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8098))
while True:
msg = input('>>>:').encode('utf-8')
if len(msg) == 0: continue
client.send(msg)
header = client.recv(4)
# 对这个头进行解包,获取真实数据的长度
head_len = struct.unpack('i', header)[0]
head_dic = json.loads(client.recv(head_len).decode('utf-8'))
print(head_dic)
# 对需要接受的数据 进行循环接收
total_size = head_dic['len']
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(res.decode('gbk'))
# 先运行server端,再运行client端==>
server:
'''
b'hello'
b'hello'
b'hello'
'''
客户端每次recv得到的长度都是为了最后循环接受真实数据服务的,最后接的时候不可以接受多了,也不可以解释少了,因为TCP的流式协议,使得客户端循环接受前的recv一定要刚好接收完真是数据前面所有的值,前面的这些值得到的数据是为了保证最后循环接收真实数据时的接收准确性的