socket补充:通信循环、链接循环、远程操作及黏包现象
socket通信循环
server端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)conn, client_addr = phone.accept()
print(conn, client_addr, sep='\n')while 1: # 循环收发消息try:from_client_data = conn.recv(1024)print(from_client_data.decode('utf-8'))conn.send(from_client_data + b'SB')except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号while 1: # 循环收发消息client_data = input('>>>')phone.send(client_data.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('utf-8'))phone.close() # 挂电话
socket通信链接循环
server端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)while 1 : # 循环连接客户端conn, client_addr = phone.accept()print(client_addr)while 1:try:from_client_data = conn.recv(1024)print(from_client_data.decode('utf-8'))conn.send(from_client_data + b'SB')except ConnectionResetError:breakconn.close()
phone.close()
服务端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号while 1:client_data = input('>>>')phone.send(client_data.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('utf-8'))phone.close() # 挂电话
socket远程操作
import subprocessobj = subprocess.Popen('dir1',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)print(obj.stdout.read().decode('gbk')) # 正确命令
print(obj.stderr.read().decode('gbk')) # 错误命令
服务端:
import socket
import subprocess
phone = socket.socket()phone.bind(('127.0.0.1',8848))phone.listen(2)
# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错while 1:conn,addr = phone.accept() # 等待客户端链接我,阻塞状态中print(f'链接来了: {conn,addr}')while 1:try:from_client_data = conn.recv(1024) # 最多接受1024字节if from_client_data.upper() == b'Q':print('客户端正常退出聊天了')breakobj = subprocess.Popen(from_client_data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)result = obj.stdout.read() + obj.stderr.read()conn.send(result)except ConnectionResetError:print('客户端链接中断了')breakconn.close()
phone.close()# shell: 命令解释器,相当于调用cmd 执行指定的命令。
# stdout:正确结果丢到管道中。
# stderr:错了丢到另一个管道中。
# windows操作系统的默认编码是gbk编码。
客户端:
import socketphone = socket.socket()phone.connect(('127.0.0.1',8848))
while 1:to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')if not to_server_data:# 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送print('发送内容不能为空')continuephone.send(to_server_data)if to_server_data.upper() == b'Q':breakfrom_server_data = phone.recv(1024) # 最多接受1024字节print(f'{from_server_data.decode("gbk")}')phone.close()
什么叫做黏包现象?为什么会出现黏包现象?
socket收发消息的原理
应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。
而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?
可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
设置缓冲区的两个好处:
- 暂时存储一些数据.
- 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
只有TCP有粘包现象,UDP永远不会粘包
黏包的两种情况:
1,接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
server端:
import socket
import subprocessphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)while 1: # 循环连接客户端conn, client_addr = phone.accept()print(client_addr)while 1:try:cmd = conn.recv(1024)ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)correct_msg = ret.stdout.read()error_msg = ret.stderr.read()conn.send(correct_msg + error_msg)except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号while 1:cmd = input('>>>')phone.send(cmd.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('gbk'))phone.close()
2、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)
server端:
import socketphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)conn, client_addr = phone.accept()frist_data = conn.recv(1024)
print('1:',frist_data.decode('utf-8')) # 1: helloworld
second_data = conn.recv(1024)
print('2:',second_data.decode('utf-8'))conn.close()
phone.close()
客户端:
import socketphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) phone.send(b'hello')
phone.send(b'world')phone.close() # 两次返送信息时间间隔太短,数据小,造成服务端一次收取
如何解决黏包现象?
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))# 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))# 但是通过struct 处理不能处理太大ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret)) # 报错
方案一:low版。
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再来一个死循环接收完所有数据。
server端:
import socket
import subprocess
import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)while 1:conn, client_addr = phone.accept()print(client_addr)while 1:try:cmd = conn.recv(1024)ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)correct_msg = ret.stdout.read()error_msg = ret.stderr.read()# 1 制作固定报头total_size = len(correct_msg) + len(error_msg)header = struct.pack('i', total_size)# 2 发送报头conn.send(header)# 发送真实数据:conn.send(correct_msg)conn.send(error_msg)except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socket
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.connect(('127.0.0.1',8080))while 1:cmd = input('>>>').strip()if not cmd: continuephone.send(cmd.encode('utf-8'))# 1,接收固定报头header = phone.recv(4)# 2,解析报头total_size = struct.unpack('i', header)[0]# 3,根据报头信息,接收真实数据recv_size = 0res = b''while recv_size < total_size:recv_data = phone.recv(1024)res += recv_datarecv_size += len(recv_data)print(res.decode('gbk'))phone.close()