TCP/UDP如何进行Socket通信

socket 接口大多数情况下操作的是传输层,更底层的协议不用它来操心。在传输层有两个主流的协议 TCP 和 UDP, socket 程序设计也是主要操作这两个协议。

  • TCP 是面向连接的,UDP 是面向无连接的。
  • TCP 提供可靠交付,无差错、不丢失、不重复、并且按序到达;UDP 不提供可靠交付,不保证不丢失,不保证按顺序到达。
  • TCP 是面向字节流的,发送时发的是一个流,没头没尾;UDP 是面向数据报的,一个一个地发送。
  • TCP 是可以提供流量控制和拥塞控制的,既防止对端被压垮,也防止网络被压垮。

从本质上来讲,所谓的建立连接,其实是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,并用这样的数据结构来保证面向连接的特性。TCP 无法左右中间的任何通路,也没有什么虚拟的连接,中间的通路根本意识不到两端使用了 TCP 还是 UDP。

所谓的连接,就是两端数据结构状态的协同,两边的状态能够对得上。符合 TCP 协议的规则,就认为连接存在;两面状态对不上,连接就算断了。

流量控制和拥塞控制其实就是根据收到的对端的网络包,调整两端数据结构的状态。TCP 协议的设计理论上认为,这样调整了数据结构的状态,就能进行流量控制和拥塞控制了,其实在通路上是不是真的做到了,谁也管不着。

所谓的可靠,也是两端的数据结构做的事情。不丢失其实是数据结构在“点名”,顺序到达其实是数据结构在“排序”,面向数据流其实是数据结构将零散的包,按照顺序捏成一个流发给应用层。总而言之,“连接”两个字让人误以为功夫在通路,其实功夫在两端。

socket 函数用于创建一个 socket 的文件描述符,唯一标识一个 socket。我们把它叫作文件描述符,因为在内核中,我们会创建类似文件系统的数据结构,并且后续的操作都有用到它。

socket 函数有三个参数。

  • domain:表示使用什么 IP 层协议。AF_INET 表示 IPv4,AF_INET6 表示 IPv6。type:表示 socket 类型。
  • SOCK_STREAM,顾名思义就是 TCP 面向流的,SOCK_DGRAM 就是 UDP 面向数据报的,SOCK_RAW 可以直接操作 IP 层,或者非 TCP 和 UDP 的协议。例如 ICMP。
  • protocol 表示的协议,包括 IPPROTO_TCP、IPPTOTO_UDP。

通信结束后,我们还要像关闭文件一样,关闭 socket。

1、TCP编程

TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 socket 赋予一个端口和 IP 地址。

服务端所在的服务器可能有多个网卡、多个地址,可以选择监听在一个地址,也可以监听 0.0.0.0 表示所有的地址都监听。服务端一般要监听在一个众所周知的端口上,例如,Nginx 一般是 80,Tomcat 一般是 8080。

最低位放在最后一个位置,我们叫作小端,最低位放在第一个位置,叫作大端。TCP/IP 栈是按照大端来设计的,而 x86 机器多按照小端来设计,因而发出去时需要做一个转换。

下来,就要建立 TCP 的连接了,也就是著名的三次握手,其实就是将客户端和服务端的状态通过三次网络交互,达到初始状态是协同的状态。下图就是三次握手的序列图以及对应的状态转换。

接下来,服务端要调用 listen 进入 LISTEN 状态,等待客户端进行连接。

连接的建立过程,也即三次握手,是 TCP 层的动作,是在内核完成的,应用层不需要参与。

接着,服务端只需要调用 accept,等待内核完成了至少一个连接的建立,才返回。如果没有一个连接完成了三次握手,accept 就一直等待;如果有多个客户端发起连接,并且在内核里面完成了多个三次握手,建立了多个连接,这些连接会被放在一个队列里面。accept 会从队列里面取出一个来进行处理。如果想进一步处理其他连接,需要调用多次 accept,所以 accept 往往在一个循环里面。

接下来,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端口号,然后发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accept 就会返回另一个 socket。

监听的 socket 和真正用来传送数据的 socket,是两个 socket,一个叫作监听 socket,一个叫作已连接 socket。成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

2、UDP编程

UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因而也需要 bind。

对于 UDP 来讲,没有所谓的连接维护,也没有所谓的连接的发起方和接收方,甚至都不存在客户端和服务端的概念,大家就都是客户端,也同时都是服务端。只要有一个 socket,多台机器就可以任意通信,不存在哪两台机器是属于一个连接的概念。因此,每一个 UDP 的 socket 都需要 bind。每次通信时,调用 sendto 和 recvfrom,都要传入 IP 地址和端口。

此文章为3月Day30 学习笔记,内容来源于极客时间《趣谈 Linux 操作系统》,推荐该课程。

猜你喜欢

转载自blog.csdn.net/key_3_feng/article/details/129867669