一、概念
TCP(Transmission Control Protocol,传输控制协议)与UDP(User Data Protocol,用户数据协议)是互联网传输数据较为常用的协议,我们熟知的HTTP就是基于TCP的。
二、区别
1. 连接类型:TCP是面向连接的协议,要传输数据必须先进行连接,就是常说的“三次握手”,握手成功建立连接之后才能进行数据的传输交互,如同微信视频聊天需要对方接受才能彼此看到。UDP是非面向连接的协议,发送数据时不管对方状态直接发送,无需建立连接,如同微信发送一个消息或者语音信息,对面在不在线无所谓。
2. 传输开销: 由于二者特性的差异,TCP在传输数据过程中,接收方接收数据包时也会向发送方反馈数据包,因为会造成额外通信开销。UDP接收方则不会进行反馈,因此不会有这方面的额外开销。
3. 速度:TCP相较于UDP较慢,这也主要是因为TCP有一个连接的过程而UDP没有。
常见基于TCP的应用:HTTP、WebSocker、重要数据文件传输等
常见基于UDP的应用:QQ等部分实时通信软件、视频/音频下载传输等
三、tcp三次握手、四次挥手
三次握手:客户端请求服务器连接。
客户端发送一个带有 SYN =1 标志的请求,同时随机生成一个 seq 序列号
服务端收到后,发送一个确认标志 ACK =1 和确认序列号 ack = seq+1,同时发送一个 SYN =1 标志以及序列号 seq 给客户端。这时,对客户端来说,收发消息都没有问题。但是,对服务器来说,仅仅是收到了客户端的连接请求,不能确认客户端是否收到了确认回应
第三次握手,客户端发送 ACK 确认标志以及序列号 ack
四次挥手:客户端请求服务器断开连接。
客户端发送一个 FIN =1 的标志以及序列号 seq 给服务端
这个时候服务端可能还有数据没有传输结束,所以只是给客户端发送一个确认标志 ACK 和序列号
等传输完所有数据之后,服务器会发送一个 FIN =1 标志以及 seq 序列号给客户端,表示可以断开连接
客户端再发送一个确认标志 ACK = 1 以及序列号给服务端,断开连接
四、tcp的基本语法
#客户端 import socket #1、创建socket对象 sk = socket.socket() #2、与服务端建立连接 sk.connect((IP,端口号)) #3、发送数据 sk.send("发送内容") #4、关闭 sk.close #服务端 import socket #1、创建socket对象 sk = socket.socket() #2、绑定对应的ip和端口号 sk.bind((ip,端口号)) #3、开启监听 sk.listen() #4、建立连接(三次握手) conn,addr = sk.accept() #5、处理收发数据的逻辑 res = conn.recv(1024)#接收数据的大小 #6、四次挥手 conn.close() #7、退还端口 sk.close
五、udp的基本语法
#客户端 import socket #1、创建udp对象 sk = socket.socket(type=socket.SOCK_DGRAM) #2、收发数据 #发 msg = "发送数据" sk.sendto(msg.encode(),(ip,端口号)) #收 msg,server_addr = sk.recvfrom(1024)#接收文件大小 #3、关闭连接 sk.close() #服务端 import socket #1、创建udp对象 sk = socket.socket(type=socket.SOCK_DGRAM) #2、绑定地址端口号 sk.bind((ip,端口号)) #3、udp服务,在一开始只能接收数据 #收 msg,cli_addr = sk.recvfrom(1024) #发 msg = "发送数据" sk.sendto(msg.encode(),cli_addr ) #4、关闭数据 sk.close()
六、注意
#socket使用后必须退还端口,以便下次重复使用 #如果不退还端口,下次使用会报出端口占用的错误 #端口重复使用可以在bind绑定端口之前,加上一句话,保证端口重复使用 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
七、黏包
1、产生黏包的原因
因为 tcp协议发送数据是以流的形式发送,网络传输数据的速度可能会快过接收方处理数据的速度,udp是以报文形式发送
2、解决黏包
解决黏包的方式有多种,比如
1)、客户端或服务端添加sleep阻塞
2)、客户端或服务端添加边界符
3)、struct模块
3、struct模块
pack()方法将任意长度的 数字 打包成新的数据,这个新数据的长度是固定
pack()方法第一个参数是格式,第二个参数是整数(数据的长度)
unpack()方法将固定长度的 数字 解包成打包前数据真实的长度
第一个参数是格式,第二个参数是pack()方法打包后生成的新数据,返回值是一个元组,元组中放着打包前数据真实长度
如果打包的数据量非常的巨大,就会导致无法打包,可以不直接打包原始数据,打包成一个数据字典,利用json.dump序列化
4、struct基本使用
#客户端 import socket import struct #1、建立socket对象 sk = socket.socket() #2、建立连接 sk.connect((ip,端口号)) #3、处理收发数据 #第一次接收字节长度 n = sk.recv(4) tup = struct.unpack("i",n) n = tup[0] #第二次接收数据 res = sk.recv(n) #第三次接收真实数据 res = sk.recv(1024) print(res.decode()) #4、关闭 sk.close()
#服务端 import socket import struct #1、创建socket对象 sk = socket.socket() #2、绑定ip和端口号 sk = bind((ip,端口号)) #3、开启监听 sk.listen() #4、三次握手 conn,addr = sk.accept() #处理收发数据 strvar = "发送数据" msg = strvar.encode() length = len(msg) res = struct.pack("i",length) #第一次发送字节长度 conn.send(res) #第二次发送数据 conn.send(msg) #第三次发送数据 conn.send("发送大量数据".encode()) #4、四次挥手 conn.close() #5、关闭连接 sk.close()
八、更多方法
#tcp相关函数 #服务端套接字函数 bind() #绑定ip和端口号 listen() #开启tcp监听 accept() #被动接受tcp客户连接,(阻塞式)等待连接 #客户端套接字函数 connect() #主动初始化tcp服务器连接 connect_ex() #connect函数扩展版本,出错时返回错码,而不是抛出异常 #公用套接字函数 recv() #接收tcp数据 send() #发送数据,返回值是发送[字节数量],可能小于要发送的字节数 sendall() #发送数据,返回值是None,发送所有数据 #udp相关函数 recvfrom() #接收udp数据 sendto() #发送udp数据 getpeername() #连接到当前套接字的远端地址 getsockname() #当前套接字的地址 getsockopt() # 返回指定套接字的参数 setsockopt() #设置指定套接字的参数 close() #关闭套接字
标签:发送,udp,socket,tcp,sk,发送数据,服务端,客户端 From: https://www.cnblogs.com/songyunjie/p/16848654.html