网络编程之套接字

时间:2022-12-18 00:24:03

基于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
#
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()
加循环的my_client.py

 粘包现象

网络编程之套接字网络编程之套接字
from socket import *
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)
粘包现象my_server.py
网络编程之套接字网络编程之套接字
from socket import *
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'))
粘包现象my_client.py

只有TCP有粘包现象,UDP永远不会粘包(最后会介绍基于UDP的socket套接字)

粘包出现的原因:
  所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

  两种情况下会发生粘包:

    发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)。——原因出在客户端,连续不间隔的发送了很小的数据。不举例了。      
    接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)。——上边的例子就是。

解决粘包问题

网络编程之套接字网络编程之套接字
import socket
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()
服务端接受cmd指令,并简单解决粘包问题。my_server.py
网络编程之套接字网络编程之套接字
import socket
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()
客户端呼应服务端解决简单粘包问题。my_client.py

 

终极解决粘包问题

网络编程之套接字网络编程之套接字
import socket
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()
服务端先发送报头长度,再发送包头,最后发送数据。my_server.py
网络编程之套接字网络编程之套接字
import socket
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()
开客户端先接收报头长度,再接收报头,最后接收数据,完美解决粘包问题。my_client.py

 作业

写一个基于socket套接字的下载,服务端向客户端发起下载请求,服务端发送数据给客户端。

网络编程之套接字网络编程之套接字
from socket import *
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()
作业答案my_server.py
网络编程之套接字网络编程之套接字
from socket import *
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()
作业答案my_client.py

上面给的答案没有加上MD5校验,无法确保下载的是否跟服务端发送的一致,下面给出加了MD5校验的答案。

网络编程之套接字网络编程之套接字
from socket import *
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()
作业答案+MD5校验my_server.py
网络编程之套接字网络编程之套接字
from socket import *
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()
作业答案+MD5校验my_cliet.py

 基于UDP的简单socket套接字模型

udp是无链接的,先启动哪一端都不会报错

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. TCP是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而UDP是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
  • udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
  • tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
网络编程之套接字网络编程之套接字
from socket import *
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()
UDP服务没有粘包my_server.py
网络编程之套接字网络编程之套接字
from socket import *
client
=socket(AF_INET,SOCK_DGRAM)
client.sendto(b
'hello',('127.0.0.1',8083))
client.sendto(b
'world',('127.0.0.1',8083))
UDP服务没有粘包my_client.py