day30:TCP&UDP:socket
目录1.TCP协议和UDP协议 2.什么是socket? 3.socket正文 1.TCP基本语法 2.TCP循环发消息 3.UDP基本语法 4.UDP循环发消息 4.黏包 5.解决黏包问题 1.解决黏包方式一:先发送接下来要发送数据的大小 2.解决黏包方式二:conn.send("00000100".encode()) 3.前戏:struct模块 4.解决黏包方式三:使用struct模块 1.TCP协议和UDP协议TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话) 优点:可靠,稳定,传输完整稳定,不限制数据大小 缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认 应用:Web浏览器,电子邮件,文件传输,1)">大量数据传输的场景 UDP(User Datagram Protocol)一种无连接的,1)">不可靠的传输层通信协议(比如:发短信) 优点:速度快,可以多人同时聊天,1)">耗费资源少,不需要建立连接 缺点:不稳定,不能保证每次数据都能接收到 应用:IP电话,实时视频会议,聊天软件,1)">少量数据传输的场景 2.什么是socket?socket的意义:通络通信过程中,信息拼接的工具(中文:套接字) ? 3.socket正文1.TCP基本语法服务端 # ### 服务端 import socket 1.创建一个socket对象 sk = socket.socket() 2.绑定对应的ip和端口号(让其他主机在网络中可以找得到) """127.0.0.1代表本地ip""" sk.bind( ("127.0.0.1",9001) ) 3.开启监听 sk.listen() 4.建立三次握手 conn,addr = sk.accept() 5.处理收发数据的逻辑 recv 接受 send 发送 res = conn.recv(1024) 最多一次接受 1024 字节 print(res.decode(utf-8")) 6.四次挥手 conn.close() 7.退还端口 sk.close() 客户端 ### 客户端 2.与服务器建立连接 sk.connect( ( 3.发送数据(只能发送二进制的字节流) sk.send(北京昨天迎来了暴雨,如今有车是不行的,还得有船".encode( 4.关闭连接 sk.close() 2.TCP循环发消息服务端 ### 服务端 socket 1.创建socket对象 sk = socket.socket() # 在bind方法之前加上这句话,可以让一个端口重复使用 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 2.绑定ip和端口号(在网络中注册该主机) sk.bind( () ) sk.listen() print(conn) print(addr) conn:<socket.socket fd=4,family=AddressFamily.AF_INET,type=SocketKind.SOCK_STREAM,proto=0,laddr=('127.0.0.1',9002),raddr=('127.0.0.1',53620)> addr:('127.0.0.1',53620) """ 5.处理收发数据的逻辑 send(字节流)""" conn.send("我去北京先买船".encode("utf-8")) """ while True: 4.三次握手 conn,addr = sk.accept() True: res = conn.recv(1024) print(res.decode()) strvar = input(请输入服务端要给客户端发送的内容) conn.send(strvar.encode()) if strvar.upper() == Q: break 2.连接服务端 sk.connect( ( 3.收发数据 res = sk.recv(1024) # 一次最多接受1024个字节 print(res.decode()) True: strvar = input(请输入您要发送的内容:) sk.send(strvar.encode()) res = sk.recv(1024) if res == bq" or res == b: break (res.decode()) 4.关闭连接 sk.close() 3.UDP基本语法服务端 1.创建udp对象 sk = socket.socket(type=socket.SOCK_DGRAM) 2.绑定地址端口号 sk.bind( ( 3.udp服务器,在一开始只能够接受数据 msg,cli_addr = sk.recvfrom(1024(msg.decode()) (cli_addr) 服务端给客户端发送数据 msg = 我是你老娘,赶紧给我回家吃饭 sk.sendto(msg.encode(),cli_addr) 4.关闭连接 sk.close() 客户端 socket.SOCK_DGRAM) 2.收发数据的逻辑 发送数据 msg = 你好,你是mm还是gg" sendto( 消息,(ip,端口号) ) sk.sendto( msg.encode(),() ) 接受数据 msg,server_addr = sk.recvfrom(1024) (server_addr) 3.关闭连接 sk.close() 4.UDP循环发消息服务端 接受消息 msg,1)">(msg.decode()) message = input(服务端给客户端发送的消息是?: 发送数据 sk.sendto(message.encode(),cli_addr) 2.收发数据的逻辑 发送数据 message = input(客户端给服务端发送的消息是?:) sk.sendto(message.encode(),() ) 接受数据 msg,addr = sk.recvfrom(1024print(msg.decode()) 3.关闭连接 sk.close() 4.黏包1.出现黏包的原因tcp协议在发送数据时,会出现黏包现象. 1.数据粘包是因为在客户端/服务器端都会有一个数据缓冲区, 缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。 2.在收发数据频繁时,由于tcp传输消息的无边界,1)">不清楚应该截取多少长度 导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包 2.黏包出现的两种情况黏包现象一: 在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包 黏包现象二: 在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包 总结: 发送端,包之间时间间隔短 或者 接收端,1)">接受不及时,就会黏包 核心是因为tcp对数据无边界截取,不会按照发送的顺序判断 3.黏包的应用场景解决黏包场景: 应用场景在实时通讯时,需要阅读此次发的消息是什么 不需要解决黏包场景: 下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓. 5.解决黏包问题1.解决黏包方式一:先发送接下来要发送数据的大小服务端 time socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET,1) sk.bind( () ) sk.listen() conn,addr = sk.accept() 处理收发数据的逻辑 先发送接下来要发送数据的大小 conn.send(5".encode()) 发送5个字节 # 发完长度之后,再发数据 conn.send(hello.encode()) conn.send(.encode()) conn.close() sk.close() 客户端 ### 客户端 黏包出现的两种情况: (1) 发送端发送数据太快 (2) 接收端接收数据太慢 time sk = socket.socket() sk.connect( () ) time.sleep(2) 睡2s,让其接受速度慢一些,制造黏包效果 处理收发数据的逻辑 先接受接下来要发送数据的大小 res = sk.recv(1) res = "5" num = int(res.decode()) num = 5 接受num这么多个字节数 res1 = sk.recv(num) 一次最多只能接收5个字节 res2 = sk.recv(1024(res1) (res2) sk.close() 2.解决黏包方式二:conn.send("00000100".encode())服务端: 00000100.encode()) " * 20 conn.send(msg.encode()) conn.send(.encode()) conn.close() sk.close() 客户端: ) ) time.sleep(2 先接受接下来要发送数据的大小 res = sk.recv(8) num = int(res.decode()) 接受num这么多个字节数 res1 = sk.recv(num) res2 = sk.recv(1024(res2) sk.close() 其实,这两种写法都存在一定的限制,并非最完美的解决方案 下面介绍一个模块,用来完美的解决黏包现象 3.前戏:struct模块struct模块里有两个方法: pack :把任意长度数字转化成具有固定4个字节长度的字节流 unpack :把4个字节值恢复成原来的数字,返回最终的是元组 struct pack i => int 要转化的当前数据是整型 res1 = struct.pack(iprint(res1,len(res1)) b'xffxc9x9a;' 4 res2 = struct.pack(print(res2,len(res2)) b'x01x00x00x00' 4 res3 = struct.pack(print(res3,len(res3)) b'x7f#Cx00' 4 pack 的范围 -2147483648 ~ 2147483647 21个亿左右 res4 = struct.pack() print(res4,len(res4)) b'x00u+}' 4 unpack i => 把对应的数据转换成int整型 tup = struct.unpack(print(tup) (2100000000,) print(tup[0]) 2100000000 4.解决黏包方式三:使用struct模块服务端 struct sk = 处理收发数据的逻辑 strvar = input(请输入你要发送的数据) msg = strvar.encode() length = len(msg) 你输入字符串的长度 res = struct.pack( 无论长度是多少,res都是固定4个字节长度的字节流 print(--- 第一次发送的是字节长度 conn.send(res) 第二次发送真实的数据 conn.send(msg) 第三次发送真实的数据 conn.send(世界真美好123.encode()) conn.close() sk.close() 客户端 第一次接受的是字节长度 n = sk.recv(4) 接收到4个字节长度的字节流 tup = struct.unpack( 将4个字节长度的字节流转化成数字 n = tup[0] n就是长度 第二次接受真实的数据 res = sk.recv(n) 第三次接受真实的数据 res = sk.recv(1024(res.decode()) sk.close() struct如何做到控制接受字节数的呢? ? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |