面试:杂记(三)

1. SO_REUSEPORT和SO_REUSEADDR

     SO_REUSEADDR用于绑定不同的本地地址和相同的端口到套接字上。也就是说设置了SO_REUSEADDR之后,不同的进程可以通过不同的本地地址,创建同一个端口的套接字。

     SO_REUSEPORT则是可以多个进程bind到相同的IP和端口。相对与SO_REUSEADDR,它能让IP地址也是一样的。这样子每个进程就可以自己创建socket、bind、listen、accept相同的地址和端口。当有新连接建立时,内核只会唤醒一个进程来accept,并保证唤醒的均衡性。

2. connect函数

    用于客户端向服务器发起连接时调用的函数,若底层是使用TCP协议,则此时会触发三次握手的协议。不管是阻塞还是非阻塞,如果连接成功都是返回0。

    阻塞情况下,根据连接失败的情况返回对应的错误码,有ETIMEOUT、ECONNREFUSED、EHOSTUNREACH、ENETUNREACH。错误原因根据错误码就可以知道了,这里ETIMEOUT是指连接超时,一般是75s,当然不同系统有不同的实现。

    非阻塞情况下,返回错误EINPROGRESS表示socket此时正在连接。可以通过select和epoll来监听连接是否成功。将描述符放到epoll中,如果连接的socket端口没有监听或者不可达(就是连接失败)会触发EPOLLERR事件,连接成功则会触发EPOLLIN事件。将描述符放到select中,连接失败和连接成功都会导致描述符变为可读可写,此时可以调用getsockopt函数及其SO_ERROR套接字选项来判断是否连接成功,如果连接成功则该函数第四个参数会返回0,否则返回对应的错误码。

3. epoll与select的区别(简述)

    epoll只会在注册的时候才把描述符拷贝进内核,即每个描述符只拷贝一次,可读可写则通过回调函数处理。而select和poll则是每次轮询都需要拷贝所有描述符到内核中。而拷贝进内核又会涉及到用户态与内核态的切换,可见epoll的效率比select要高很多。

    select限制了描述符的数量为1024,这个是系统的设置,没法修改的。而epoll则不会有这个限制。

    select底层是用poll实现的。epoll底层由红黑树和一个就绪列表实现,注册的时候将描述符挂载到红黑树上,当描述符可读或可写时则会将其赋值到就绪列表上,用户只需要读取就绪列表就可以得到所有可读可写的描述符。

   epoll的触发方式分为水平触发level-triggered lT与边缘触发edge-triggered ET(类似电路里的触发方式)。对水平触发,只要就绪列表中的数据没有被读完,内核就会一直通知用户读取。而对于边缘触发,内核只通知一次,如果数据没有被读完,也不会重复通知。

4. TIME_WAIT状态为什么需要2MSL

    TIME_WAIT状态用于确保四次挥手正常结束。

    MSL即Maximum Segment Lifetime,是报文在网络中的存活时间。TIME_WAIT状态是主动FIN方收到对端的FIN报文发送ACK后进入的状态。2MSL是一个发送和一个回复的最大时间,如果对端没有收到这个ACK,则在2MSL内必然会收到对方重发的FIN报文。如果2MSL内都没有收到对端的FIN报文,则说明对端已经收到了ACK报文,就可以结束TCP连接了。

5. send()与recv()

    send()与recv()没有固定的对应关系,不定数目的send()可以触发不定数目的recv(),因为存在发送缓冲区与接收缓冲区。

    send()只是把应用层的数据拷贝到内核发送缓冲区,并不保证数据一定会被发送到对端,真正执行发送及什么时候发送都是由系统(协议栈)决定的。

    在非阻塞模式下send()操作会立即返回:如果发送缓冲区可用大小为0,则会立即返回EWOULDBLOCK错误,表示无法拷贝任何数据到发送缓冲区;如果发送缓冲区可用大小不为0,但小于发送数据的长度,则拷贝可用大小的数据到缓冲区;由此可知,非阻塞的send()总是尽自己最大能力往发送缓冲区拷贝尽可能多的数据,所以存在非阻塞send()返回的大小比发送数据的长度要小的情况。

    recv()读到数据的话则返回实际读取数据的长度。

    在recv()中会检测发送缓冲区是否有数据需要发送,有的话则先发送数据,然后再看接受缓冲区有没有数据。

你可能感兴趣的:(面试)