一、预备知识
发包:
C请求,S同意后并我也要挖隧道,C才可以挖隧道到S。(三次握手)
结束发包:
C请求,S确认,S请求,C确认(四次挥手)
UDP协议:传输不可靠,但不需要建管道,直接按IP发过去
总结:①TCP传输可靠,但效率低
②UDP传输不可靠,但效率高
2.2 TCP
2.2.1 服务端
由上图可知,服务端需要先建立SOCKET链接,首先需要导入socket模块,并链接。
1 import socket2 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
之后就需要绑定(主机,端口号)到套接字,开始监听。其中绑定时,IP号和端口号是元组,并且端口号是0-65535,但其中0-1024是给操作系统的,使用需要管理员权限。监听,其中5代表最大链接数量。
s.bind(('127.0.0.1',8080))#0-65535:0-1024给操作系统使用s.listen(5)
紧接着,服务器通过一个永久循环来接收来自客户端的连接,accept()会一直等待,知道客户端发来信息(暂只考虑单线程情况)。
1 whileTrue:#链接循环2 conn,client_addr=s.accept()
接下来就是收发消息了,并需要进行通信循环。
1 #收发消息2 whileTrue:#通信循环3 try:4 data=conn.recv(1024) #1024表示接收数据的最大数,单位是bytes5 print('客户端的数据',data)6 conn.send(data.upper())7 except ConnectionResetError:8 break9 conn.close()
接下来就是关闭套接字。
1 s.close()
2.2.2 客户端
首先和服务端一样,需要先建立SOCKET链接,首先需要导入socket模块,并链接。
1 import socket2 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
之后通过(主机IP号,端口号)到套接字连接。
1 s.connect(('127.0.0.1',8080))
之后发收消息,同样有着通信循环,和服务端相比,由于没有等待连接,因此少个链接循环。
1 #发收消息2 whileTrue:#通信循环3 msg=input('>>').strip()4 phone.send(msg.encode('utf-8'))5 data=phone.recv(1024)6 print(data.decode('utf-8'))
接下来就是关闭套接字。
1 s.close()
2.3 UDP协议
相比TCP协议,UDP是面向无连接的协议,因此使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以发送数据包,其不管是否发送到达。
和TCP协议类似,也是服务端和客户端。
2.3.1 服务端
服务端需要先建立SOCKET链接,首先需要导入socket模块,并绑定端口。
1 import socket2 server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)3 server.bind(('127.0.0.1',8080))
其不需要监听和连接,即不需要listen()和accept(),而是直接接收来自客户端的数据。
1 whileTrue:2 data,cliend_addr=server.recvfrom(1024)3 print(data)4 server.sendto(data.upper(),cliend_addr)
最后关闭套接字。
1 server.close()
2.3.2 客户端
同样,也需要先建立SOCKET链接,首先需要导入socket模块。
1 import socket2 client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
但不需要调用connect(),直接通过sendto()给服务端发数据。
1 whileTrue:2 msg=input('>>:').strip()3 data=client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))4 data,server_addr=client.recvfrom(1024)5 print(data,server_addr)
最后关闭套接字。
1 server.close()
2.4 粘包现象及解决方案
2.4.1 粘包现象
何为粘包,在上文中,我们一直使用s.recv(1024)来接收数据,但如果需要接收的数据比1024长,那么剩余的数据会在发送端的IO缓冲区暂存下来,等下次接收端来接收数据时,先将缓冲区的数据发送出去,再接收下次的数据。当然,我们可以将1024改为8192,但数据比这个还大呢,我们接收的额定值就不能变大了,还是会发生这样的事件。因此,这样的事件我们称之为粘包现象。当然,粘包现象仅存在于TCP协议中,UDP协议中不存在。
2.4.2 解决方案
粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。此处,我们就需要借助于第三方模块struct。用法为:
1import json,struct2#为避免粘包,必须制作固定长度的报头3header_dic={'file_size':1073741824,'file_name':'a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'}#1G文件大小,文件名和md5值45#为了该报头能传送,需要序列化并且转为bytes,用于传输6header_json = json.dumps(header_dic)# 转成字符串类型7header_bytes = header_json.encode('utf-8')89#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节10head_len_bytes=struct.pack('i',len(head_bytes))#这4个字节里只包含了一个数字,该数字是报头的长度1112#客户端开始发送报文长度13conn.send(head_len_bytes)#先发报头的长度,4个bytes14#再发报头的字节格式15conn.send(head_bytes)16#然后发真实内容的字节格式17conn.sendall(文件内容)1819#服务端开始接收20head_len_bytes=s.recv(4)#先收报头4个bytes,得到报头长度的字节格式21x=struct.unpack('i',head_len_bytes)[0]#提取报头的长度2223header_bytes=s.recv(x)#按照报头长度x,收取报头的bytes格式24header_str=header_bytes.decode('utf-8')25header_dic=json.loads(header_str)#提取报头2627#最后根据报头的内容提取真实的数据,比如数据的长度28real_data_len=s.recv(header_dic['file_size'])29s.recv(real_data_len)
因此对于一个文件传输:
服务端:
1importsocket2importos3importstruct4importjson5share_dir=r'C:\Users\。。。\Desktop\python\oldboypython\day6\10文件传输\服务端\share'67phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)8# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)9phone.bind(('127.0.0.1',9901))#0-65535:0-1024给操作系统使用10phone.listen(5)11print('starting...')12whileTrue:# 链接循环13conn,client_addr=phone.accept()14print(client_addr)15whileTrue:#通信循环16try:17#1、收命令18res=conn.recv(8096)#b'get a.txt'19ifnotres:break#适用于linux操作系统20#2、解析命令,提取相应的命令参数21cmds=res.decode('utf-8').split()#['get','a.txt']22filename=cmds[1]2324#3、以读的方式打开文件,读取文件内容发送给客户端25#3.1 制作固定长度的报头26header_dic={27'filename':filename,28'md5':'xxdxxx',29'file_size':os.path.getsize('%s/%s'%(share_dir,filename))30}31header_json=json.dumps(header_dic)#转成字符串类型32header_bytes=header_json.encode('utf-8')3334#3.2 先发送报头的长度35conn.send(struct.pack('i',len(header_bytes)))3637#3.3 再发报头38conn.send(header_bytes)3940#3.4 发真实的数据41# conn.send(stdout+stderr) #+是一个可以优化的点42withopen('%s/%s'%(share_dir,filename),'rb')asf:43# conn.send(f.read())44forlineinf:45conn.send(line)46exceptConnectionResetError:#适用于windows操作系统47break48conn.close()4950phone.close()文件传输服务端
客户端:
1importsocket2importstruct3importjson45download_dir=r'C:\Users\。。。\Desktop\python\oldboypython\day6\10文件传输\客户端\download'6phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)7phone.connect(('127.0.0.1',9901))8whileTrue:9#1、发命令10cmd=input('>>: ').strip()#get a.txt11ifnotcmd:continue12phone.send(cmd.encode('utf-8'))13#2、接收文件的内容,以写的方式打开新文件,接收服务端发来的文件的内容写入客户端的新文件14#2.1 先收报头的长度15obj=phone.recv(4)16header_size=struct.unpack('i',obj)[0]17#2.2 在收报头18header_bytes=phone.recv(header_size)19#2.3 从包头中解析出对真实数据的描述的信息20header_json=header_bytes.decode('utf-8')21header_dic=json.loads(header_json)22print(header_dic)23total_size=header_dic['file_size']24file_name=header_dic['filename']25#2.4 接收数据26withopen('%s/%s'%(download_dir,file_name),'wb')asf:27recv_size=028whilerecv_size
欢迎关注我的千人交流学习答疑群:125240963
网友评论