Day30 socket
-
socket套接字
socket处于应用层与传输层之间,提供了一些简单的接口. 避免与操作系统之间对接,省去了相当繁琐复杂的操作.
socket在python中属于一个模块.为我们提供了这些简单的功能.
-
单个客户端与服务端通信(low版)
server
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(('127.0.0.1',8088)) phone.listen(3) conn,addr = phone.accept() from_client_data = conn.recv(1024) print(f"来自客户端的消息:{from_client_data.decode('utf-8')}") to_client_data = input('>>>') conn.send(to_client_data.encode('utf-8')) conn.close() phone.close()
client
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8088)) to_server_data = input('>>>') phone.send(to_server_data.encode('utf-8')) from_server_data = phone.recv(1024) print(f'收到来自服务端的信息:{from_server_data.decode("utf-8")}') phone.close()
-
基于TCP协议的socket单个循环通信
server:
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(('127.0.0.1',8088)) phone.listen(3) conn,addr = phone.accept() while 1: try: from_client_data = conn.recv(1024) print(f"来自客户端的消息:{from_client_data.decode('utf-8')}") to_client_data = input('>>>') conn.send(to_client_data.encode('utf-8')) except ConnectionResetError: print('客户端异常断开链接') break conn.close() phone.close()
client:
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8088)) while 1: to_server_data = input('>>>') phone.send(to_server_data.encode('utf-8')) from_server_data = phone.recv(1024) print(f'收到来自服务端的信息:{from_server_data.decode("utf-8")}') phone.close()
-
基于TCP协议的socket 链接+循环 通信
server
import socket 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('客户端正常退出聊天了') break print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') to_client_data = input('>>>').strip().encode('utf-8') conn.send(to_client_data) except ConnectionResetError: print('客户端链接中断了') break conn.close() phone.close()
client
import socket phone = 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('发送内容不能为空') continue phone.send(to_server_data) if to_server_data.upper() == b'Q': break from_server_data = phone.recv(1024) # 最多接受1024字节 print(f'来自服务端消息:{from_server_data.decode("utf-8")}') phone.close()
-
基于TCP协议的socket通信: 实例: 远程执行命令
server
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('客户端正常退出聊天了') break obj = 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('客户端链接中断了') break conn.close() phone.close() # shell: 命令解释器,相当于调用cmd 执行指定的命令。 # stdout:正确结果丢到管道中。 # stderr:错了丢到另一个管道中。 # windows操作系统的默认编码是gbk编码。
client
import socket phone = 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('发送内容不能为空') continue phone.send(to_server_data) if to_server_data.upper() == b'Q': break from_server_data = phone.recv(1024) # 最多接受1024字节 print(f'{from_server_data.decode("gbk")}') phone.close()
-
粘包现象
展示收发的问题
server
# 发多次收一次 # import socket # # phone = socket.socket() # # phone.bind(('127.0.0.1',8848)) # # phone.listen(5) # # # conn,addr = phone.accept() # 等待客户端链接我,阻塞状态中 # # from_client_data = conn.recv(1024) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # # from_client_data = conn.recv(1024) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # conn.close() # phone.close() # 发一次收多次 # import socket # # phone = socket.socket() # # phone.bind(('127.0.0.1',8848)) # # phone.listen(5) # # # conn,addr = phone.accept() # 等待客户端链接我,阻塞状态中 # # from_client_data = conn.recv(3) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # # from_client_data = conn.recv(3) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # # from_client_data = conn.recv(3) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # # from_client_data = conn.recv(3) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # # conn.close() # phone.close()
client
# 发多次收一次 # import socket # # phone = socket.socket() # # phone.connect(('127.0.0.1',8848)) # # # phone.send(b'he') # phone.send(b'llo') # # # phone.close() # Nigle算法 # 发一次收多次 # import socket # # phone = socket.socket() # # phone.connect(('127.0.0.1',8848)) # # # phone.send(b'hello world') # # # phone.close()
现象
server
# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。 # 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('客户端正常退出聊天了') # break # # obj = subprocess.Popen(from_client_data.decode('utf-8'), # shell=True, # stdout=subprocess.PIPE, # stderr=subprocess.PIPE, # # ) # result = obj.stdout.read() + obj.stderr.read() # print(f'总字节数:{len(result)}') # conn.send(result) # except ConnectionResetError: # print('客户端链接中断了') # break # conn.close() # phone.close() # s1 = '太白jx' # # print(len(s1)) # b1 = s1.encode('utf-8') # # print(b1) # print(len(b1)) ''' 客户端 服务端 第一次: ipconfig 317字节 300个字节 17个字节 客户端 服务端 第二次: dir 376字节 17字节 376字节 ''' # 2. 连续短暂的send多次(数据量很小),你的数据会统一发送出去. # import socket # # phone = socket.socket() # # phone.bind(('127.0.0.1',8848)) # # phone.listen(5) # # # conn,addr = phone.accept() # 等待客户端链接我,阻塞状态中 # # from_client_data = conn.recv(1024) # 最多接受1024字节 # print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') # conn.close() # phone.close() # 展示一些收发的问题。
client
# import socket # # phone = 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('发送内容不能为空') # continue # phone.send(to_server_data) # if to_server_data.upper() == b'Q': # break # from_server_data = phone.recv(300) # 最多接受1024字节 # # print(f'{from_server_data.decode("gbk")}') # print(len(from_server_data)) # # phone.close() # 2. 连续短暂的send多次(数据量很小),你的数据会统一发送出去. # import socket # # phone = socket.socket() # # phone.connect(('127.0.0.1',8848)) # # # phone.send(b'he') # phone.send(b'll') # phone.send(b'o') # # # phone.close() # Nigle算法
-
操作系统的缓存区
-
为什么存在缓冲区??
- 暂时存储一些数据.
- 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.
缺点: 造成了粘包现象之一.
-
-
什么情况下出现粘包
-
出现粘包的情况
连续短暂的send多次(数据量很小),你的数据会统一发送出去.
-
深入研究收发
-
-
如何解决粘包现象
解决粘包现象的思路:
服务端发一次数据 10000字节,
客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码.
-
遇到的问题: recv的次数无法确定.
你发送总具体数据之前,先给我发一个总数据的长度:5000个字节。然后在发送总数据。
客户端: 先接收一个长度。 5000个字节。
然后我再循环recv 控制循环的条件就是只要你接受的数据< 5000 一直接收。
-
遇到的问题: 总数据的长度转化成的字节数不固定
服务端: conn.send(total_size) conn.send(result) total_size int类型 客户端: total_size_bytes = phone.recv(4) total_size data = b'' while len(data) < total_size: data = data + phone.recv(1024)
你要将total_size int类型转化成bytes类型才可以发送
387 ---- > str(387) '387' ---->bytes b'387' 长度 3bytes
4185 ----> str(4185) '4185' ---->bytes b'4185' 长度 4bytes
18000------------------------------------------------------> 长度 5bytes
我们要解决:
将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来。
struct模块
-
-
low版解决粘包现象
server
# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。 import socket import subprocess import struct 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) # 接收命令 if from_client_data.upper() == b'Q': print('客户端正常退出聊天了') break obj = subprocess.Popen(from_client_data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) result = obj.stdout.read() + obj.stderr.read() total_size = len(result) print(f'总字节数:{total_size}') # 1. 制作固定长度的报头 head_bytes = struct.pack('i',total_size) # 2. 发送固定长度的报头 conn.send(head_bytes) # 3. 发送总数据 conn.send(result) except ConnectionResetError: print('客户端链接中断了') break conn.close() phone.close() # import struct
client
import socket import struct phone = 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('发送内容不能为空') continue phone.send(to_server_data) if to_server_data.upper() == b'Q': break # 1. 接收报头 head_bytes = phone.recv(4) # 2. 反解报头 total_size = struct.unpack('i',head_bytes)[0] total_data = b'' while len(total_data) < total_size: total_data += phone.recv(1024) print(len(total_data)) print(total_data.decode('gbk')) phone.close()