二、使用异步复用IO使用 毫秒级超时)
异步IO执行流程:
1.首先将标志位设为Non-blocking模式,准备在非阻塞模式下调用connect函数
2.调用connect,正常情况下,因为TCP三次握手需要一些时间;而非阻塞调用只要不能立即完成就会返回错误,所以这里会返回EINPROGRESS,表示在建立连接但还没有完成。
3.在读套接口描述符集(fd_set rset)和写套接口描述符集(fd_set wset)中将当前套接口置位用FD_ZERO()、FD_SET()宏),并设置好超时时间(struct timeval *timeout)
4.调用select( socket, &rset, &wset, NULL, timeout )
返回0表示connect超时,如果你设置的超时时间大于75秒就没有必要这样做了,因为内核中对connect有超时限制就是75秒。
//select 实现毫秒级超时示例:
- static void conn_select() {
- // Open TCP Socket
- m_Socket = socket(PF_INET,SOCK_STREAM,0);
- if( m_Socket < 0 )
- {
- m_connectionStatus = STATUS_CLOSED;
- return ERR_NET_SOCKET;
- }
- struct sockaddr_in addr;
- inet_aton(m_Host.c_str(), &addr.sin_addr);
- addr.sin_port = htons(m_Port);
- addr.sin_family = PF_INET;
- // Set timeout values for socket
- struct timeval timeouts;
- timeouts.tv_sec = SOCKET_TIMEOUT_SEC ; // const -> 5
- timeouts.tv_usec = SOCKET_TIMEOUT_USEC ; // const -> 0
- uint8_t optlen = sizeof(timeouts);
- if( setsockopt( m_Socket, SOL_SOCKET, SO_RCVTIMEO,&timeouts,(socklen_t)optlen) < 0 )
- {
- m_connectionStatus = STATUS_CLOSED;
- return ERR_NET_SOCKET;
- }
- // Set the Socket to TCP Nodelay ( Send immediatly after a send / write command )
- int flag_TCP_nodelay = 1;
- if ( (setsockopt( m_Socket, IPPROTO_TCP, TCP_NODELAY,
- (char *)&flag_TCP_nodelay, sizeof(flag_TCP_nodelay))) < 0)
- {
- m_connectionStatus = STATUS_CLOSED;
- return ERR_NET_SOCKET;
- }
- // Save Socket Flags
- int opts_blocking = fcntl(m_Socket, F_GETFL);
- if ( opts_blocking < 0 )
- {
- return ERR_NET_SOCKET;
- }
- //设置为非阻塞模式
- int opts_noblocking = (opts_blocking | O_NONBLOCK);
- // Set Socket to Non-Blocking
- if (fcntl(m_Socket, F_SETFL, opts_noblocking)<0)
- {
- return ERR_NET_SOCKET;
- }
- // Connect
- if ( connect(m_Socket, (struct sockaddr *)&addr, sizeof(addr)) < 0)
- {
- // EINPROGRESS always appears on Non Blocking connect
- if ( errno != EINPROGRESS )
- {
- m_connectionStatus = STATUS_CLOSED;
- return ERR_NET_SOCKET;
- }
- // Create a set of sockets for select
- fd_set socks;
- FD_ZERO(&socks);
- FD_SET(m_Socket,&socks);
- // Wait for connection or timeout
- int fdcnt = select(m_Socket+1,NULL,&socks,NULL,&timeouts);
- if ( fdcnt < 0 )
- {
- return ERR_NET_SOCKET;
- }
- else if ( fdcnt == 0 )
- {
- return ERR_TIMEOUT;
- }
- }
- //Set Socket to Blocking again
- if(fcntl(m_Socket,F_SETFL,opts_blocking)<0)
- {
- return ERR_NET_SOCKET;
- }
- m_connectionStatus = STATUS_OPEN;
- return 0;
- }
说明:在超时实现方面,不论是什么脚本语言:PHP、Python、Perl 基本底层都是C&C++的这些实现方式,需要理解这些超时处理,需要一些Linux 编程和网络编程的知识。
延伸阅读:
http://blog.sina.com.cn/s/blog_4462f8560100tvgo.html
http://blog.csdn.net/thimin/article/details/1530839
http://hi.baidu.com/xjtdy888/item/93d9daefcc1d31d1ea34c992
http://blog.csdn.net/byxdaz/article/details/5461142
http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/
http://hi.baidu.com/suyupin/item/df10004decb620e91f19bcf5
http://stackoverflow.com/questions/7092633/connect-timeout-with-alarm
http://stackoverflow.com/questions/7089128/linux-tcp-connect-with-select-fails-at-testserver?lq=1
http://cppentry.com/bencandy.php?fid=54&id=1129
总结 】
1. PHP应用层如何设置超时?
PHP在处理超时层次有很多,不同层次,需要前端包容后端超时:
浏览器客户端) -> 接入层 -> Web服务器 -> PHP -> 后端 (MySQL、Memcached)
就是说,接入层Web服务器层)的超时时间必须大于PHPPHP-FPM)中设置的超时时间,不然后面没处理完,你前面就超时关闭了,这个会很杯具。还有就是PHP的超时时间要大于PHP本身访问后端MySQL、HTTP、Memcached)的超时时间,不然结局同前面。
2. 超时设置原则是什么?
如果是希望永久不超时的代码比如上传,或者定期跑的程序),我仍然建议设置一个超时时间,比如12个小时这样的,主要是为了保证不会永久夯住一个php进程或者后端,导致无法给其他页面提供服务,最终引起所有机器雪崩。
如果是要要求快速响应的程序,建议后端超时设置短一些,比如连接500ms,读1s,写1s,这样的速度,这样能够大幅度减少应用雪崩的问题,不会让服务器负载太高。
3. 自己开发超时访问合适吗?
一般如果不是万不得已,建议用现有很多网络编程框架也好、基础库也好,里面一般都带有超时的实现,比如一些网络IO的lib库,尽量使用它们内置的,自己重复造轮子容易有bug,也不方便维护不过如是是基于学习的目的就当别论了)。
4. 其他建议
超时在所有应用里都是大问题,在开发应用的时候都要考虑到。我见过一些应用超时设置上百秒的,这种性能就委实差了,我举个例子:
比如你php-fpm开了128个php-cgi进程,然后你的超时设置的是32s,那么我们如果后端服务比较差,极端情况下,那么最多每秒能响应的请求是:
128 / 32 = 4个
你没看错,1秒只能处理4个请求,那服务也太差了!虽然我们可以把php-cgi进程开大,但是内存占用,还有进程之间切换成本也会增加,cpu呀,内存呀都会增加,服务也会不稳定。所以,尽量设置一个合理的超时值,或者督促后端提高性能。
PHP之友评论