python网络编程之socket

时间:2022-12-17 22:27:52

  一、socket是什么

  在TCP/IP五层协议中,工作在应用层的软件程序要想把它的数据发送给网络另一端的计算机并让那台计算机能把接收到的数据正常解析出来传递给对应的程序就需要按照互联网协议在数据的前面依次加上每一层的头部信息,如果这个过程让程序员自己去完成就需要去了解每一层的协议的工作原理,这无疑是非常耗费时间的极大的降低开发效率,因此前人为了避免这种情况就在应用层和下面四层之间加入了socket层。socket层对下四层做了封装并给开发人员提供了方便使用的接口,开发人员只需要遵循socket的规则去写程序,socket就会帮助我们把程序的数据加上应该有的下四层的头部信息,所以我们无需再为数据的封装操心了。

 

 二、TCP协议的socket使用方法

  服务端基本格式:

  

import socket

phone
=socket.socket() #创建socket对象,默认参数为family=AF_INET(基于网络的套接字家族), type=SOCK_STREAM(使用TCP/IP协议),可以修改
phone.bind(('127.0.0.1',8081)) #服务端绑定IP和端口,方便客户端连接
phone.listen() #监听是否有客户端试图连接

while True:
conn,addr
=phone.accept() #建立连接,返回值是一个包含连接对象以及客户端IP、端口元祖的大元祖

while True:
try:
data
=conn.recv(1024) #接收数据,bytes类型
conn.send(data.upper()) #发送数据,bytes类型
except Exception:
break
conn.close()
#关闭连接
phone.close() #关闭socket

 

  客户端基本格式:

  

import socket
phone
=socket.socket() #创建socket对象
phone.connect(('127.0.0.1',8081)) #连接服务端

while True:
msg
=input('>>:').strip()
if not msg:continue
phone.send(msg.encode(
'utf-8')) #发送数据,bytes类型
data=phone.recv(1024) #接收数据,bytes类型
print(data)

 

   三、UDP协议的socket使用方法

   服务端基本格式:

   

import socket
ip_port
=('127.0.0.1',9000)
BUFSIZE
=1024
udp_server_client
=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)

while True:
msg,addr
=udp_server_client.recvfrom(BUFSIZE) #接收的是发送端的消息以及发送端地址的元祖
print(msg,addr)

udp_server_client.sendto(msg.upper(),addr)

 

  客户端基本格式:

  

import socket
ip_port
=('127.0.0.1',9000)
BUFSIZE
=1024
udp_server_client
=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
msg
=input('>>: ').strip()
if not msg:continue

udp_server_client.sendto(msg.encode(
'utf-8'),ip_port)

back_msg,addr
=udp_server_client.recvfrom(BUFSIZE)
print(back_msg.decode('utf-8'),addr)

 

 

  四、粘包

 1、什么是粘包

  在一次发或者收的过程中多个数据包的信息粘在了一起,造成数据混乱,要注意的是粘包只有TCP传输中会发生。

  2、成因

  发送端:TCP协议在传输数据的过程中会使用一种优化算法,这种算法为了减少网络延迟对传输数据的影响提高传输效率,在遇到短时间多个小数据包进入系统缓存时会将这几个数据包封装在一起传输,这就造成了粘包。

  接收端:接收端接收一个数据包的数据时在没有接收完的情况下又有一个新的数据包进入缓存,这时就可能会产生粘包。

  3、解决方法

   1)在发送小数据包时中间间隔一个大于网络延迟的时间

    2)在发送真实数据前先告知接收端真实数据的长度

  4、利用struct模块自定义传输协议

   1、自定义报头,格式类似如下

    

head={'cmd':'put','filename':filepath,'filesize':filesize}

   2、利用json模块将报头序列化并且转码为bytes类型方便传输

   3、计算转码后的报头长度

   4、利用struct模块将报头长度信息封装为固定长度的bytes类型数据,然后发送给接收端

   

head_pack=struct.pack('i',len(head_json.encode('utf-8'))) #将数字转换为4个字节的bytes类型数据

 

   5、将转码后的报头发送给接收端

   6、发送真实数据

 

    五、socketserver

    socketserver是一个用于处理并发连接的模块,可以使用多线程(ThreadingTCPserver、ThreadingUDP server)或者多进程(ForkingTCPserver、ForkingUDPserver)来处理并发,同时内部使用selectors模块根据当前操作系统选择合适的网络I/O模型将I/O阻塞降到最低,使server端最大效率的处理连接请求。在写自己的requesthandlerclass时有三个参数需要知道一下:request获取连接对象、client_address客户端的IP和端口,同时再启动服务端之前可以设置server.allow_reuse_address来允许地址重用。

    服务端:

    

import socketserver
import struct
import json
import os
class FtpServer(socketserver.BaseRequestHandler): #定义自己的socketserver类,继承BaseRequestHandler
coding='utf-8'
server_dir
='file_upload'
max_packet_size
=1024
BASE_DIR
=os.path.dirname(os.path.abspath(__file__))
def handle(self): #重写handle方法,处理所有通信过程
print(self.request)
while True:
data
=self.request.recv(4)
data_len
=struct.unpack('i',data)[0]
head_json
=self.request.recv(data_len).decode(self.coding)
head_dic
=json.loads(head_json)
# print(head_dic)
cmd=head_dic['cmd']
if hasattr(self,cmd):
func
=getattr(self,cmd)
func(head_dic)
def put(self,args):
file_path
= os.path.normpath(os.path.join(
self.BASE_DIR,
self.server_dir,
args[
'filename']
))

filesize
= args['filesize']
recv_size
= 0
print('----->', file_path)
with open(file_path,
'wb') as f:
while recv_size < filesize:
recv_data
= self.request.recv(self.max_packet_size)
f.write(recv_data)
recv_size
+= len(recv_data)
print('recvsize:%s filesize:%s' % (recv_size, filesize))


ftpserver
=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)#实例化一个server对象并将服务端的IP和端口以及自己定义的socketserver类的类名传入
ftpserver.serve_forever()#启动socketserver并永久运行

 

  客户端:

  只需要使用socket即可