目录
1,udp
2,tcp
3,socket 函数
4, 粘包问题
标签(空格分隔): 网络编程
目录
tcp 和 udp
1,udp
tcp,udp 是第四层传输层拥有的协议, 用于在完成寻址功能后的数据传输. 传输层将路由交换层和应用数据层划分开, 主要提供数据传输控制.
udp 的使用是面向无连接的, 即协议自身的设计是不保证数据的有序性和重传的, 这样的缺点是丢包率增加, 并且无法有序的接收数据. 不过这两个缺点都可以通过应用层来弥补. 而 udp 的优点是报头小, 数据传输效率高(不需要确认, 协商各种流控制等等), 所以 udp 更适用于数据需要快速传输, 并对数据完整性并不太高的场景. 比如: 视频直播, 要求实时传输, 而且可以接受部分的丢帧.
udp 报头非常的简单, 核心只有端口 + 校验和
2,tcp
tcp 的使用是面向连接的, 即数据的传输必须基于虚链路的完整建立. 虚链路的建立提供了很多优秀的功能, 比如确认重传以保证数据的完整性, 滑动窗口以保证数据传输的高效性, 各类选项字段提供不同的可选功能. tcp 的缺点是报头大, 因为需要包含比 udp 更多的功能字段, 而且 tcp 数据的每次发送都必须得到确认否则将会重传. tcp 一般应用于对数据完整性要求很高的场景. 比如: ssh.
tcp 的报头字段比较多, 核心有: seq 和 ack 用于处理确认重传, SYN/FIN/RST 等用于提供数据包身份标记, 窗口用于处理流量控制, 还有各种选项.
3,socket 函数
为了将路由交换和应用层分离开, socket 提供了一个统一的接口供应用层直接调用而无需考虑底层路由交换的通信问题.
所有的操作系统都提供 socket 调用, python 的 socket 模块也是对底层 socket 模块的封装, 并提供了方便使用的一些函数接口.
1, 建立对象
- # 实例化一个 socket 对象, 用于处理本地的 socket 事务, 不论是服务器还是客户端都一样
- # socket 模块提供了很多不同类型的 socket, 这里选择 tcp, 使用 ipv4
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- 2,bind
- # socket 对象可以绑定本地的 ip 和端口号, 这个函数在服务器和客户端均可使用, 不过一般不会绑定客户端
- # 客户端的 ip 和端口一般使用随机.
- # 服务器一般会固定 ip 和端口以供大量客户端连接
- # 服务器提供的 ip 会通过 dns 发布域名
- # bind 的参数是一个 ip+port 的元组, 如果没有提供 ip, 则会监听本机所有对外接口 ip
- sock.bind(('127.0.0.1', 8080))
- 3,listen
- # 此函数只应该用于服务器, 因为服务器才需要监听端口等待客户主动连接
- # 此函数将会告知操作系统监听 socket 连接
- # 此函数的底层操作应该就是完成 tcp 三次握手
- # 此函数的 5 表示已经完成三次握手的客户端最大数量, 但是这些客户端还未与服务器交互数据
- sock.listen(5)
- 4,connect
- # 此函数只应该用于客户端, 因为客户端才需要连接服务器的端口
- # 此函数一旦启动, 则会在客户端上随机选取本地端口
- # 此函数的语义是发起 tcp 三次握手
- sock.connect(('127.0.0.1', 8080))
- 5,accept
- # 此函数应该用于服务器, 当 tcp 三次握手完成之后, 服务器通过此函数获取此客户端 socket 对象和地址.
- # 此函数是一个阻塞函数, 即, 如果服务器没有任何虚链路完成, 将会无限阻塞, 直到有一个虚链路通过
- # listen 完成, accept 才会返回.
- # 如果要服务器提供无限接收客户端的功能, 应该循环此函数以提供链路循环
- conn, addr = sock.accept()
- 6,recv
- # 此函数用于从一个 socket 对象 (管道) 中获取数据, 而实际上, 是从操作系统的网卡缓存中获取数据
- # 可以指定需要一次获取的字节数, 获取得到的数据是 bytes 类型, 需要 decode 才方便阅读
- # 此函数是一个阻塞函数, 即, 如果网卡缓存没有任何数据, 则会一直阻塞到数据到达为止
- msg = conn.recv()
- 7,send
- # 此函数用于将 bytes 类型的数据发送给 socket 对象(管道), 而实际上, 是发送给网卡缓存, 后续交由
- # 操作系统真正的发送数据.
- # 此函数非阻塞, 可以直接返回, 不过要特别注意的是, msg 如果为空, 此函数可以正确执行, 但是实际上
- # 操作系统是没有发送数据给对端的. 这样会产生一些 socket 连接的问题, 所以要杜绝发空.
- conn.send(msg)
- 8,close
- # 关闭虚链路
- conn.close()
- 9,getpeername
- # 获取一个虚链路对端的地址二元组
- print(sock.getpeername())
4, 粘包问题
根本原因: tcp 面向流, 无法区分消息之间的数据边界, 固定 recv 就会导致消息粘包
解决思路: 每次 recv 的时候动态获取, 并准确的获取一个消息的长度
解决办法: 每一个消息都增加固定长度的报头. 每次 recv 的时候先获取固定长度的报头, 从报头中获取本次消息的准确长度, 然后再 recv 完整准确的信息.
来源: https://www.cnblogs.com/zzzlw/p/9287547.html