基于TCP的简单socket套接字模型
简单套接字的实现
#my_server.py
import socket#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、绑定手机卡
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
#3、开机
phone.listen(5)
#4、等电话连接
print('starting...')
conn,addr=phone.accept()
print('IP:%s,PORT:%s' %(addr[0],addr[1]))
#5、收发消息
data=conn.recv(1024) #最大收1024
conn.send(data.upper())
#6、挂电话
conn.close()
#7、关机
phone.close()
#my_client.py
import socket#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8080))
#3、发收消息
phone.send('hello'.encode('utf-8'))
data=phone.recv(1024)
print(data.decode('utf-8'))
#4、挂电话
phone.close()
加上循环
#加上循环的my_server.py
import socket#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、绑定手机卡
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8082))
#3、开机
phone.listen(5)
#4、等电话连接
print('starting...')
while True: #连接循环
conn,addr=phone.accept()
print('IP:%s,PORT:%s' %(addr[0],addr[1]))
#5、收发消息
while True: #通信循环
try:
data=conn.recv(1024) #最大收1024
print(data)
if not data:break #针对linux
conn.send(data.upper())
except Exception:
break
#6、挂电话
conn.close()
#7、关机
phone.close()
import socket加循环的my_client.py
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8082))
#3、发收消息
while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
print('has send===>')
data=phone.recv(1024)
print('has recv')
print(data.decode('utf-8'))
#4、挂电话
phone.close()
粘包现象
from socket import *粘包现象my_server.py
import time
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8888))
server.listen(5)
conn,addr=server.accept()
data1=conn.recv(2)
print(data1)
time.sleep(5)
data2=conn.recv(1024)
print(data2)
from socket import *粘包现象my_client.py
import time
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8888))
client.send('helloworld'.encode('utf-8'))
time.sleep(3)
client.send('alexdsb'.encode('utf-8'))
只有TCP有粘包现象,UDP永远不会粘包(最后会介绍基于UDP的socket套接字)
粘包出现的原因:
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
两种情况下会发生粘包:
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)。——原因出在客户端,连续不间隔的发送了很小的数据。不举例了。
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)。——上边的例子就是。
解决粘包问题
import socket服务端接受cmd指令,并简单解决粘包问题。my_server.py
import subprocess
import struct
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、绑定手机卡
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #再次使用原端口启动服务端,不好用
phone.bind(('127.0.0.1',8091))
#3、开机
phone.listen(5)
#4、等电话连接
print('starting...')
while True: #连接循环
conn,addr=phone.accept()
print('IP:%s,PORT:%s' %(addr[0],addr[1]))
#5、收发消息
while True: #通信循环
try:
cmd=conn.recv(1024) #最大收1024
if not cmd:break #针对linux
#执行命令
obj=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout=obj.stdout.read()
stderr=obj.stderr.read()
#发送数据的描述信息:长度
header=struct.pack('i',len(stdout)+len(stderr))
conn.send(header)
#发送真实数据
conn.send(stdout)
conn.send(stderr)
except Exception:
break
#6、挂电话
conn.close()
#7、关机
phone.close()
import socket客户端呼应服务端解决简单粘包问题。my_client.py
import struct
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8091))
#3、发收消息
while True:
cmd=input('>>: ').strip()
if cmd == 'quit':break
if not cmd:continue
phone.send(cmd.encode('utf-8'))
#先收报头
header=phone.recv(4)
total_size=struct.unpack('i',header)[0]
#循环收:1024
total_data=b''
recv_size=0
while recv_size < total_size:
recv_data=phone.recv(1024)
total_data+=recv_data
recv_size+=len(recv_data)
print(total_data.decode('gbk'))
#4、挂电话
phone.close()
终极解决粘包问题
import socket服务端先发送报头长度,再发送包头,最后发送数据。my_server.py
import subprocess
import struct
import json
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、绑定手机卡
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8092))
#3、开机
phone.listen(5)
#4、等电话连接
print('starting...')
while True: #连接循环
conn,addr=phone.accept()
print('IP:%s,PORT:%s' %(addr[0],addr[1]))
#5、收发消息
while True: #通信循环
try:
cmd=conn.recv(1024) #最大收1024
if not cmd:break #针对linux
#执行命令
obj=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout=obj.stdout.read()
stderr=obj.stderr.read()
#制作报头
header_dic = {'filename': 'a.txt',
'total_size': len(stdout)+len(stderr),
'md5': 'asdfa123xvc123'}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
#先发报头的长度
conn.send(struct.pack('i',len(header_bytes)))
#再发送报头
conn.send(header_bytes)
#最后发送真实数据
conn.send(stdout)
conn.send(stderr)
except Exception:
break
#6、挂电话
conn.close()
#7、关机
phone.close()
import socket开客户端先接收报头长度,再接收报头,最后接收数据,完美解决粘包问题。my_client.py
import struct
import json
#1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2、打电话
phone.connect(('127.0.0.1',8092))
#3、发收消息
while True:
cmd=input('>>: ').strip()
if cmd == 'quit':break
if not cmd:continue
phone.send(cmd.encode('utf-8'))
#先收报头长度
obj=phone.recv(4)
header_size=struct.unpack('i',obj)[0]
#再收报头
header_bytes=phone.recv(header_size)
header_json=header_bytes.decode('utf-8')
header_dic=json.loads(header_json)
print(header_dic)
#最后循环收真实的数据
total_size=header_dic['total_size']
filename=header_dic['filename']
total_data=b''
recv_size=0
with open(filename,'wb') as f:
while recv_size < total_size:
recv_data=phone.recv(1024)
total_data+=recv_data
recv_size+=len(recv_data)
print(total_data.decode('gbk'))
#4、挂电话
phone.close()
作业
写一个基于socket套接字的下载,服务端向客户端发起下载请求,服务端发送数据给客户端。
from socket import *作业答案my_server.py
import os
import json
import struct
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8091))
server.listen(5)
def get(filepath,conn):
#制作报头
header_dic={
'filename':os.path.basename(filepath),# C:\\\\1.png
'size':os.path.getsize(filepath),
'md5':'xxxxxxxxxx'
}
header_json=json.dumps(header_dic)
header_bytes=header_json.encode('utf-8')
#先发送报头的长度
conn.send(struct.pack('i',len(header_bytes)))
#再发送报头
conn.send(header_bytes)
#最后发送真实的数据
with open(filepath,'rb') as f:
for line in f:
conn.send(line)
print('===>')
while True:
conn,addr=server.accept()
while True:
try:
data=conn.recv(1024)
cmd,filepath=data.decode('utf-8').split() #get C:\\\\1.png
if cmd == 'get':
get(filepath,conn)
if not data:break
except Exception as e:
print(e)
break
conn.close()
server.close()
from socket import *作业答案my_client.py
import struct
import json
download_dir=r'C:\Users\Administrator\PycharmProjects\python全栈7期\day31\home'
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8091))
while True:
cmd=input('>>: ').strip() #get a.txt
if not cmd:continue
client.send(cmd.encode('utf-8'))
#先收报头长度
obj=client.recv(4)
header_size=struct.unpack('i',obj)[0]
#再收报头
header_bytes=client.recv(header_size)
header_json=header_bytes.decode('utf-8')
header_dic=json.loads(header_json)
filename=header_dic['filename']
abs_path=r'%s\%s' %(download_dir,filename)
size=header_dic['size']
print(header_dic)
#最后收真实数据
recv_size=0
with open(abs_path,'wb') as f:
while recv_size < size:
line=client.recv(1024)
f.write(line)
recv_size+=len(line)
client.close()
上面给的答案没有加上MD5校验,无法确保下载的是否跟服务端发送的一致,下面给出加了MD5校验的答案。
from socket import *作业答案+MD5校验my_server.py
import os
import json
import struct
import hashlib
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
def get(filepath,conn):
#制作报头
header_dic={
'filename':os.path.basename(filepath),# C:\\\\1.png
'size':os.path.getsize(filepath),
}
header_json=json.dumps(header_dic)
header_bytes=header_json.encode('utf-8')
#先发送报头的长度
conn.send(struct.pack('i',len(header_bytes)))
#再发送报头
conn.send(header_bytes)
#再发送真实的数据
with open(filepath,'rb') as f:
m=hashlib.md5()
for line in f:
conn.send(line)
m.update(line)
#最后发送md5值
md5=m.hexdigest()
print(md5)
conn.send(md5.encode('utf-8'))
while True:
conn,addr=server.accept()
while True:
try:
data=conn.recv(1024)
cmd,filepath=data.decode('utf-8').split() #get C:\\\\1.png
if cmd == 'get':
get(filepath,conn)
if not data:break
except Exception as e:
print(e)
break
conn.close()
server.close()
from socket import *作业答案+MD5校验my_cliet.py
import struct
import json
import hashlib
download_dir=r'D:\\'
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8000))
while True:
cmd=input('>>: ').strip() #get a.txt
if not cmd:continue
client.send(cmd.encode('utf-8'))
#先收报头长度
obj=client.recv(4)
header_size=struct.unpack('i',obj)[0]
#再收报头
header_bytes=client.recv(header_size)
header_json=header_bytes.decode('utf-8')
header_dic=json.loads(header_json)
filename=header_dic['filename']
abs_path=r'%s\%s' %(download_dir,filename)
size=header_dic['size']
print(header_dic)
#再收真实数据
recv_size=0
with open(abs_path,'wb') as f:
m=hashlib.md5()
while recv_size < size:
line=client.recv(1024)
f.write(line)
m.update(line)
recv_size+=len(line)
client_md5=m.hexdigest()
#最后收md5值
server_md5=client.recv(1024).decode('utf-8')
if client_md5 != server_md5:
os.remove(abs_path)
print('文件已损坏,请重写下载')
client.close()
基于UDP的简单socket套接字模型
udp是无链接的,先启动哪一端都不会报错
- TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- TCP是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而UDP是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
- udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
- tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
from socket import *UDP服务没有粘包my_server.py
server=socket(AF_INET,SOCK_DGRAM) #数据报协议
server.bind(('127.0.0.1',8083))
data1,client_addr=server.recvfrom(1)
data2,client_addr=server.recvfrom(1)
print(data1) #b'h'
print(data2) #b'w'
server.close()
from socket import *UDP服务没有粘包my_client.py
client=socket(AF_INET,SOCK_DGRAM)
client.sendto(b'hello',('127.0.0.1',8083))
client.sendto(b'world',('127.0.0.1',8083))