!!!!!!!!!!!!
分类: LINUX
2010-12-16 19:57:10
网络编程常见问题总结 串讲(七):)
listen的时候的backlog有什么影响?
backlog代表连接的队列, 这里对于内核中其实会维护2个队列
未完成队列, 这个是服务器端接收到连接请求后会先放到这里(第一次握手)这个时候端口会处于SYN_RCVD状态
已完成队列,完成三次握手的连接会放到这里,这个时候才是连接建立
在我们的linux环境中backlog 一般是被定义为已完成队列的长度, 为完成队列一般是按照以完成队列长度的一半来取, backlog为5, 那么已完成队列为5,未完成队列为3, 总共是8个。 如果这里的8个都被占满了,那么后面的连接就会失败,这里的行为可以由 /proc/sys/net/ipv4/tcp_abort_on_overflow 参数控制, 这个参数打开后队列满了会发送RST包给client端,client端会看到Connection reset by peer的错误(线上部分内核打开了这个参数), 如果是关闭的话, 服务端会丢弃这次握手, 需要等待TCP的自动重连, 这个时间一般比较长, 默认情况下第一次需要3秒钟, 由于我们的连接超时一般都是很小的, client采用ullib库中的超时连接函数, 那么会发现这个时候连接超时了。
长连接和短连接混用是否会有问题?
虽然这种方式并不合适,但严格来说如果程序中做好相关的守护操作(包括一些情况下系统参数的调整) 是不会出现问 题,基本来说在长短连接混用情况下出现的问题都是由于我们的程序存在不同程度上的缺陷造成的.
可能出现的问题:
只要有一端采用了短连接,那么就可以认为总体是短连接模式。
服务端长连接, 客户端短连接
客户端主动关闭, 服务端需要接收到close的FIN包, read返回0 后才知道客户端已经被关闭。在这一段时间内其实服务端多维护了一个没有必要连接的状态。在同步模式(pendingpool,ub-xpool, ub-cpool, ub-epool)中由于read是在工作线程中,这个连接相当于线程多做了一次处理,浪费了系统资源。如果是IO异步模式(ub/apool或者使用ependingpool读回调)则可以马上发现,不需要再让工作线程进行处理
服务端如果采用普通线程模型(ub-xpool)那么在异常情况下FIN包如果没有及时到达,在这一小段时间内这个处理线程不能处理业务逻辑。如果出现问题的地方比较多这个时候可能会有连锁反应短时间内不能相应。
服务端为长连接,对于服务提供者来说可能早期测试也是采用长连接来进行测试,这个时候accept的baklog可能设置的很小,也不会出现问题。 但是一旦被大量短连接服务访问就可能出现问题。所以建议listen的时候baklog都设置为128, 我们现在的系统支持这么大的baklog没有什么问题。
每次总是客户端主动断开,这导致客户端出现了TIME_WIAT的状态,在没有设置SO_LINGER或者改变系统参数的情况下,比较容易出现客户端端口不够用的情况。
服务端短连接,客户端长连接这个时候的问题相对比较少, 但是如果客户端在发送数据前(或者收完数据后)没有对脏数据进行检查,在写的时候都会出现大量写错误或者读错误,做一次无用的操作,浪费系统资源 一般的建议是采用长连接还是短连接,两端保持一致, 但采用配置的方式并不合适,这个需要在上线的时候检查这些问题。比较好的方式是把采用长连接还是短连接放到数据包头部中。客户端发送的时候标记自己是采用短连接还是长连接,服务端接收到后按照客户端的情况采取相应的措施,并且告知客户端。特别的如果服务端不支持长连接,也可以告知客户端,服务采用了短连接
要注意的是,如果采用了一些框架或者库, 在read到0的情况下可能会多打日志,这个对性能的影响可能会比较大。
网络编程常见问题总结 串讲(八)
select, epoll使用上的注意
select, epoll实现上的区别可以参考, 本质上来说 select, poll的实现是一样的,epoll由于内部采用了树的结构来维护句柄数,并且使用了通知机制,省去了轮询的过程,在对于需要大量连接的情况下在CPU上会有一定的优势.
select默认情况下可以支持句柄数是1024, 这个可以看/usr/include/bits/typesizes.h 中的__FD_SETSIZE, 在我们的编译机(不是开发机,是SCMPF平台的机器)这个值已经被修改为51200, 如果select在处理fd超过1024的情况下出现问题可用检查一下编译程序的机器上__FD_SETSIZE是否正确.
epoll在句柄数的限制没有像select那样需要通过改变系统环境中的宏来实现对更多句柄的支持
另外我们发现有些程序在使用epoll的时候打开了边缘触发模式(EPOLLET), 采用边缘触发其实是存在风险的,在代码中需要很小心,避免由于连接两次数据到达,而被只读出一部分的数据. EPOLLET的本意是在数据情况发生变化的时候激活(比如不可读进入可读状态), 但问题是这个时候如果在一次处理完毕后不能保证fd已经进入了不可读状态(一般来说是读到EAGIN的情况), 后续可能就一直不会被激活. 一般情况下建议使用EPOLLET模式.一个最典型的问题就是监听的句柄被设置为EPOLLET, 当同时多个连接建立的时候, 我们只accept出一个连接进行处理, 这样就可能导致后来的连接不能被及时处理,要等到下一次连接才会被激活.
小提示: ullib 中常用的ul_sreado_ms_ex,ul_swriteo_ms_ex内部是采用select的机制,即使是在scmpf平台上编译出来也还是受到51200的限制,可用ul_sreado_ms_ex2,和ul_swriteo_ms_ex2这个两个接口来规避这个问题,他们内部不是采用select的方式来实现超时控制的(需要ullib 3.1.22以后版本)
一个进程的socket句柄数只能是1024吗?
答案是否定的, 一台机器上可以使用的socket句柄数是由系统参数 /proc/sys/fs/file-max 来决定的.这里的1024只不过是系统对于一个进程socket的限制,我们完全可以采用ulimit的参数把这个值增大,不过增大需要采用root权限,这个不是每个工程师都可以采用的.所以 在公司内采用了一个limit的程序,我们的所有的机器上都有预装这个程序,这个程序已经通过了提权可以以root的身份设置ulimit的结果.使用的时候 limit ./myprogram 进行启动即可, 默认是可以支持51200个句柄,采用limit -n num 可以设置实际的句柄数. 如果还需要更多的连接就需要用ulimit进行专门的操作.
另外就是对于内核中还有一个宏NR_OPEN会限制fd的做大个数,目前这个值是1024*1024
小提示: linux系统中socket句柄和文件句柄是不区分的,如果文件句柄+socket句柄的个数超过1024同样也会出问题,这个时候也需要limit提高句柄数.
ulimit对于非root权限的帐户而言只能往小的值去设置, 在终端上的设置的结果一般是针对本次shell的, 要还原退出终端重新进入就可以了。
用limit方式启动,程序读写的时候出core?
这个又是另外一个问题,前面已经提到了在网络程序中对于超时的控制是往往会采用select或者poll的方式.select的时候对于支持的FD其实是有上限的,可以看/usr/inclue/sys/select.h中对于fd_set的声明,其实一个__FD_SETSIZE/(8*sizeof(long))的long数组,在默认情况下__FD_SETSIZE的定义是1024,这个可以看 /usr/include/bits/typesizes.h 中的声明,如果这个时候这个宏还是1024,那么对于采用select方式实现的读写超时控制程序在处理超过1024个句柄的时候就会导致内存越界出core .我们的程序如果是线下编译,由于许多开发机和测试这个参数都没有修改,这个时候就会造成出core,其实不一定出core甚至有些情况下会出现有数据但还是超时的情况. 但对于我们的SCMPF平台上编译出来的程序是正常的,SCMPF平台上这个参数已经进行了修改,所以有时会出现QA测试没问题,RD自测有问题的情况。
一台机器最多可以建立多少连接?
理论上来说这个是可以非常多的,取决于可以使用多少的内存.我们的系统一般采用一个四元组来表示一个唯一的连接{客户端ip, 客户端端口, 服务端ip, 服务端端口} (有些地方算上TCP, UDP表示成5元组), 在网络连接中对于服务端采用的一般是bind一个固定的端口, 然后监听这个端口,在有连接建立的时候进行accept操作,这个时候所有建立的连接都只用到服务端的一个端口.对于一个唯一的连接在服务端ip和 服务端端口都确定的情况下,同一个ip上的客户端如果要建立一个连接就需要分别采用不同的端,一台机器上的端口是有限,最多65535(一个unsigned char)个,在系统文件/proc/sys/net/ipv4/ip_local_port_range 中我们一般可以看到32768 61000 的结果,这里表示这台机器可以使用的端口范围是32768到61000, 也就是说事实上对于客户端机器而言可以使用的连接数还不足3W个,当然我们可以调整这个数值把可用端口数增加到6W. 但是这个时候对于服务端的程序完全不受这个限制因为它都是用一个端口,这个时候服务端受到是连接句柄数的限制,在上面对于句柄数的说明已经介绍过了,一个进程可以建立的句柄数是由/proc/sys/fs/file-max决定上限和ulimit来控制的.所以这个时候服务端完全可以建立更多的连接,这个时候的主要问题在于如何维护和管理这么多的连接,经典的一个连接对应一个线程的处理方式这个时候已经不适用了,需要考虑采用一些异步处理的方式来解决, 毕竟线程数的影响放在那边
小提示: 一般的服务模式都是服务端一个端口,客户端使用不同的端口进行连接,但是其实我们也是可以把这个过程倒过来,我们客户端只用一个端但是服务端确是不同的端口,客户端做下面的修改原有的方式 socket分配句柄-> connect 分配的句柄 改为 socket分配句柄 ->对socket设置SO_REUSEADDR选项->像服务端一样bind某个端口->connect 就可以实现 .
不过这种应用相对比较少,对于像网络爬虫这种情况可能相对会比较适用,只不过6w连接已经够多了,继续增加的意义不一定那么大就是了.
对于一个不存在的ip建立连接是超时还是马上返回?
这个要根据情况来看, 一般情况connect一个不存在的ip地址,发起连接的服务需要等待ack的返回,由于ip地址不存在,不会有返回,这个时候会一直等到超时才返回。如果连接的是一个存在的ip,但是相应的端口没有服务,这个时候会马上得到返回,收到一个ECONNREFUSED(Connection refused)的结果。
但是在我们的网络会存在一些有限制的路由器,比如我们一些机器不允许访问外网,这个时候如果访问的ip是一个外网ip(无论是否存在),这个时候也会马上返回得到一个Network is unreachable的错误,不需要等待。