Unix网络编程——Web programming

本文深入探讨了OSI模型,特别是TCP/IP协议族的运作机制。讲解了TCP、UDP、SCTP在传输层的角色,以及它们各自的特点。详细阐述了TCP连接的建立与终止过程,包括三次握手和四次挥手。此外,还介绍了端口号的分配、套接字编程、I/O模型以及服务器编程中的fork和exec函数。内容涵盖网络连接的多个层面,包括地址解析、错误处理和并发服务器设计。
摘要由CSDN通过智能技术生成
OSI模型
描述一个网络中各个协议层的常用方法是使用国际标准化组织的计算机通信开放系统互联模型
图中TCP与UDP之间留有间隙,表明网络应用染过传输层直接使用IPv4或IPv6是可能的。OSI模型的顶上三层被合并成一层,称为应用层,即web客户、Telnet客户、Web服务器、FTP服务器和其他在使用的网络应用所在的层
套接字提供的是从OSI模型的顶上三层进入传输层的接口,这样设计的理由有两个
1、顶上三层处理具体网络应用(如FTP、Telnet、HTTP)的所有细节,对通信细节了解很少;低下四层对具体网络应用了解不多,却处理所有的通信细节;发送数据,等待确认,给无序列的数据排序,计算并验证校验和等
2、顶上三层通常构成用户进程,底下四层通常作为操作系统内核的一部分提供。Unix与其他现代操作系统都提供分隔用户进程与内核的机制,得第4和第5层之间的接口是构建API的自然位置
传输层:TCP、UDP、SCTP
概述:SCTP是一个交心的协议,最初设计用于跨因特网传输电话信令;UDP是一个简单的、不可靠的数据报协议;TCP是一个复杂、可靠的字节流协议。SCTP与TCP相似之处在于它也是一个可靠的传输协议,但它还提供消息边界、传输级别多宿支持,以及将头端阻塞减少到最小的一种方法
总图
tcpdump的网络应用或使用BSD分组过滤器,或使用数据链路提供接口直接与数据链路进行通信
IPv4:使用32位地址,给TCP、UDP、SCTP、ICMP、IGMP提供分组递送服务
IPv6:使用128位地址
TCP:传输控制面向连接的协议,为用户进程提供可靠的全双工字节流。TCP套接字是一种流套接字,大多数因特网应用程序使用TCP
UDP:用户数据报协议,无连接协议,UDP套接字是一种数据报套接字,UDP数据报不能保证最终到达它们的目的地,与TCP一样,UDP既可以使用IPv4,也可以使用IPv6
SCTP:流控制传输协议,SCTP是一种提供可靠全双工关联的面向连接的协议,提供消息的服务,维护来自应用层的记录边界
ICMP:网络控制消息协议,ICMP处理在路由器和主机之间流通的错误和控制消息,这些消息通常由TCP/IP网络支持软件本身产生和处理
IGMP:国际组管理协议,IGMP用于多播,在IPv4中可选
ARP:地址解析协议,把一个IPv4地址映射成一个硬件地址,ARP通常用于诸如以太网、金牌环网和FDDI等广播网络,在点到点网络上不需要
RARP:反向地址解析协议,把一个硬件地址映射成一个IPv4地址,有时用于无盘节点的引导
ICMPv6:国际控制消息协议版本6,综合了ICMPv4、IGMP、ARP的功能
BPF:BSD分组过滤器,提供对于数据链路层的访问能力,通常可以在源自Berkeley的内核中提到
DLIP:数据链路提供者接口,特提供对于数据链路层的访问能力,通常随SVR4内核提供
传输控制协议
TCP提供客户与服务器之间的连接,TCP客户先与某个给定服务器建立一个连接再跨该连接与那个服务器交换数据,然后终止连接
TCP提供可靠性,当TCP向另一端发送数据时,要求对端返回一个确认。如果没有收到确认,TCP就自动重传数据并等待更长时间。在数次重传失败时,TCP才放弃,在尝试发送数据上所花费的时间一般为4~10分钟
TCP含有用于动态估算客户和服务器之间的往返的时间的算法,以便知道等待一个确认需要多少时间
TCP通过给其中字节关联一个序列号对所发送的数据进行排序。例:假设一个应用写2048字节到一个TCP套接字,导致TCP发送2个分节:一个分节所含数据的序列号为1~1024,另一个分节所含数据的序列号为1025~2048(分节是TCP传递给IP的数据单元)
TCP连接是全双工的,意味着在一个给定的连接上应用可以在任何时刻在进出两个方向上既发送数据,又接收数据,因此,TCP必须为每个数据流方向跟踪诸如序列号和通告窗口大小等状态信息
TCP连接的建立和终止
建立一个TCP连接时:
1、服务器必须准备好接收外来的连接,通常调用socket、bind、listen这3个函数完成
2、客户通常调用connect发起主动打开,导致客户TCP发送一个SYN分节,告诉服务器客户将在连接中发送的数据的初始序列号。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项
3、服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,含有服务器将在同一连接中发送的数据的初始序列号,服务器在单个分节中发送SYN和对客户SYN的ACK确认
4、客户必须确认服务器的SYN、
这种交换至少需要3个分组,因此称为TCP的三路握手
图中给出的客户的初始序列号为J,服务器的初始序列号为K。ACK中的确认号是发送这个ACK的一端所期待的下一个序列号
TCP选项:
每一个SYN可以含有多个TCP选项,下面是常用的TCP选项
MSS选项:发送SYN的TCP一端使用本选项通知对端的最大分辨大小,即在本连接的每个TCP分解中愿意接收的最大数据量,发送端TCP使用接收端的MSS值作为所发送分节的最大大小
窗口模式选项:指定TCP首部中的通告窗口必须扩大的位数,因此所提供的最大窗口接近1GB,在一个TCP连接上使用窗口模式的前提是它的两个端系统必须都支持这个选项
TCP连接终止:
TCP建立一个连接需3个分节,终止一个连接则需4个字节
1、某个应用进程首先调用close,称该端执行主动关闭,该端的TCP发送一个IIN分节,表示数据发送完毕
2、接收到IIN的对端执行被动关闭,IIN由TCP确认,接收也作为一个文件结束符传毒给接收端应用进程,因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收
3、一段时间后,接收到这个文件结束符的应用进程调用close关闭套接字,导致TCP也发送一个FIN
4、接收这个最终FIN的原发送端TCP确认这个FIN
TCP状态转换图
观察分组
下图展示了一个完整的TCP连接所发生的实际分组交换情况,包括连接建立,数据传送和连接终止3个阶段,图中还展示了每个端点所经历的TCP状态。一旦建立一个连接,客户就构造一个请求并发送个服务器,服务器处理该请求并发送一个应答
TIME_WAIT状态
可靠地实现TCP全双工连接的终止;允许老的重复分节在网络中消逝
端口号
端口号被划分为以下3段
1、端口号为0~1023,由IANA分配和控制,相同端口号分配个TCP_UDP 和SCTP的同一给定服务
2、已登记的端口号为1024~49151,不受IANA控制,由IANA等级并提供它们的使用情况清单,以及方便整个群体
3、49152~65535是动态的或私用的端口,IANA不管这些端,即所谓的临时端口
套接字对
是一个定义该连接的两个端点的四元组:本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。套接字对唯一标识一个网络上的每个TCP。在两个端点均非多宿这个最简单的情况下SCTP与TCP所用的四元组套接字对一致
TCP端口号与并发服务器
例:首先,在主机freebsd上启动服务器,该主机是多宿的,其IP地址为12.106.32.254和192.168.42.1,服务器在端口21上执行被打开,从而开始等待客户的请求
使用记号{* : 21,* : *}指出服务器的套接字对,服务器在任意本地接口的端口21上等待连接请求,外地IP地址和外地端口都没有指定,用“*.*”表示,称为监听套接字
在地址为206.168.112.219的主机上启动第一个客户,对服务器IP之一12.106.32.254执行主动打开,如图
当服务器接收并接受这个客户的连接时,fork自身的副本,让子进程来处理该客户端的请求,如图
假设在客户主机上另有一个客户请求连接到同一个服务器,客户主机的TCP为这个新客户的套接字分配一个未使用的临时端口,如图
缓冲区大小及限制
TCP输出
如图,展示了某个应用进程写数据到一个TCP套接字时发送的步骤
每一个TCP套接字有一个发送缓冲区,可以使用SO_SNDBUF套接字选项来更改该缓冲区的大小,当某个应用进程调用white时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据,该应用进程将被投入睡眠。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,写一个TCP套接字的write调用成功返回仅仅表示可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据
UDP输出
下图展示了某个应用进程写数据到一个UDP套接字中时发送的步骤
以虚线框展示套接字发送缓冲区,因为实际上并不存在。任何UDP套接字都有发送缓冲区大小,仅仅是可写到该套接字的UDP数据报的大小上线。如果一个应用程序进程写一个大于套接字发送缓冲区大小的数据报,内核将返回该进程一个EMSGSIZE错误
SCTP输出
下图展示了某个应用进程写数据到一个SCTP套接字中发送的步骤
可以用SO_SNDBUF套接字选项来更改这个缓冲区的大小,当一个应用进程调用write时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据,应用进程将被投入睡眠。内核将不从write系统调用返回,直到应用进程缓冲区的所有数据都复制到套接字发送缓冲区,因此,从写一个SCTP套接字的write调用成功返回仅仅表示可以重新使用原来的应用进程缓冲区,并不表明对端的SCTP或应用进程已接收到数据
套接字地址结构
IPv4套接字地址结构
以sockaddr_in命名,定义在<netinet/in.h>头文件中,下图给出了POSIX定义
通用套接字结构
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(指向该结构的指针)来传递。在如何声明所传递指针的数据类型存在一个问题,有ANSI C后解决办法很简单,void * 是通用的指针类型,然而套接字函数是在ANSI C之前定义的,办法是在<sys/socket.h>头文件中定义一个通用的套接字地址结构
于是套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,如bind函数的ANSI C函数原型所示
IPv6套接字地址结构
该结构在<netinet/in.h>头文件定义,如图
注:1、如果系统支持套接字地址结构中的长度字段,那么SIN6_LEN常值必须定义
2、IPv6的地址族是AF_INRT6,而IPv4地址组是AB_INET
3、结构中字段的先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的
4、sin6_flowinfo字段分成两个字段:低序20位是流标;高序20位保留
新的通用套接字地址结构
sockaddr_storage结构在<netinet/in.h>头文件中定义
sockaddr_storage类型提供的通用套接字结构相比sockaddr存在以下两点差别:
1、如果系统支持的任何套接字地址结构有对齐需要那么sockaddr_storage能够满足最苛刻的对齐要求
2、sockaddr_storage足够大,能够容纳系统的任何套接字地址结构
基本TCP套接字编程
socket函数
为了指向网络I/O,一个进程做的第一件事就是调用socket函数,指定期望的通信协议类型
其中family参数指明协议族,该参数也往往被称为协议域。type参数指明套接字类型,protocol参数应设为下图所示的某个协议类型常值,或设为0,以选择所给定family和typc组合的系统默认值
socket函数在成功时,返回一个小的非页整数值,与文件描述类似,称之为套接字描述符
connect函数
TCP客户用connect函数来建立与TCP服务器的连接
如果是TCP套接字,调用connect函数将激发TCP的三路握手过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
1、若TCP客户没有收到SYN分节的响应,则返回ETIMEOUT。例:调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等待了75s后仍无响应则返回本错误
2、若对客户的SYN的响应是RST,则表明该服务器主机在指定的端口上没有进程在等待与之连接,例如服务器进程没在允许,是一种硬错误,客户一接收到RST就玛啥返回ECONNREFUSED错误
3、若客户发生的SYN中间的某个路由器引发一个“destination unreadable”ICMP错误,则认为是一种软错误。客户主机内核保存该消息,并按第一种情况所述的时间间隔继续发生SYN,若在某个规定时间后仍未收到响应,则把保存的消息作为EHOSTUNREACH错误返回给进程
bind函数
把一个本地协议地址赋予一个套接字,对于国际网协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合
第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号或指定一个IP地址,也可以两者都指定或都不指定
下图汇总了如何根据预期的结果,设置sin_addr和sin_port或sin6_addr和sin6_port的值
如果指定端口号为0,那么内核在bind被调用时选择一个临时端口,如果指定IP地址为通配地址,内核将等到套接字已连接或已在套接字上发出数据报时才选择一个本地IP地址
listen函数
仅由TCP服务器调用,做两件事情:
1、当socket函数创建一个套接字时,被假设成一个主动套接字,listen函数把一个未连接的套接字转换成一个被动套接字,指示内核受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态到LISTEN状态
2、本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数
本函数通常在socket和bind两个函数之后,并在调用accept函数之前调用
为理解其中的backlog参数,须认识到内核为任何一个给定的监听套接字维护两个队列:
1、未完成连接队列,每个这样的SYN分带对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程,套接字处于SYN_RCVD状态
2、已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态
每当在未完成连接队中创建一项时,来自监听套接字的参数就复制到即将建立的连接中,连接的创建机制完全是自动的
accept函数
由TCP服务器调用,用于已完成从连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠(假设套接字为默认的阻塞方式)
参数cliaddr和addrlen用来返回已连接的对端进程的协议进程。addrlen是值-结果参数:调用前,将由* addrlen所引用的整数值置为由cliaddr所指的套接字地址结果的长度,返回时,该整数值即为由内核存放在该套接字地址内的确切字节数
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,与所返回客户的TCP连接
本函数最多返回三个值:一个既可能是新套接字描述符,也可能是出错指示的整数客户进程的协议地址、该地址的大小。如果对返回的客户协议地址不感兴趣,可以把cliaddr和addrlen均置为空指针
fork和exect函数
fork函数是Unix中派生新进程的唯一方法
调用fork函数一次,返回两次,在调用进程(父进程)中返回一次,返回值是新派生进程(子进程)的进程ID号;左子进程又返回一次,返回值为0,因此,返回值本身告知当进程是子进程还是父进程
fork子进程返回0而不是父进程的进程ID的原因在于:任何子进程只有一个父进程,而且子进程总是可以通过调用getppid取得父进程的进程ID
父进程中调用fork之前打开的所有描述符在fork返回之后由子进程分享,将看到网络服务器利用这一特性:父进程调用accept之后调用fork,所接受的已连接套接字随后就在父进程与子进程之间共享。通常情况下,子进程接着读写这个已连接套接字,父进程则关闭这个已连接套接字
fork函数的两个典型用法:
1、一个进程创建一个自身的副本,副本都可以在另一个副本执行其他任务的同时处理各自的某个操作
2、一个进程要执行另一个程序,既然创建新进程的唯一办法是调用fork,该进程首先调用fork创建一个自身的副本,然后其中一个副本调用exec把自身替换成新的程序
存放在硬盘上的可执行程序文件能被Unix执行的唯一方法:由一个现有进程调用六个exec函数中的某一个,exec把当前进程映像替换成新的程序文件,而且该进程通常从main函数开始执行,进程Id不变
6个exec函数区别:
1、待执行的程序文件是由文件名还是路径名指定
2、新程序的参数是一一列出的还是由一个指针数组引用
3、把调用进程的环境传递给新程序还是新程序指定新的环境
如图所示,只有execve是内核中的系统调用,其他5个都是调用execve的库函数
开发服务器
Unix中编写并发服务器程序最简单的办法是fork一个子进程来服务每个客户,如下图
当一个连接建立时,accept返回,服务器接着调用fork,然后子进程服务客户,父进程等待另一个连接,既然新的客户由子进程提供服务,父进程关闭已连接套接字
close函数
用来关闭套接字,并终止TCP连接
close一个TCP套接字的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用。TCP尝试发送已排队等待发送到对端的任何数据,发送完毕后发送的是正常的TCP连接终止序列
I/O模型
阻塞式I/O
最流行的I/O模型,默认所有套接字都是阻塞的,如图
进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区或发生错误才返回。最常见的错误是系统调用被信号中断,进程在从调用recvfrom开始到包返回整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报
非阻塞式I/O模型
进程把一个套接字设置或非阻塞是在通知内核:当所请求I/O操作非得把本进程投入睡眠才完成时,不要把本进程投入睡眠,返回一个错误
前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULD BLOCK错误,第四次调用recvfrom时已有一个数据报准备好,被复制到应用进程缓冲区,可是recvfrom成功返回,接着处理数据
I/O复用模型
可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上
阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,调用recvfrom把所读数据报复制到应用进程缓冲区
信号驱动式I/O模型
让内核在描述符就绪时发送SIGIO信号通知
首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,进程继续工作。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,读取数据报
异步I/O模型
工作机制:告知内核启动某个动作,并让内核在整个操作完成后通知使用者,与信号驱动模型在于:信号驱动式I/O是由内核通知使用者何时可以启动一个I/O操作,而异步I/O模型是由内核通知使用者何时完成
调用aio_read函数,给内核传递描述符、缓冲区指针、缓冲区大小,并告诉内核当整个操作完成时如何通知使用者。该系统调用立即返回,而且在等待I/O完成期,进程不被阻塞
select函数
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒
调用select告知内核对哪些描述符感兴趣以及等待多长时间,描述符不限于套接字,任何描述符都可以使用select来测试
timeout告知内核等待所指定描述符中的任何一个就绪可花多长时间,其timeval结构用于指定这段时间的秒数和微秒数
该参数有以下三种可能:
1、永远等待下去:仅在有一个描述符准备好I/O时才返回,把该参数设置为空指针
2、等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数
3、根本不等待:检查描述符后立即返回,称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器必须为0
中间的三个参数readset、writeset、exceptset指定要让内核测试读、写和异常条件的描述符,目前支持的异常只有两个:
1、某个套接字的带外数据的到达
2、某个已置为分组模型的伪终端存在可以从其主端读取的控制状态
描述符就绪条件
满足以下四个条件的任何一个时,一个套接字准备好读
1、该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小
2、该连接的读半部关闭
3、该套接字是一个监听套接字且已完成数不为0
4、其上有一个套接字错误待处理
满足以下四个条件的任何一个时,一个套接字准备好写
1、该套接字发送缓冲区中的数据字节数大于等于套接字发送缓冲区低水位标记的当前大小,或该套接字已连接,或该套接字不需要连接
2、该连接的写半部关闭
3、使用非阻塞式connect的套接字已建立连接,或connect已经以失败告终
4、其上有一个套接字错误待处理
如果一个套接字存在带外数据或仍处于带外标记,那么它有异常条件待处理
select的最大描述符数
有些厂家正在将select的实现修改为允许进程将FD_SETSIZE定义为比默认值更大的某个值。BSD/OS已改变了内核实现以允许更大的描述符集,并定义了4个新的FD_xxx宏用于动态分配并操纵这样的描述符集,然后从可移植性考虑,使用大描述符集需要小心
poll函数
提供的功能与select类似,并能提供额外的信息
第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd条件
要测试的条件由events成员决定,函数在相应的revents成员中返回该描述符的状态
下图列出了用于指定events标志以及测试revents标志的一些常值
poll识别三类数据:普通、优先级带、高优先级
POSIX在poll的定义中留了许多方法可返回相应的条件
timeout参数指定poll函数返回前等待多长时间
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值