此博文是学习UNP(UNIX Network Programming)后的读书笔记,供以后自己翻阅回顾知识。
- TCP、UDP概述
在前面《计算机网络与TCP/IP》栏目下已经介绍过一些关于TCP、UDP的相关知识TCP/IP(三):传输层TCP与UDP,这里只是简单从UNIX网络编程的角度介绍TCP、UDP协议。
我们都知道UDP 缺乏可靠性、无连接的,面向数据报 的协议,如果想确保数据报到达目的地,必须自己在应用层实现一些特性:对端的确定、本端的超时和重传等。UDP面向报文的特性,使得UDP不像TCP中可以通过设置MSS(最大分节大小)避免IP层分片,UDP中没有相应的措施避免在IP层中进行分片,所以在使用UDP中,应该控制传输数据报的大小,避免分片,但是数据报太小,利用率低,应该合理规划。
相反,TCP提供 可靠的传输服务、流量控制、面向字节流、连接的协议, 通过超时重传、确认等手段实现可靠的传输服务,TCP中含有动态估算客户和服务器之间的往返时间(round-trip time RTT)的算法,知道等待确认需要多少时间。TCP中对所发送数据中的每个字节进行排序(序列号), 例如一个应用写2048字节到一个TCP套接字,导致TCP发送2个分节,一个分节发送序列号为1~1024的数据,一个分节发送序列号为1025~2048的数据, 接收端会对数据的序列号进行排序组合(各个分节可能非顺序到达目的端),保证数据的正确性,同时如果分节在传输的过程中丢失,发送端应该超时重传,对于重复的分节,接收端有能力丢弃重复的分节。TCP总是告知对端任何时刻它一次能从对端接收多少字节的数据,这称为通告窗口,该窗口指出接收缓冲区中当前可用的空间量,从而保证发送端发送的数据不会使接收缓冲区溢出。通过此种方式提供流量的控制。
- TCP连接的建立和终止
通常服务器端通过调用socket,bind和listen实现“被动打开”, 客户端通过调用socket,connect实现“主动打开”,TCP通过三次握手建立连接
终止一个TCP连接需要4个分节:
- 主动调用close的应用进程执行“主动关闭”,此时改端发送一个FIN分节,表示数据发送完毕;
- 接收这个FIN的对端执行被动关闭。此时这个FIN由内核TCP负责进行确认,用户程序不用管是内核在主动进行回答,同时内核将一个文件结束符(end-of-file)传递给接收端的应用程序(放在等候应用程序接收的任何其他数据之后),FIN的接收意味着接收应用进程在相应的连接上没有数据可以接收。
- 一段时间后,当应用程序接收到这个文件结束符,应用程序调用close主动关闭它的套接字,这导致它的TCP也发送一个FIN。
- 接收这个最终FIN的原发送端TCP(执行主动关闭的那一端)确认这个FIN,对FIN的确认都是内核TCP协议在进行处理。
TCP的11个状态转换图:
TCP的同时打开和同时关闭:
TIME_WAIT状态两个存在理由:(详细见)
- 可靠地实现TCP全双工连接的终止;
- 允许老的重复分节在网络中的消逝。
- TCP端口号和并发服务器
TCP无法仅仅通过查看目的端口号来分离外来的分节到不同的端点,必须查看套接字对的所有4个元素才能确定由哪个端点来接收某个到达的分节。
{接收接口的IP地址:服务端口号,客户端的IP地址:客户端的端口号}//服务端的4元素套接字对
{客户端出口IP地址:客户端临时端口号,服务器的IP地址:服务端口号}//客户端的4元素套接字对
对于一个多宿主机监听21号端口的套接字,如果不设置监听端接口的IP地址,则是通配符*表示,表示监听到达此主机的任意IP地址(服务器的IP地址),在UNIX中可以通过SOADDR_ANY指定,在调用bind前把套接字的地址结构中的IP地址字段设置为 SOADDR_ANY;
当客户主机启动一个客户执行主动打开,指定服务器的IP地址为12.106.32.254,服务器端的通过调用fork生成子进程进行处理,此时服务端有两个套接字,一个是监听套接字,一个是和客户连接的套接字:
当有多个客户请求的时候,套接字对的情况如下:
如果一个分节来自206.168.112.219:1500,目的地址为:12.106.32.254:21,它被传递给第一个进程进行处理;
如果一个分节来自206.168.112.219:1501,目的地址为:12.106.32.254:21,它被传递给第二个进程进行处理;
所有目的端口号为21 的其他TCP分节都被递送到监听套接字的父进程。
- 缓存区大小和限制
每一个TCP套接字都有一个发送缓存区,可以通过SO_SNDBUF套接字选项来更改缓存区的大小,当某个进程中调用write时,内核从该应用进程的缓存区中复制所有数据到所写套接字的发送缓存区中,如果缓存区的容不下应用进程的所有数据(发送缓存区的数据大小太小或者发送缓存区存在数据),此时write会阻塞,直到发送缓存区中有空间存应用进程发送的数据。当应用进程从write返回只是说明可以重新使用原来的应用进程的缓存区,不代表对端接收到数据。
这里的输出队列需要注意的是,如果输出队列满了,新到的分组将会丢弃,同时沿协议栈向上返回一个错误,在某个时刻重传这个分节。套接字缓存区中的数据,直到接收到对端的确认,才会删除数据。
对于UDP来说,内核并没有维护一个套接字发送缓存区,但是依然可以通过SO_SNDBUF设置发送缓存区的大小,如果应用程序写一个大于缓存区大小的数据报将返回EMSGSIZE错误。而实际不存在套接字缓存区,因为UDP中不需要处理超时重传,同理,write返回成功不能说明对端接收到数据,只能说明所写的数据报加入到数据链路层的输出队列,同时如果队列已满,则丢弃数据报,内核可能返回错误也可能不返回错误,具体依赖于实现。