第四章 基本TCP套接字编程
2017-11-07 21:43 szn好色仙人 阅读(275) 评论(0) 编辑 收藏 举报//1. socket: 第一个参数:指明协议族 常用的如下: AF_INET:IPv4 AF_INET6:IPv6 第二个参数:指明套接字类型 SOCK_STREAM:字节流套接字 SOCK_DGRAM:数据报套接字 SOCK_SEQPACKET:有序分组套接字,用于SCTP中 SOCK_RAW:原始套接字 第三个参数:某个协议值类型常量,或设为0,以选择所给定的第一个参数和第二个参数组合的系统默认值 IPPROTO_TCP:TCP传输协议 IPPROTO_UDP:UDP传输协议 IPPROTO_SCTP:SCTP传输协议 函数成功返回一个小的非负整数值,类似于文件描述符,称为套接字描述符,失败返回-1 #define INVALID_SOCKET (SOCKET)(~0) //定义于WinSock2.h中 socket 函数中前两个参数的组合 (空白项为无效组合,非空白项皆为有效组合)
//2. TCP客户用 connect 函数来建立于TCP服务器的连接 客户在调用 connect 前不必非得调用 bind ,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口 如果是TCP套接字,调用 connect 会触发TCP的三路握手过程,仅在建立成功或出错时才返回,其中出错有如下几种情况: (1):若TCP客户没有收到SYN分节的响应,则会返回错误。举例来说:调用 connect 函数时,4.4BSD内核发出一个SYN,若无响应, 等待6s后在发送一个,若仍无响应,则等待24s在发送一个。若总共等待75s后仍未收到响应则返回错误 (2):若对客户的SYN响应时RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程等待其连接,客户收到RST后将立刻返回错误 RST是TCP在发生错误时候发送的TCP分节,产生RST的三个条件: A:目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器 B:TCP想取消一个已有连接 C:TCP接收到一个根本不存在的连接上的分节 (3):若客户发出的SYN在中间的某个路由器上引发一个 destination unreachable (目的地不可达) ICMP错误,则客户主机保存该消息, 继续按照(1)中规则发送SYN,若规定时间内仍未接收到响应,则返回错误。以下两种情况也有可能: A:按照本地系统的转发表,根本没有到达远程系统的路径 B:connect 调用根本不等待就返回 若 connect 失败,则该套接字不再可用,必须关闭套接字,不能在对这个套接字再次调用 connect 备注:若 connect 失败,继续使用此套接字继续 connect ,当服务器 listen 后,客户端调用的 connect 是可用连接上的(测试可用) 但是,为了防止意外,还是以书中所讲为准 connect 函数成功返回0,失败返回-1 #define SOCKET_ERROR (-1) //定义于Winsock2.h中 //3. bind 函数把一个本地协议赋予一个套接字。对于网际网协议,协议地址是32位IPv4地址或者128为IPv6地址和16位号端口的组合。 对于TCP,调用 bind 函数可以指定一个端口号或指定一个地址,也可两者皆指定或皆不指定: A:如果一个TCP客户或服务器未曾调用 bind 捆绑一个端口,当调用 connect 或者 listen 时,内核就要为相应的套接字选择一个临时端口 让内核来选择临时端口,对于TCP客户来说是正常的,然而对于TCP服务器来说是罕见的,因为服务器是通过端口被认识的 B:进程也可以讲本地的一个IP绑定到套接字上,对于TCP客户来说,这就为该套接字上发送的IP数据报指派了源IP地址。 对于TCP服务器,这就限定该套接字只接受那些目的地为这个地址的客户连接 TCP客户通常不把IP地址绑定到他的套接字上。当连接套接字时,内核将根据所用外出网络接口确定源IP地址,而所用外出接口则取决于到达服务器所需的路径 如果TCP服务器没有把IP地址捆绑到他的套接字上,那么内核就把客户发送的SYN的目的IP地址作为服务器的源IP地址 bind 函数指定要捆绑的IP地址或端口号产生的结果
如果指定端口号为0,那么内核就在 bind 被调用时选择一个临时端口,如果指定IP地址为通配地址,那么内核将等到套接字已连接(TCP)或在已连接上发出数据报(UDP) 时,才选择一个本地地址 系统使用 INADDR_ANY(IPv4) 和 in6addr_any(IPv6) 来标示通配地址 虽然 INADDR_ANY 的值一般为0,但是为了统一起见,仍最好使用 htonl 对其进行字节序设置 为了得到内核选择的临时端口值,可以使用 getsockname 来返回协议地址 //4. listen 函数仅由TCP服务器调用,它做两件事情: (1):当 socket 函数创建一个套接字时,他被假设为一个主动套接字,也就是说,他是一个将调用 connect 发起连接的客户套接字。 listen 函数把一个未连接的套接字 转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。 调用 listen 将导致套接字从 CLOSED 转换为 LISTEN 状态 (2):第二个参数规定了内核应该为相应套接字排队的最大连接个数 内核为任意一个给定的监听套接字维护两个队列: (1):未完成连接队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程, 这些套接字处于 SYN_RCVD 状态 (2):已完成连接队列(completed connection queue),每个已完成TCP三路握手过程的客户对应其中的一项。这些套接字处于 ESTABLISHED 状态 TCP为监听套接字维护的两个队列:
每当在未完成连接队列中创建一项,来自监听套接字的参数就复制到即将建立的队列中。连接的创建机制是完全自动的,无需服务器插手 TCP三路握手和监听套接字的两个队列
当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应三路握手的第二个分节:服务器的SYN响应,其中捎带过去对客户SYN的ACK。这一项一直保留在未完成 队列中,直到三路握手的第三个分节(客户对服务器SYN的ACK)到达或者该项超时为止。如果三路握手正常完成,该项就从未完成连接队列转移到已完成连接队列的队尾。当进程调用 accept 时,已完成队列中的队头将被返回给进程,或者改队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒 关于上述两个队列,需注意一下几点: A:listen 函数的第二个参数曾被规定为这两个队列总和的最大值 B:源自Berkeley的实现给 listen 的第二个参数一个模糊因子:把他乘以1.5得到未处理队列最大长度 C:不要把 listen 第二个参数定义为0,不同的实现对此有不同的解释 D:在三路握手正常完成前提下(没有丢失分节,没有发生重传),未完成连接队列的任何一项在其中的存留时间就是一个RTT,而RTT的值取决于特定的客户与服务器。 对于一个Web服务器,许多客户与单个服务器之间的中值RTT为187ms E:当一个客户的SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST。这么做是因为:这种情况是暂时的,客户TCP将重发SYN,期望不就就能在这些队列中找到可用空间, 要是服务器立即响应一个RST,客户的 connect 就会立即返回一个错误,强制进程处理这种情况,而不是让TCP的重传机制来处理。客户无法区分响应SYN的RST是因为该端口无服务还是 该端口有服务,但是他的队列满了。有些实现在这些队列满时的确发送了RST,由于上述原因,这种做法是不正确的。 F:在三路握手完成后,但在服务器调用 accept 前到达的数据应由服务器TCP排队,最大数据量为相应已连接套接字的接收缓冲区大小 备注:listen 的第二个参数不是支持的连接数目,而是一个与其两个队列相关的值,即使 listen 的第二个参数设为1,也不会影响服务器连接客户端数目的上限 //5. accept 函数由TCP服务器调用,用于从已完成队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程将投入睡眠(假定套接字为阻塞方式) accept 函数调用成功,那么其返回值是由内核自动生成的一个全新的套接字,代表与客户的TCP连接 accept 函数的第二个与第三个参数可以为空,代表服务器并不关心客户的身份 //6. 通常 UNIX 的 close 函数也用来关闭套接字,并终止TCP连接 close 一个TCP套接字的默认行为是将该套接字标记为已关闭的,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是说不能再作为 read 和 write 的第一个参数。 然而TCP将尝试发送已排队等待发送的数据到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列 //7. getsockname 函数用于获取与某个套接字关联的本地协议地址 getpeername 函数用于获取与某个套接字关联的外地协议地址