本文主要记录一下学习socket的过程
socket主要通信流程如下
socket 常用一些方法
服务端套接字函数
s.bind() 绑定(主机,端口)
s.listen() 监听
s.accept() 阻塞等待连接
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
服务端客户端公用
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.close() 关闭套接字
简单tcp服务端和客户端例子
from socket import *
addr_port=('127.0.0.1',8081)
back_log=5
buffer_size=1024
sk=socket(AF_INET,SOCK_STREAM)
sk.bind(addr_port)
sk.listen(back_log)
print('服务端运行起来了')
while True:
conn,addr=sk.accept()
while True:
#print(conn)
#print(addr)
try:
data=conn.recv(buffer_size)
except Exception as e:
break
if not data: # 客户端如果退出(指正常sk.close(),而非异常退出),服务端将收到空消息,退出
break
print('客户端发来的消息是',data.decode('utf-8'))
conn.send(data.upper())
conn.close()
sk.close()
服务端
from socket import *
ip_port=('127.0.0.1',8081)
back_log=5
buffer_size=1024
sk=socket(AF_INET,SOCK_STREAM)
sk.connect(ip_port)
while True:
msg=input('请输入需要发送信息:')
if msg=='q':
break
sk.send(msg.encode('utf-8'))
print('数据发送成功')
data=sk.recv(buffer_size)
print('收到服务端发来的消息',data.decode('utf-8'))
sk.close()
客户端
udp服务端和客户端例子
udp是无连接的
from socket import *
ip_port=('127.0.0.1',8080)
buffer_size=1024
udp_sk=socket(AF_INET,SOCK_DGRAM)
udp_sk.bind(ip_port)
while True:
data,addr=udp_sk.recvfrom(buffer_size)
print(data)
udp_sk.sendto(data.upper(), addr)
服务端
from socket import *
ip_port=('127.0.0.1',8080)
buffer_size=1024
udp_sk=socket(AF_INET,SOCK_DGRAM) #数据报
while True:
msg=input('请输入数据>>: ').strip()
udp_sk.sendto(msg.encode('utf-8'),ip_port)#udp发送数据时要指定server的ip和端口
data,addr=udp_sk.recvfrom(buffer_size)
print(data.decode('utf-8'))
客户端
粘包及解决方式
为了更好理解粘包的原理,我们首先要大致明白数据收发的原理。服务端应用程序会先把数据发送到自己的缓存中(由于tcp的一些优化算法,会将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,再通过缓存发送出去),然后经过网卡传输出去,数据到达客户端后,也会先放在客户端的缓存中,客户端从自己的缓存中取数据。
对tcp来说没有所谓的send一次,recv一次,可以sed多次,recv一次
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
两种情况下都会发生粘包
1、发送端在发送数据时,会等到缓冲区数据满了,才会向外发送,会导致粘包(发送数据时间间隔很短,数据了很小,会合到一起,就会导致不同是数据是一起发送出去的)
2、接收段接受数据的大小小于缓冲区的数据或者(即只接受了一部分数据,下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
解决粘包的方法
1、发送端在发送数据前,先发送数据的大小(类似报文封装一个头部),再发送真实数据,接收端也是先接收数据大小的数据,再循环接收真实的数据
2、使用struct模块
类似封装头部方式解决粘包
import socket
import subprocess
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.bind(addr)
sk.listen(3)
while True:
conn,addr = sk.accept()
print(addr)
while True:
try: #捕捉客户端异常关闭(ctrl+c)
data = conn.recv(1024)
except Exception:
break
if not data: #客户端如果退出,服务端将收到空消息,退出
#conn.close()
break
print('执行命令为:',str(data,'utf8'))
obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
cmd_result=obj.stdout.read()
result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小
result_len=bytes(str(result_len),'utf8')
print(result_len)
#print(cmd_result)
conn.sendall(result_len)
conn.sendall(cmd_result)
服务端
import socket
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.connect(addr)
while True:
inp = input('>>>')
if inp == 'q':
break
sk.send(bytes(inp,'utf8'))
res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收
print(res_len)
data=bytes()
while len(data)!=int(str(res_len,'utf8')):
recv=sk.recv(1024)
data+=recv
print(str(data,'gbk'))
sk.close()
客户端
使用struct
import socket
import subprocess
import struct
# sk = socket.socket()
# addr = ('127.0.0.1',9090)
# sk.bind(addr)
# sk.listen(3)
#
# while True:
# conn,addr = sk.accept()
# print(addr)
# while True:
# try: #捕捉客户端异常关闭(ctrl+c)
# data = conn.recv(1024)
# except Exception:
# break
# if not data: #客户端如果退出,服务端将收到空消息,退出
# #conn.close()
# break
# print(str(data,'utf8'))
# inp = input('>>>>')
# conn.send(bytes(inp,'utf8'))
#远程执行命令返回结果
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.bind(addr)
sk.listen(3)
while True:
conn,addr = sk.accept()
print(addr)
while True:
try: #捕捉客户端异常关闭(ctrl+c)
data = conn.recv(1024)
except Exception:
break
if not data: #客户端如果退出,服务端将收到空消息,退出
#conn.close()
break
print('执行命令为:',str(data,'utf8'))
obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
cmd_result=obj.stdout.read()
result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小
# result_len=bytes(str(result_len),'utf8')
# print(result_len)
# #print(cmd_result)
# conn.sendall(result_len)
# conn.sendall(cmd_result)
data_length = struct.pack('i', result_len)
conn.send(data_length)
conn.send(cmd_result)
服务端
import socket
import struct
from functools import partial
# sk = socket.socket()
# addr = ('127.0.0.1',9090)
# sk.connect(addr)
# while True:
# inp = input('>>>')
# if inp == 'q':
# break
# sk.send(bytes(inp,'utf8'))
# data = sk.recv(1024)
# print(str(data,'utf8'))
# sk.close()
#远程执行命令返回结果
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.connect(addr)
while True:
inp = input('>>>')
if inp == 'q':
break
sk.send(bytes(inp,'utf8'))
# res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收
# print(res_len)
# data=bytes()
# while len(data)!=int(str(res_len,'utf8')):
# recv=sk.recv(1024)
# data+=recv
# print(str(data,'gbk'))
length_data = sk.recv(4)
length = struct.unpack('i', length_data)[0]
#recv_msg = ''.join(iter(partial(sk.recv, 1024), b''))
recv_size = 0
recv_data = b''
while recv_size < length:
recv_data += sk.recv(1024)
recv_size = len(recv_data)
print('命令的执行结果是 ', recv_data.decode('gbk'))
sk.close()
客户端