《Unix网络编程》读书笔记

UDP和TCP

UDP(User Datagram Protocol,用户数据报协议)是一个无链接协议,不保证UDP数据报会到达其最终目的地,不保证各数据报的前后顺序跨网络后保持不变,也不保证每一个数据报只到达一次。算法

UDP提供无链接的服务,由于UDP客户与服务器之间没必要存在任何长期的关系。一个UDP客户可使用一个套接字发送数据报给多个服务器,一个UDP服务器也能够用同一个套接字从不一样的客户接收数据报。编程

每一个UDP数据报都一个长度,数据报的长度会随数据一同传递给接收端进程;而TCP是一个字节流协议,没有任何记录边界。安全

TCP(Transmission Control Protocl,传输控制协议)是一个面向链接的协议,为用户进程提供可靠的全双工字节流。服务器

TCP提供客户与服务器之间的链接。TCP客户先与某个给定的服务器创建链接,再跨该链接与那个服务器交换数据,而后终止这个链接。网络

TCP提供了可靠性。当TCP向另外一端发送数据时,它要求对端返回一个确认。若是没有收到确认,TCP就自动重传数据并等待更长时间,在数次重传失败后才放弃。TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time,RTT)的算法,以便知道等待一个确认须要多少时间。TCP经过给其中每一个字节关联一个序列号对所发送的数据进行排序,接收端根据收到的分节的序列号从新排序,而且根据序列号判断并丢弃重复数据。并发

TCP提供流量控制。TCP老是告知对端在任什么时候刻它一次可以从对端接收多少字节的数据,这称为通告窗口,该窗口指出接收缓冲区当前可用的空间量,从而确保发送端发送的数据不会使接收缓冲区溢出。异步

TCP链接是全双工的,这意味着在一个给定的链接上应用能够在任什么时候刻在进出两个方向上既发送数据又接收数据。socket

TCP链接创建和终止

TCP创建一个链接须要3个分节,称为TCP的三路握手,而终止一个链接则须要4个分节。下图展现了一个完整的TCP链接所发生的实际分组交换状况,包括链接创建、数据传送和连接终止3个阶段,还展现了每一个端点所历经的TCP状态。函数

每个SYN选项能够含有多个TCP选项,下面是经常使用的选项。大数据

  • MSS选项。发送SYN的TCP一端使用本选项通告对端它的最大分节大小,即MSS(Maximum Segment Size),也就是它在本链接的每一个TCP分节中愿意接受的最大数据量。
  • 窗口规模选项。TCP能通告的最大窗口大小是65535,由于TCP首部中相应的字段占16位。
  • 时间戳选项。它能够防止失而复现的分组可能形成的数据损坏。

TCP涉及链接创建和链接终止的操做能够用状态转换图来讲明。

TIME_WAIT状态有两个存在的理由:

  1. 可靠地实现TCP全双工链接的终止。假设最终的ACK丢失了,服务器将从新发送它的最终那个FIN,所以客户必须维护状态信息,以容许它从新发送最终的那个ACK。
  2. 容许老的重复分节在网络中消逝。假设在关闭一个链接一段时间后在相同的IP地址和端口之间创建了另外一个链接,即前一个链接的化身,TCP必须防止来自某个链接的老的重复分组在该链接已终止后再现,从而被误解为属于其化身的分组。所以TCP将不给处于TIME_WAIT状态的链接发起新的化身,TIME_WAIT状态持续时间为2MSL(Maximum Segment Lifetime,最长分节生命期),就足以让某个方向上的分组和另外一方向上的应答最多存活MSL被丢弃。

一个TCP套接字对是一个定义该链接的两个端点的四元组:本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号,套接字对惟一标识一个网络上的每一个TCP链接。

缓冲区大小及限制

许多网络有一个可由硬件规定的MTU(Maximum Transmission Unit,最大传输单元),如以太网的MTU是1500字节。在两个主机之间的路径最小MTU称为路径MTU,两个主机之间相反的两个方向上路径MTU能够不一致,由于因特网中路由选择每每是不对称的。当一个IP数据报将从某个接口发出时,若是它的大小超过相应链路的MTU,IP将执行分片,这些分片在到达最终目的地以前一般不会被重组。

IPv4和IPv6都定义了最小重组缓冲区大小,它是IPv4或IPv6的任何实现都必须保证支持的最小数据报大小,其值对于IPv4为576字节。

TCP的MSS用于向对端TCP通告对端在每一个分节中能发送的最大TCP数据量。MSS的目的是告诉对端其重组缓冲区大小的实际值,从而视图避免分片。MSS一般设置成MTU减去IP和TCP首部固定长度(都为20字节),如在以太网中使用IPv4的MSS为1460(1500-20-20)。

每个TCP套接字有一个发送缓冲区,当某个应用进程调用write时,内核从该应用进程的缓冲区复制全部数据到所写套接字的发送缓冲区。对于阻塞套接字,若是发送缓冲区容不下该应用进程的全部数据,进程将被投入睡眠,直到应用进程缓冲区中的全部数据都复制到套接字发送缓冲区。所以,从写一个TCP套接字的write调用成功返回仅仅表示咱们能够从新使用原来的应用进程缓冲区,并不代表对端的TCP或应用进程已接收到数据。TCP提取套接字发送缓冲区中的数据并把它发送给对端TCP,在对端ACK到达后,本端TCP才能从发送缓冲区中丢弃已确认的数据。TCP数据经由IP传递给数据链路,每一个数据链路都有一个输出队列,若是该队列已满,新的分组将被丢弃,并沿协议栈向上返回一个错误到TCP,TCP将注意到这个错误,并在之后某个时刻重传相应的分节,这个过程对应用进程透明。

UDP是不可靠的,它没必要保存应用进程数据的副本,所以UDP套接字没有发送缓冲区,但有发送缓冲区大小,表示可写到改套接字的UDP数据报大小的上限,若是一个应用进程写一个大于套接字发送缓冲却大小的数据报,内核将返回给进程一个EMSGSIZE错误。因为没有相似TCP的MSS,UDP应用进程在发送大数据报比TCP更可能被分片。从写一个UDP套接字的write调用成功返回表示所写的数据报或其全部片断已被缴入数据链路层输出队列,若是该队列没有足够的空间存放改数据报或它的某个片断,内核一般会返回一个ENOBUFS给它的应用进程。

套接字地址结构

IPv4套接字结构为sockaddr_in,定义在头文件<netinet/in.h>中。

套接字函数为了能支持任何协议族的套接字地址结构,使用了一个通用套接字地址结构的指针做为参数,头文件<sys/socket.h>中定义了这个通用的套接字地址结构sockaddr。

下面是网络地址在点分十进制数串和网络字节序二进制值之间转换的函数。

inet_addr出错时返回INADDR_NONE(一般是一个32为均为1的值),这意味着255.255.255.255不能由该函数处理。现在inet_addr已被废弃,新的代码应该改用inet_aton函数。

inet_ntoa返回值所指向的字符串驻留在静态内存中,这意味着该函数是不可重入的。

TCP套接字编程

基本TCP客户/服务器程序的套接字函数使用以下:

socket函数

为了执行网络IO,一个进程必须作的第一件事就是调用socket函数。

其中family指明协议域:

type参数指明套接字类型:

connect函数

TCP客户用connect函数来创建与TCP服务器的连接。

若是是TCP套接字,调用connect函数将触发TCP的三路握手过程,并且仅在链接创建成功或出错时才返回,出错返回多是如下几种状况。

  1. 若TCP客户没有收到SYN分节的响应,通过必定的重试后返回ETIMEDOUT错误。
  2. 若对客户的SYN的响应是RST,则代表该服务器主机在咱们指定的端口上没有进程在等待与之链接,客户收到RST返回ECONNREFUSED错误。
  3. 若客户发出的SYN在中间的某个路由器上引起一个"destination unreachable"ICMP错误,通过必定的重试后返回EHOSTUNREACH或ENETUNREACH错误。

bind函数

bind函数把一个本地协议地址赋予一个套接字。

bind能够指定一个也能够端口号,或指定一个IP地址,也能够二者都指定,或者都不指定。

若一个TCP客户或服务器未调用bind绑定一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时端口。

TCP服务器绑定到某个IP,这就限定该套接字只接收目的地址为该IP的链接。TCP客户一般不绑定IP,链接套接字时,内核将根据外出网络接口来选择源IP。若TCP服务器未绑定IP,内核把客户发送的SYN的目的地址做为服务器的源IP。

从bind函数返回的一个常见错误是EADDRINUSE("Address aready in use",地址已使用)。

listen函数

listen函数把一个未链接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的链接请求。

backlog参数规定了内核应该为相应套接字排队的最大链接数。内核为任何一个给定的监听套接字维护队列:

  1. 未完成链接队列,每一个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。
  2. 已完成连接队列,每一个已完成TCP三路握手过程的客户对应其中一项。

当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST。

accept函数

accept函数用于从已完成链接队列对头返回下一个已完成链接。

若是accpet成功,其返回已链接套接字的描述符,这是一个不一样于监听套接字的新套接字。咱们称它的第一个参数为监听套接字描述符,称它的返回值为已链接套接字描述符。

若是咱们对返回客户协议地址不感兴趣,能够把cliaddr和addrlen均置为空指针。

close函数

close函数用来关闭套接字,并终止TCP链接。

close一个TCP套接字的默认行为是把该套接字标记成已关闭,而后当即返回到调用进程。TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP链接终止序列。

关闭已链接套接字只是致使相应描述符的引用计数减1,若是引用计数值仍大于0,close调用并不引起TCP四分组链接终止序列。若是确实想在某个TCP链接上发送一个FIN,能够改用shutdown函数代替close。

recv和send函数(305)

 

getsockname和getpeernanme函数

这两个函数或者返回与某个套接字关联的本地协议地址(getsockname),或者返回与某个套接字关联的外地协议地址(getpeername)。

recv和send函数

TCP异常状况

服务器进程终止

服务器进程终止后,进程中全部打开的描述符都被关闭,这就致使向客户发送一个FIN,而客户TCP响应一个ACK。

若是客户继续发送数据给服务器,服务器收到来自客户的数据时,因为先前打开那个套接字的进程已经终止,因而响应一个RST。

然而客户端进程看不到这个RST,因为前面接收到的FIN,调用read会当即返回0。若是进程忽略该错误,继续发送数据到服务器,将返回EPIPE错误。

当一个进程向某个已收到RST的套接字执行写操做时,内核向进程发送一个SIGPIPE信号,写操做会分会EPIPE错误。

服务器主机崩溃

当服务器主机崩溃时,客户TCP持续重传数据分节,试图从服务器上接收一个ACK。当客户TCP最终放弃时,给客户进程返回一个错误。假设服务器主机已崩溃,从而对客户的数据分节没有响应,那么所返回的错误是ETIMEDOUT;若是某个中间路由器断定服务器主机不可达,从而响应一个"destination unreachable"ICMP消息,那么返回的错误是EHOSTUNREACH或ENETUNREACH。

服务器主机崩溃后重启

当服务器主机崩溃后重启时,它的TCP丢失了崩溃钱的全部链接信息,所以服务器TCP对于所收到的来自客户的数据分节响应一个RST。

当客户TCP收到该RST时,客户的read调用返回ECONNRESET错误。

服务器主机关机

系统关机时,init进程一般先给全部进程发送SIGTERM信号,而后给全部仍在运行的进程发送SIGKILL信号。当服务器进程终止时,它的全部打开着的描述符都被关闭。

I/O模型

  • 阻塞式I/O。默认情形下全部套接字都是阻塞的。
  • 非阻塞式I/O。进程把一个套接字设置成非阻塞是在通知内核:当全部请求的I/O操做非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
  • I/O复用模型。I/O复用是调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。
  • 信号驱动式I/O模型。让内核在描述符就绪时发送SIGIO信号通知进程。
  • 异步I/O模型。函数的工做机制是:告知内核启动某个操做,并让内核在整个操做完成后通知咱们。

套接字选项

有如下方法来获取和设置影响套接字的选项:

  • getsockopt和setsockopt函数
  • fcntl函数
  • ioctl函数

getsockopt和setsockopt函数

getsockopt和setsockopt仅用于套接字。

可获取和设置的套接字选项以下:

SO_KEEPALIVE

给一个TCP套接字设置保持存活选项后,若是2小时内在该套接字的任一方向上都没有数据交换,TCP就自动给对端发送一个保持存活探测分节。

SO_LINGER

本选项指定close函数对面向链接的协议如何操做。默认操做是close当即返回,可是若是有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端。本选项在用户进程和内核间传递以下结构,它在头文件<sys/socket.h>中定义:

本选项有如下情形:

  1. 若是l_onoff为0,那么关闭本选项,TCP默认设置生效,即close当即返回。
  2. 若是l_onoff为非0且l_linger为0,那么当close某个链接时TCP将终止该链接。就是说TCP将丢弃保留在套接字发送缓冲区中的任何数据,并发送一个RST给对端,而没有一般的四分组链接终止序列,这样避免了TCP的TIME_WAIT状态。
  3. 若是l_onoff为非0且l_linger为非0,那么当关闭套接字时内核将拖延一段时间。就是说若是在套接字发送缓冲区中仍残留数据,那么进程将被投入睡眠,直到数据都已发送且被对端确认或延滞时间到。

SO_RCVBUF和SO_SNDBUF

对于TCP来讲,套接字接收缓冲区中可用空间的大小限定了TCP通告对端的窗口大小。对于UDP来讲,当接收到的数据报装不进套接字接收缓冲区时,改数据报就被丢弃。

因为TCP的窗口规模选项是在创建链接时用SYN分节与对端互换获得的。对于客户端,SO_RCVBUF选项必须在调用connect以前设置;对于服务器,该选项必须在调用listen以前给监听套接字设置。

SO_RCVTIMEO和SO_SNDTIMEO

这两个选项容许咱们给套接字的接收和发送设置一个超时值。

SO_REUSEADDR

本选项能起到如下4个不一样的做用:

  1. 容许启动一个监听服务器并绑定某个端口,即便之前创建的将该端口用做他们的本地端口的链接仍存在。
  2. 容许在同一端口上启动同一服务器的多个实例,只要每一个实例绑定一个不一样的本地IP地址便可。
  3. 容许单进程绑定同一端口到多个套接字上,只要每次绑定不一样的本地IP地址便可。
  4. 容许彻底重复的绑定,即一样的IP地址和端口能够绑定到多个套接字上,本特性仅支持UDP套接字。

TCP_NODELAY

开启本选项将禁止TCP的Nagle算法,默认状况下该算法是启动的。Nagle算法的目的是为了减小广域网上小分组的数目。该算法的思想是:若是某个给定的链接上有待确认的数据,那么本来应该做为用户写操做之响应的在该链接上当即发送相应小分组的行为就不会发生。

fcntl函数

fcntl函数可执行各类描述符控制操做。

fcntl提供的与网络编程相关的特性主要是设置非阻塞式I/O,经过使用F_SETFL命令设置O_NONBLOCK文件状态标志,能够把一个套接字设置为非阻塞型。典型代码以下:

UDP套接字编程

UDP客户/服务器程序程序所用的套接字函数以下:

recvfrom和sendto函数

flags老是置为0。

写一个长度为0的数据报是可行的,这会造成一个只包含IP首部和UDP首部而没有数据的IP数据报。recvfrom返回0是可接受的:它并不像TCP套接字上read返回0表示对端已关闭链接。

若是recvfrom的from参数是一个空指针,那么相应的addrlen也必须是一个空指针,表示不关心数据发送者的协议地址。

客户的临时端口是在第一次调用sendto时一次性选定的,不能改变;然而客户的IP地址却能够随客户发送的每一个UDP数据报而变更。

服务器进程未运行

若是服务器进程未运行,客户数据报发出,服务器主机响应一个"port unreachable"ICMP消息,不过这个ICMP错误不返回给客户进程。咱们称这个ICMP错误为异步错误,该错误由sendto引发,但sendto自己却成功返回。一个基本规则是:对于一个UDP套接字,由它引起的异步错误却并不返回给它,除非它已链接。

UDP的connect函数

咱们能够给UDP套接字调用connect,内核只是检查是否存在当即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号,而后当即返回到调用进程。

UDP客户进程或服务器进程只有在使用本身的UDP套接字与肯定的惟一对端进行通讯时,才能够调用connect。

对于已链接UDP套接字,与默认的未链接UDP套接字相比,有如下不一样:

  1. 不能给输出操做指定目的IP地址和端口号,即不使用sendto而改用write或send。
  2. 没必要使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已链接UDP套接字上,由内核为输入操做返回的数据报只有那些来自connect所指定协议地址的数据报。
  3. 由已链接UDP套接字引起的异步错误会返回给它们所在的进程,而未链接UDP套接字不接收任何异步错误。

对于TCP套接字,connect只能调用一次。对于一个已链接的UDP套接字出于这两个目的之一能够再次调用connect:指定新的IP地址和端口号;断开套接字。

Unix域协议

Unix域协议并非一个实际的协议族,而是单个主机上执行客户/服务器通讯的一种方法。Unix域提供两类套接字:字节流套接字(相似TCP)和数据报套接字(相似UDP)。有如下理由使用Unix域套接字:

  1. 在某些系统中,Unix域套接字比位于同一主机的TCP套接字通讯要快。
  2. Unix域套接字可用于在同一主机上的不一样进程之间传递描述符。
  3. Unix域套接字吧客户的凭证(用户ID和组ID)提供给服务器,从而可以提供额外的安全检查措施。

地址结构

Unix域套接字地址结构在头文件<sys/un.h>中定义:

socketpair函数

socketpair函数建立两个链接起来的套接字。

family参数必须为AF_LOCAL,protocol参数必须为0,type参数既能够是SOCK_STREAM,也能够是SOCK_DGRAM,新建立的两个套接字描述符做为sockfd[0]和sockfd[1]返回。

套接字函数

在connect调用中指定的路径名必须是一个当前绑定在某个打开的Unix域套接字上的路径名,并且它们套接字类型也必须一致。

Unix域字节流套接字相似TCP套接字:它们都为进程提供一个无记录边界的字节流接口;Unix域数据报套接字相似UDP套接字:它们都提供一个保留记录边界的不可靠的数据报服务。

在一个未绑定的Unix域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点不一样于UDP套接字。

相关文章
相关标签/搜索