zzzzou 阅读(14) 评论(0)

目录

标签(空格分隔): 网络编程


目录

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完整准确的信息。