Socket编程(二)——协议的细节和选项
接上一篇,主要介绍一下Socket编程中如何体现TCP/IP族协议的一些细节,包括流量控制、半关闭等。
TCP连接过程
由于TCP协议是面向连接的,所以在进行数据传输前必须建立连接。
服务端方面
服务端首先需要创建自己的套接字,这个套接字并不是真正用于收发数据的,而是起到类似“门卫”的作用。服务端套接字绑定一个端口号后,便可以调用listen
函数等待来自客户端的连接。当收到来自客户端的连接请求时,服务端就应该调用accept
函数接受连接请求。回顾上一篇blog,该函数会返回一个套接字,这个套接字才是真实用于与客户端进行数据传输的。完成连接后就可以进数据传输了。
客户端方面
客户端方面的连接较为简单,只需要调用connect
函数对服务端的指定端口发起连接请求即可。需要注意的是,客户端不需要指定端口号,而是由操作系统分配一个临时端口号。
“无数据边界”
之前提到过,TCP这类面向连接的协议是不存在数据边界的。考虑书本上的例子——回声服务端:即服务端会将客户端发送的内容原样发送回去。这样的功能看似很简单,但是如果使用面向连接的协议来实现,就需要考虑许多问题。例如:
- 客户端发送的内容会先放在缓冲区,有可能会被分成多次发送。
- 服务端接收数据的数据有可能也被分成多段,无法确定字符串的结尾。(服务端只调用一次
read
的话会导致读取内容不全) - 服务端发送的内容有可能在缓冲区被分成多次发送。
简而言之就是双方都不知道到底如何定义一个字符串的结束,这就要我们在应用层设计好协议来方便双方的数据传输。在传输层、网络层的协议都定义了固定格式的协议头,这就是为了保证数据传输过程中双方都能够理解,不会产生歧义。
在这个例子中,可以约定用数据的前4个字节作为字符串长度,接下来的若干个字节就是字符串的内容。在读数据时,使用循环来保证读取的内容达到规定的长度。当然,也可以规定某个字符作为结束符,实现方法不同,协议的特点也不同。
"无数据边界"意味着“数据传输过程中调用IO函数的次数不具有任何意义”。
UDP消息发送
惯例上,人们习惯以”数据报(包)“作为面向连接的数据单位,以”消息“作为不面向连接的数据单位。与TCP协议不同,由于不需要建立连接,客户端和服务端双方都只需要一个套接字,就像是“信箱”一样,而且一个套接字可以同时与多个主机通信。
IO函数
基于UDP的IO函数与TCP有所不同,毕竟实现原理都不同。
1 |
|
上面函数与TCP的IO函数最大的区别就是直接在函数中指定了接收和发送数据的地址,也不需要使用bind
函数来绑定端口号,而是调用sendto
和recvfrom
自动分配一个端口,这点比TCP更直接,也能够直接体现出UDP这类面向消息的协议的特点。
已连接(connected)UDP套接字
UDP的IO函数传输过程分为3个步骤:注册IP和端口、传输数据、删除IP和端口。如果通信的对象较为固定,频繁调用IO函数可能会花费较多时间在注册和删除IP端口上,所以可以创建已连接UDP套接字来提高性能。
1 |
|
上述代码创建了一个UDP类型的套接字,但调用了 connect
函数,但这并不意味着使用这个套接字来建立连接,而是向目标套接字注册IP和端口信息。
优雅地断开套接字连接
TCP协议在通信结束后还会经历四次挥手来断开TCP连接,四次挥手中的前两次通信完成后的状态称为半关闭,即一方表明自己没有数据需要发送,但仍可以接受数据,待对方数据发送完后再完全断开连接。Socket编程中用于完成半关闭的函数为:
1 |
|
上述三个常量在Windows平台下为SD_RECEIVE,SD_SEND,SD_BOTH
DNS域名服务
DNS的概念不难,主要介绍一下库中关于DNS的函数
1 |
|
比较值得注意的是h_addr_list
参数,是一个链表,保存整数形式的域名链表。
相反的函数为:
1 |
|
套接字的可选项
进行套接字编程时可以根据需求对套接字的传输特性进行设置。下面两个是对可选项进行操作的函数:
1 |
|
SO_REUSEADDR(重用地址)
在最初进行编程时,很经常发现服务端主动关闭后需要过一段时间(几分钟)才能再次在同样的端口上再次运行服务端。其原因就在于TCP断开连接的Time-wait状态。服务端主动关闭后,在四次挥手后还需要等待一个来回时间的Time-wait阶段来保证对方收到了自己上一个ACK报文,在这个过程中服务端原先绑定的端口不会被释放。所以短时间内再次运行客户端会出现bind error。
通过将SO_REUSEADDR
这个可选项设置为true可以解决这个问题。
TCP_NODELAY
将这个可选项设置为true可以禁用Nagle算法,从而在传输较大数据时提高传输效率(但是可能会增加网络流量,影响传输)。
Nagle算法即在收到上段数据的ACK报文后再发送下一个段,在传输时间较长时Nagle算法会导致传输效率低下。