1.四次挥手(补充)
客户端向服务端发送一个请求消息,断开连接(代表客户端没有数据传输了)
服务端接收请求,发出响应
等到服务端所有数据收发完毕之后
服务端向客户端发送断开连接的请求
客户端接收请求后,发出响应
等到2msl,最大报文生存时间之后
客户端与服务端彻底断开连接
2.socket
socket的意义:通络通信过程中,信息拼接的工具(中文:套接字) # 开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉) # 在bind方法之前加上这句话,可以让一个端口重复使用 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
3.tcp基本语法
####客户端 import socket #1.创建一个socket对象 sk = socket.socket() #2.创建连接 sk.connect(('127.0.0.1',9000)) #3.发送数据(二进制字节流) sk.send("今天天气真好".encode('utf-8')) #4.关闭连接 sk.close()
####服务端 ''' 客户端和服务端在收发数据时 一发一收是一对,否则会导致数据异常 send 发送 recv接收 ''' import socket #1.创建socket对象 sk = socket.socket() #2.绑定对应的ip和端口(注册网络,让其他主机能够找到) '''127.0.0.1 代表本地ip''' sk.bind(('127.0.0.1',9000)) #3.开启监听 sk.listen() #4.创建三次握手 conn,addr = sk.accept() print(conn,addr) """ conn = <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 61176)> addr = ('127.0.0.1', 61176) """ #5.收发数据(recv里面的参数单位是字节,代表一次最多接受多少数据) res = conn.recv(1024) print(res.decode('utf-8')) #6.四次挥手 conn.close() #7.退还端口 sk.close()
4.tcp循环发消息
####客户端 # ### tcp客户端 import socket #1.创建socket对象 sk = socket.socket() #2.创建连接 sk.connect(('127.0.0.1',9000)) #3.发送数据 ''' res = sk.recv(1024) #一次接收最大字节1024 print(res) ''' while True: strvar = input('请输入内容') sk.send(strvar.encode('utf-8')) res = sk.recv(1024) if res == b'q': break print(res.decode('utf-8')) #4.关闭连接 sk.close()
####服务端 # ### 服务端 import socket #1.创建socket对象 sk = socket.socket() #2.注册主机绑定ip及端口 sk.bind(('127.0.0.1',9000)) #3.监听 sk.listen() #4.三次握手 # conn,addr = sk.accept() #5.接发收数据 ''' 数据类型:二进制的字节流 b修饰的字符串 => 代表的是二进制的字节流 里面的字符必须是ascii编码中的字符,不能是中文,否则报错 ''' while True: conn,addr = sk.accept() while True: res = conn.recv(1024) print(res.decode('utf-8')) strvar = input('请输入内容') conn.send(strvar.encode('utf-8')) #退出 if strvar == 'q': break #6.四次挥手 conn.close() #7.退还端口 sk.close()
5.udp基本语法
#### 客户端 import socket #type = socket.SOCK_DGRAM => 返回udp协议对象 #1.创建udp对象 sk = socket.socket(type=socket.SOCK_DGRAM) #2.发送数据 msg = '大妹纸,你好啊' #sendto (二进制字节流,(ip,端口)) sk.sendto(msg.encode('utf-8'),('127.0.0.1',9000)) #客户端接收服务端发过来的数据 msg,ser_addr = sk.recvfrom(1024) print(msg.decode()) print(ser_addr) #3.关闭连接 sk.close()
####服务端 import socket #type = socket.SOCK_DGRAM => 返回udp协议对象 #1.创建对象 sk = socket.socket(type=socket.SOCK_DGRAM) #2.绑定地址端口号 sk.bind(('127.0.0.1',9000)) #3.接收消息(udp作为服务端的时候,第一次一定是 接收消息) msg,cli_addr = sk.recvfrom(1024) print(msg.decode()) print(cli_addr) # ('127.0.0.1', 56184) #服务端给客户端发消息 msg = '我是老爷们,我不好' sk.sendto(msg.encode(),cli_addr) #.关闭连接 sk.close()
6.黏包
# tcp协议在发送数据时,会出现黏包现象. (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区, 缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。 (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度 导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
7.黏包出现的两种情况
#黏包现象一: 在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包 #黏包现象二: 在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包 #总结: 发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包 核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
8.黏包对比:tcp和udp
#tcp协议: 缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包 优点:不限制数据包的大小,稳定传输不丢包 #udp协议: 优点:接收时候数据之间有边界,传输速度快,不黏包 缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包 #tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送 但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止 而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包
9.解决黏包的几种办法
# ### 客户端 # ### 针对于tcp协议,会出现黏包现象 import socket import time #1.创建对象 sk = socket.socket() #2.创建连接 sk.connect(('127.0.0.1',9000)) time.sleep(0.1) #处理收发数据逻辑 ''' res1 = sk.recv(1024) res2 = sk.recv(1024) print(res1) print(res2) ''' #先接收数据的总大小 res = sk.recv(1) num = int(res.decode('utf-8')) #接收num这么多的字节数 res1 = sk.recv(num) #第一次 res2 = sk.recv(1024) #第二次 print(res1) print(res2) #4.关闭连接 sk.close()
####服务端 import socket #1.创建对象 sk = socket.socket() # 把这句话写在bind之前,让一个端口绑定多个程序,可以重复使用(仅用在测试环节) #sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #2.注册主机绑定ip和端口 sk.bind(('127.0.0.1',9000)) #3.监听 sk.listen() #4.三次握手 conn,addr = sk.accept() #在发送真实数据之前,提前告诉接收端数据的大小 conn.send('5'.encode()) #处理收发数据逻辑 conn.send('hello'.encode()) #第一次发送 #time.sleep(0.1) conn.send(',world'.encode()) #第二次发送 #6.四次挥手 conn.close() #7.退还端口 sk.close()
####客户端 # ### 针对于tcp协议,会出现黏包现象 import socket import time #1.创建对象 sk = socket.socket() #2.创建连接 sk.connect(('127.0.0.1',9000)) time.sleep(0.1) # 处理收发数据逻辑 ''' 黏包的现象 res1 = sk.recv(1024) res2 = sk.recv(1024) print(res1) print(res2) ''' #先接收数据的总大小 res = sk.recv(8) num = int(res.decode('utf-8')) #接收num这么多的字节数 res1 = sk.recv(num) res2 = sk.recv(1024) print(res1) print(res2) #关闭连接 sk.close()
# ### 服务端 import socket #1.创建对象 sk = socket.socket() #2.注册主机绑定ip和端口 sk.bind(('127.0.0.1',9000)) #3.监听 sk.listen #4.三次握手 conn,addr = sk.accept() #5.接发收数据处理逻辑 conn.send('00000100'.encode()) #8字节 #处理收发数据逻辑 msg = 'hello' * 20 conn.send(msg.encode()) #time.sleep(0.1) conn.send(',world'.encode()) #6.四次挥手 conn.close() #7.退还端口 sk.close()
# ### 客户端 用struct解决 # ### 针对于tcp协议,会出现黏包现象 import socket import time import struct #1.创建对象 sk = socket.socket() #2.创建连接 sk.connect(('127.0.0.1',9000)) #处理收发数据逻辑 #接受第一次发送过来的数据(数据的大小) n = sk.recv(4) #都会压缩成4个字节来接收 tup = struct.unpack("i",n) #unpack解包,'i'表示整型int,返回元组 print(tup) #(24,) n = tup[0] print(n) #第二次接收的真实的数据 res1 = sk.recv(n) print(res1.decode()) #第三次接收的真实的数据 res2 = sk.recv(1024) print(res2.decode()) #关闭连接 sk.close()
# ### 服务端 import socket import struct #1.创建对象 sk = socket.socket() #2.注册主机绑定ip和端口 sk.bind(('127.0.0.1',9000)) #3.监听 sk.listen() #4三次握手 conn,addr = sk.accept() #5.处理收发数据逻辑 strvar = input('请输入') msg = strvar.encode() length = len(msg) #第一次把长度先发送过去 res = struct.pack('i',length) conn.send(res) #第二次在发送真实数据 conn.send(msg) #第三次在发送一个数据 conn.send(',world'.encode()) #6.四次挥手 conn.close() #7.退还端口 sk.close()
10.struct用法(解决黏包)
import struct ''' pack: 把任意长度的数字转化成具有4个字节的固定长度的字节流 unpack: 把4个字节值恢复成原本的数字,返回是元组 ''' #i => int 要转化的当前数据是整型 '''pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值''' res = struct.pack('i',999999999) res = struct.pack("i",1234343433) res = struct.pack("i",2100000011) print(res) print(len(res)) #i => 把对应的数据转换成int,最后返回元组 tup = struct.unpack('i',res) print(tup[0]) ## (2100000011,) ''' 解决黏包场景: 应用场景在实时通讯时,需要阅读此次发的消息是什么 不需要黏包解决场景 下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓 '''