网络基础知识-面试

来源:互联网 发布:js广告 编辑:程序博客网 时间:2024/06/06 02:55
常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。

主动关闭的一方在发送最后一个 ack 后,就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间,这个是TCP/IP必不可少的,也就是“解决”不了的。
主要有两个原因
1。防止上一个连接中的包,迷路后重新出现,影响新连接
  (经过2MSL,上一个连接中所有的重复包都会消失)
2。可靠的关闭TCP连接
   在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发
   fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以
   主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

场景:
当在服务器端关闭某个服务再重新启动时,它是会进入TIME_WAIT状态的。
举例:
1.客户端连接服务器的80服务,这时客户端会启用一个本地的端口访问服务器的80,访问完成后关闭此连接,立刻再次访问服务器的80,
  这时客户端会启用另一个本地的端口,而不是刚才使用的那个本地端口。原因就是刚才的那个连接还处于TIME_WAIT状态。
2.客户端连接服务器的80服务,这时服务器关闭80端口,立即再次重启80端口的服务,这时可能不会成功启动,原因也是服务器的连接
  还处于TIME_WAIT状态。

--------------------------------------------------------------------------
Q:问一下TIME_WAIT有什么问题,是闲置而且内存不回收吗?

A:是的,这样的现象实际是正常的,有时和访问量大有关,设置这两个参数:
net.ipv4.tcp_tw_reuse 是表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连接;
net.ipv4.tcp_tw_recycle 是加速TIME-WAIT sockets回收

Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大
多数时候工作正常,但是某些时候会报告"bind: address in use",于是重启失
败。

A: Andrew Gierth
server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。至于
TIME_WAIT状态,你无法避免,那是TCP协议的一部分。

Q: 如何避免等待60秒之后才能重启服务

A: Erik Max Francis
使用setsockopt,比如  
int option = 1;

if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,
sizeof( option ) ) < 0 )
{
die( "setsockopt" );
}
--------------------------------------------------------------------------

TIME_WAIT在web server中的场景:
再引用网络资源的一段话:
  1.值得一说的是,对于基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可想而知,对于访问量大的Web Server,会存在大量的TIME_WAIT状态,
  假如server一秒钟接收1000个请求,那么就会积压 240*1000=240,000个 TIME_WAIT的记录,维护这些状态给Server带来负担。当然现代操作系统都会用快速的查找算法来管理这些
  TIME_WAIT,所以对于新的TCP连接请求,判断是否hit中一个TIME_WAIT不会太费时间,但是有这么多状态要维护总是不好。  
  2.HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP连接传输多个 request/response,一个主要原因就是发现了这个问题。  

也就是说HTTP的交互跟上面画的那个图是不一样的,关闭连接的不是客户端,而是服务器,所以web服务器也是会出现大量的TIME_WAIT的情况的。
--------------------------------------------------------------------------
方法一:
解决思路很简单,就是让服务器能够快速回收和重用那些TIME_WAIT的资源。
/etc/sysctl.conf文件的修改:
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭  
net.ipv4.tcp_tw_reuse = 1  
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭  
net.ipv4.tcp_tw_recycle = 1  
修改完之后执行/sbin/sysctl -p让参数生效。

方法二:
在高并发短连接的server端,当server处理完client的请求后立刻closesocket此时会出现大量time_wait状态,导致连接不上,
用linger强制关闭可以解决此问题,但是linger可能会导致数据丢失,linger值为0时是强制关闭,无论并发多少都能正常连接上,如果非0会发生部分连接不上的情况!
(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。)
这里有一个基本原则:
设置SO_LINGER选项后,close的成功返回只是告诉我们先前发送的数据和FIN已由对端TCP确认,而不能告诉我们对端应用程序是否已成功接收数据。
但是如果我们不设置这个选项,我们连TCP是否确认了数据都不知道。

int option=0;
if ( setsockopt ( mastersocket, SOL_SOCKET, SO_LINGER, &option,
sizeof(option)) < 0)
{
    die("setsockopt");
}
--------------------------------------------------------------------------
必看:待处理错误  保活探测分节、
http://blog.csdn.net/u012062760/article/details/45173351
 默认情况下,服务器通过socket、bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着连接)的端口,
bind会失败。但是如果在socket和bind之间调用SO_REUSEADDR选项,那么bind会成功
 以下为使用SO_REUSEADDR建议:
           1、在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR选项
           2、当编写一个可在同一时刻在同一主机上运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将所参加多播组的地址作为本地IP地址捆绑

以下我们对几种close返回做一个总结:

1、 close立即返回,根本不等待

2、 close拖延到接收到对端对FIN的ACK才返回

3、 后跟一个read调用的shutdown一直等到接收对端的FIN才返回

另一种可以让客户知道服务器应用已经接受数据的方法是应用级ACK。

客户端在发送完数据后调用read读取一个字节的数据:
char ack;  
write();             //客户端写数据给服务器  
read();             //准备接收一个结束数据
而服务器在接受完数据后发送一个字节的应用级ACK:
nbytes = read();    //接收完客户端的数据  
write();            //向客户端写一个应用级ACK  
--------------------------------------------------------------------------
recv和send的阻塞和非阻塞

这里只描述同步Socket的send函数的执行流程。当调用该函数时,

(1)send先比较待发送数据的长度len和套接字s的发送缓冲区的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;

(2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,

如果协议 还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len

(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完

(4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,
而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。

这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,

(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,

(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,
那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,
所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),

recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

--------------------------------------------------------------------------
select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),
会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

  对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)
并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。
epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

  对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,
具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。