errno

errno不应该理解为错误原因,应该理解为调用函数失败的原因。

连接:

1.accept返回-1

accept返回-1表示获取tcp全连接队列中的连接的时候失败,会对errno进行设置:

errno == EWOULDBLOCK 表示全连接队列为空,这个在阻塞IO中见不到,在非阻塞IO中会见到

2.linux中全连接队列的大小设置

这个是在listen的时候由传入的backlog参数指定,mac系统的话backlog指定的是半连接队列和全连接队列对的总和。

3.connect返回-1

errno == EINPROGRESS 表示正在建立连接

errno == EISCONN 连接已经建立

上面的异常一般发生在非阻塞套接字,阻塞情况的话除非是服务器IP:port设置不对就根本连不上,阻塞套接字的connect的话他是一直等待TCP三次握手完成,可以进行数据交互的时候进行返回;

非阻塞的话由于connect过程中要实现TCP的三次握手,因此connect之后直接返回一般情况下返回-1,也就是正在进行连接的状态,可以检测描述符的写事件,如果可写那么说明连接建立完毕(不能检测可读,因为可读的话需要对端发送数据过来才检测的到)。

断开:

TCP是支持全双工的通讯方式,他同时也允许半连接的通讯,就是说只进行单方向的连接;

主动断开:

close(fd)

shutdown(fd, SHUT_RDWR)

shutdown(fd, SHUT_RD) -- 主动关闭本地读端,对端写端关闭

shutdown(fd, SHUT_WR) -- 主动关闭本地写端,对端读端关闭

被动断开:

read读取到0 -- 检测到本地读端关闭

write == -1 && errno == EPIPE -- 检测到本地写端关闭

消息到达

TCP的连接是在内核中完成的,即使服务器端没有进行accept,客户端也可以进行数据发送,这些数据会缓冲在内核的数据缓冲区中。

那么当内核缓冲区满了会怎么样呢?是会客户端数据发送阻塞?还是客户端依旧在发送,发送过去的数据会被服务器端丢弃?

在使用阻塞式套接字进行测试发现是客户端数据会阻塞发送,直到接收端读取数据后发送端确认对方收到后继续进行数据发送。

read返回-1

errno == EWOULDBLOCK -- 读缓冲区为空,非阻塞读取

errno == EINTR -- 被信号打断,阻塞读取

其他错误 -- 需要close掉

write返回-1

errno == EWOULDBLOCK -- 写缓冲区满了,非阻塞读取

errno == EINTR -- 被信号打断,阻塞读取

阻塞IO和非阻塞IO

阻塞与非阻塞操作取决于对应描述符是阻塞的还是非阻塞的

两者区别在于当遇到无法立即执行的情况时的应对方式,即是否阻塞;而在进行相应操作的时候,比如读取从内核数据的过程中或者写入内核用户区数据的过程中,这个过程不论文件描述符是否设置为非阻塞他都是阻塞执行的。

文件描述符默认是阻塞的,通过fcntl可以设置为非阻塞:

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

IO多路复用

就是使用一个接口检测多个描述符状态,如select、poll、epoll;在检测到满足条件的时候,就进行返回,然后用户根据返回数据对目标文件描述符进行操作。

epoll

epoll_create -- 内核中创建epoll红黑树和epoll就绪队列

epoll_ctl -- 针对红黑树进行检测事件操作,如添加、修改和删除。

epoll_wait -- 在内核检测到条件满足的时候,将满足条件的事件链接到双向链表中(内核红黑树节点数据结构增加两个指针即可),然后返回的时候将对应的数据复制到传入epoll_wait的数组中。 

epoll_event -- 这个数据中的data域如果作为指针使用,那么该指针的意义是指向用户区某片空间的指针,data数据仅用于绑定和对应文件描述符自定义的数据。

nginx使用epoll的边缘触发 -- 为了节省发送数据过程中对发送的内核控制(水平触发下会一直触发写可用);

多线程的意义:多线程的意义和目的就是为了更加高效的使用CPU,在线程A进行IO阻塞的时候切换到线程B进行运行。多核CPU的话,多线程可以提升程序的运行效率;

关于backlog

阻塞模式下,如果连接超过了backlog,那么新的连接到达的时候会被阻塞,经过一段时间返回失败 -- "Connection timed out"

关于send和write

send和recv针对的过程是用户区数据和内核区数据,send将用户区数据复制到内核区,recv是将内核区数据复制到用户区;他和实际的网络层数据收发没有关系,网络层的数据交互是由内核实现的。

延迟ACK -- 通过setsockopt可关闭

ACK会在接收到数据包之后延迟一段时间进行发送,如果在此过程中多个数据包到达,会不断重置ACK计时,超时后会根据接收到的数据包进行ack回应。

状态转换

AsyncRestTemplate 返回 Unexpected end of file from server accept返回-1原因_套接字

close的时候发送缓冲区还有数据

这种情况下与套接字的SO_LINGER属性相关,默认的情况就是连接会发送完毕发送缓冲区中的数据,最后才发送FIN包。

close_wait大量存在

伴随着close_wait状态大量存在的时候,必定伴随着对端fin_wait_2大量存在,这个的本质就是close_wait端没有及时的close掉套接字导致四次挥手卡在了该状态。可以先close掉,或者将close之前的操作通过线程处理,中止就是尽快close掉套接字描述符。

fin_wait_1状态下会进行重发,tcp_orphan_retries是对应的次数设置,超过该次数会直接close掉;

fin_wait_2状态,该状态需要对端close才能进入time_wait状态,如果对端不发,他就一直在该状态,可以通过开启tcp的keep-alive在超时时使fin_wait_2关闭。

last_ack状态会在重发多次后如果一直收不到ack,就直接进入close状态。

close之后,文件描述符资源释放,在套接字连接进入close状态之后,tcb资源才被释放(读、写缓冲在确认连接断开后同时释放)。

如果希望在time_wait阶段就使用对应的端口,可以使用端口复用进行对端口进行使用。

夹带私货:

tcp个人理解就是udp的升级版本,由于tcp是直接在内核中实现了,所有更新较慢,kcp可以理解为用户层的tcp,就是将tcp的种种特性基于udp进行用户态编程实现,从而修改所有有关可靠连接的属性设置。