本文主要是介绍网络编程相关函数深层次解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
connect函数解析
TCP客户用connect函数来建立与TCP服务器的连接:
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
//成功返回0,失败返回-1并设置errno
客户端在调用connect之前不必非得调用bind函数,因为如果需要的话内核会确定源IP地址并选择一个临时端口号作为端口;
如果是TCP套接字,调用connect函数将触发TCP的三次握手过程,而且仅在连接建立成功或出错才返回:
若TCP没有收到SYN分节的响应,则返回ETIMEOUT错误。举例,调用connect函数时,4.4BSD内核发送一个SYN,若无响应则等待6s后再发送一个,若仍无响应则等待24s后再发送一个。若总共等待了75s后仍未收到响应则返回本错误。
若对客户的SYN的相应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。这是一种硬错误,客户一收到RST就马上返回ECONNERFUSED错误;
若客户发出的SYN在中间某个路由器上引发一个“destination unreachable”(目的地不可达)ICMP错误,则认为是一个软错误。客户主机内核会保存该消息,并按第一种情况所述的时间间隔继续发送SYN,若在某个规定时间内仍无响应,则把保存的信息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程,以下两种情况是可能的:
- 按照本地系统的转发表,根本没有到达远程系统的路径;
- connect调用根本不等待就返回;
按照TCP状态转移图,connect函数导致当前套接字从CLOSED状态转移到SYN_SENT状态,若成功,则转移到ESTABLISHED状态。若connect失败,则该套接字不再可用,必须关闭,不能对这样的套接字再次调用connect函数;
listen函数解析
listen函数仅由TCP服务器调用,其做两件事:
当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态;
listen函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数:
#include<sys/socket.h>int listen(int sockfd, int backlog);
为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:
- 未完成队列:每个这样的YSN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手,这些套接字处于SYN_RECV状态;
- 已完成连接队列:每个已完成的TCP三路握手过程的客户对应其中的一项,这些套接字处于ESTABLISHED状态;
每当在未完成连接队列中创建一项时,来自监听套接字的参数就复制到即将建立的连接中,连接的创建机制是完全自动的。
当来自客户的SYN到达时,TCP在未完成队列中创建一个新项,然后响应三路握手的第二个分节:服务器的SYN相应,其中捎带对可读SYN的ACK,这一项一直保留在文玩城连接队列中,直到三路握手的第三个分节(客户对服务器的SYN的ACK)到达,或者该项超时为止;
如果三路握手成功,该项从未完成队列移到已完成队列的队尾,当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者该队列为空,那么进程就被投入到睡眠,知道TCP在该队列中放入一项才唤醒它;
accept函数解析
函数原型为:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
如果已连接队列中没有等待的连接,套接字也没有被标记为non-blocking,accept()会阻塞调用函数直到连接出现,如果套接字被标记为non-blocking,队列中也没有等待的连接,accept()返回错误EAGAIN 或 EWOULDBLOCK。
一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包:
- 若有,把数据拷贝出来,删除接收到的数据报,创建新的socket与客户发来的地址建立连接;
- 若没有,就阻塞等待;
考虑以下情况:如果监听队列中处于ESTABLISHED状态的连接对应的客户端出现网络异常(比如掉线),或者提前退出,那么服务器端对这个连接执行的accept调用是否成功?
accept只是从监听队列中取出连接,而不论连接处于何种状态(如上面的ESTABLISHED或者CLOSE_WAIT状态),更不关心任何网络状况的变化;
close关闭连接
函数原型是:int close(int fd);
fd参数是待关闭的连接,不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用计数为0时,才真正关闭连接。
如果无论如何都要立即终止连接(而不是将socket引用计数减1),可以使用shutdown系统调用:
#include<sys/socket.h>
int shutdown(int sockfd, int howto);
howto参数决定了shutdown的行为:
- SHUT_RD:关闭sockfd上读的一半,应用程序不会再针对socket文件描述符执行读操作,并且将socket接收缓冲区的数据都丢弃;
- SHUT_WR:关闭sockfd上写的一半,sockfd的发送缓冲区的数据会在真正关闭连接之前全部发送出去,应用程序不再对该sockfd执行写操作,连接处于半关闭状态;
- SHUT_RDWR:同时关闭sockfd上的读和写;
带外标记
Linux内核检测到TCP紧急标志时,将通知应用程序由带外数据需要接收,内核通知应用程序带外数据到达有两种常见方式:
- I/O复用产生的异常;
- SIGURG信号;
但是,即使应用程序得知了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能正确接收带外数据,可通过如下系统调用实现:
#include<sys/socket.h>
int sockatmark(int sockfd);
其判断sockfd是否处于带外标记,即下一个要被读取的数据是否是带外数据,如果是,sockatmark返回1,可以利用带MSG_OOB标志的recv调用来接收带外数据。否则,返回0;
socket选项
#include<sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);
对服务器而言,有部分socket选项只能在调用listen系统调用前针对监听socket设置才有效,对监听socket设置这些选项,那么accept返回的连接socket将自动继承这些选项;
socket选项的SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
SO_RCVBUF 和 SO_SNDBUF选项分别表示TCP缓冲区和发送缓冲区的大小,当我们用setsockopt设置TCP接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不得小于某个最小值。TCP接收缓冲区最小值为256字节,发送缓冲区的最小值为2048字节,这样做确保一个TCP连接有足够的空闲缓冲区来处理拥塞;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf));setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf));
这篇关于网络编程相关函数深层次解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!