【 概述 】
在PHP开发中工作里非常多使用到超时处理到超时的场合,我说几个场景:
1. 异步获取数据如果某个后端数据源获取不成功则跳过,不影响整个页面展现
2. 为了保证Web服务器不会因为当个页面处理性能差而导致无法访问其他页面,则会对某些页面操作设置
3. 对于某些上传或者不确定处理时间的场合,则需要对整个流程中所有超时设置为无限,否则任何一个环节设置不当,都会导致莫名执行中断
4. 多个后端模块(MySQL、Memcached、HTTP接口),为了防止单个接口性能太差,导致整个前面获取数据太缓慢,影响页面打开速度,引起雪崩
5. 很多需要超时的场合
这些地方都需要考虑超时的设定,但是PHP中的超时都是分门别类,各个处理方式和策略都不同,为了系统的描述,我总结了PHP中常用的超时处理的总结。
【Web服务器超时处理】
[ Apache ]
一般在性能很高的情况下,缺省所有超时配置都是30秒,但是在上传文件,或者网络速度很慢的情况下,那么可能触发超时操作。
目前 apache fastcgi php-fpm 模式 下有三个超时设置:
fastcgi 超时设置:
修改 httpd.conf 的fastcgi连接配置,类似如下:
FastCgiExternalServer /home/forum/apache/apache_php/cgi-bin/php-cgi -socket /home/forum/php5/etc/php-fpm.sock
ScriptAlias /fcgi-bin/ "/home/forum/apache/apache_php/cgi-bin/"
AddHandler php-fastcgi .php
Action php-fastcgi /fcgi-bin/php-cgi
AddType application/x-httpd-php .php
缺省配置是 30s,如果需要定制自己的配置,需要修改配置,比如修改为100秒:(修改后重启 apache):
<IfModule mod_fastcgi.c>
FastCgiExternalServer /home/forum/apache/apache_php/cgi-bin/php-cgi -socket /home/forum/php5/etc/php-fpm.sock -idle-timeout 100
ScriptAlias /fcgi-bin/ "/home/forum/apache/apache_php/cgi-bin/"
AddHandler php-fastcgi .php
Action php-fastcgi /fcgi-bin/php-cgi
AddType application/x-httpd-php .php
</IfModule>
如果超时会返回500错误,断开跟后端php服务的连接,同时记录一条apache错误日志:
1
2
[Thu Jan 27 18:30:15 2011] [error] [client 10.81.41.110] FastCGI: comm with server "/home/forum/apache/apache_php/cgi-bin/php-cgi" aborted: idle timeout (30 sec)
[Thu Jan 27 18:30:15 2011] [error] [client 10.81.41.110] FastCGI: incomplete headers (0 bytes) received from server "/home/forum/apache/apache_php/cgi-bin/php-cgi"
其他 fastcgi 配置参数说明:
IdleTimeout 发呆时限
ProcessLifeTime 一个进程的最长生命周期,过期之后无条件kill
MaxProcessCount 最大进程个数
DefaultMinClassProcessCount 每个程序启动的最小进程个数
DefaultMaxClassProcessCount 每个程序启动的最大进程个数
IPCConnectTimeout 程序响应超时时间
IPCCommTimeout 与程序通讯的最长时间,上面的错误有可能就是这个值设置过小造成的
MaxRequestsPerProcess 每个进程最多完成处理个数,达成后自杀
[ Lighttpd ]
配置:lighttpd.conf
Lighttpd配置中,关于超时的参数有如下几个(篇幅考虑,只写读超时,写超时参数同理):
主要涉及选项:
server.max-keep-alive-idle = 5
server.max-read-idle = 60
server.read-timeout = 0
server.max-connection-idle = 360
--------------------------------------------------
# 每次keep-alive 的最大请求数, 默认值是16
server.max-keep-alive-requests = 100
# keep-alive的最长等待时间, 单位是秒,默认值是5
server.max-keep-alive-idle = 1200
# lighttpd的work子进程数,默认值是0,单进程运行
server.max-worker = 2
# 限制用户在发送请求的过程中,最大的中间停顿时间(单位是秒),
# 如果用户在发送请求的过程中(没发完请求),中间停顿的时间太长,lighttpd会主动断开连接
# 默认值是60(秒)
server.max-read-idle = 1200
# 限制用户在接收应答的过程中,最大的中间停顿时间(单位是秒),
# If the user pauses for too long while receiving a response (not finished), lighttpd will actively disconnect
# The default value is 360 (seconds)
server.max-write-idle = 12000
# Timeout limit for reading client requests, unit is seconds, set to 0 to indicate no limit
# When the setting is less than max-read-idle, read-timeout takes effect
server.read-timeout = 0
# The timeout limit for writing the response page to the client, the unit is seconds, set to 0 to indicate no limit
# When the setting is less than max-write-idle, write-timeout takes effect
server.write-timeout = 0
# The upper limit of request processing time. If mod_proxy_core is used, it is the interaction time limit with the backend. The unit is seconds
server.max-connection-idle = 1200
--------------------------------------------------
Description:
For consecutive requests on a keep-alive connection, the maximum interval for sending the first request content is determined by the parameter max-read-idle. From the second request onwards, the maximum interval for sending the request content is determined by the parameter max-keep-alive- idle decision. The timeout between requests is also determined by max-keep-alive-idle. The total timeout for sending request content is determined by the parameter read-timeout. The timeout for Lighttpd to interact with the backend is determined by max-connection-idle.
Extended reading:
http://www.snooda.com/read/244
[ Nginx ]
Configuration: nginx.conf
http {
#Fastcgi: (valid for backend fastcgi, fastcgi does not belong to proxy mode)
Fastcgi_connect_timeout 5; #Connection timeout
Fastcgi_send_timeout 10; #Write timeout
Fastcgi_read_timeout 10; #Read timeout
#Proxy: (valid for proxy/upstreams)
proxy_connect_timeout 15s; #Connection timeout
proxy_read_timeout 24s; #Read timeout
proxy_send_timeout 10s; #Write timeout
}
Description:
Nginx's timeout settings are very clear and easy to understand. The above timeouts are for different working modes, but there are many problems caused by timeouts.
Extended reading:
http://hi.baidu.com/pibuchou/blog/item/a1e330dd71fb8a5995ee3753.html
http://hi.baidu.com/pibuchou/blog/item/7cbccff0a3b77dc60b46e024.html
http://hi.baidu.com/pibuchou/blog/item/10a549818f7e4c9df703a626.html
http://www.apoyl.com/?p=466
[PHP itself timeout processing]
[PHP-fpm]
Configuration: php-fpm.conf
//...
Sets the limit on the number of simultaneous requests that will be served.
Equivalent to Apache MaxClients directive.
Equivalent to PHP_FCGI_CHILDREN environment in original php.fcgi
Used with any pm_style.
#php-cgi number of processes
The timeout (in seconds) for serving a single request after which the worker process will be terminated
Should be used when 'max_execution_time' ini option does not stop script execution for some reason
'0s' means 'off'
#php-fpm request execution timeout, 0s means never timeout, otherwise set an Ns as the number of seconds for timeout
The timeout (in seconds) for serving of single request after which a php backtrace will be dumped to slow.log file
'0s' means 'off'
Description:
In php.ini, there is a parameter max_execution_time that can set the maximum execution time of PHP scripts. However, in php-cgi (php-fpm), this parameter will not take effect. Really able to control the maximum execution time of PHP scripts:
1
That is to say, if you run max_execution_time in mod_php5.so mode, it will take effect, but if you run it in php-fpm mode, it will not take effect.
Extended reading:
http://blog.s135.com/file_get_contents/
[PHP]
Configuration: php.ini
Options:
max_execution_time = 30
Or set it in code:
ini_set(“max_execution_time”, 30);
set_time_limit(30);
Description:
It takes effect on the current session. For example, setting 0 will never time out, but if PHP's safe_mode is turned on, these settings will not take effect.
The effect is the same, but you need to refer to the php-fpm part for the specific content. If request_terminate_timeout is set in php-fpm, then max_execution_time will not take effect.
[Backend & interface access timeout]
【HTTP access】
Generally, we access HTTP in many ways, mainly: curl, socket, file_get_contents() and other methods.
If the other party's server never responds, we will be in tragedy. It is easy to kill the entire server, so we also need to consider the timeout issue when accessing http.
[CURL access HTTP]
CURL is a commonly used and reliable lib library for accessing the HTTP protocol interface. It has high performance and has some concurrency support functions.
CURL:
curl_setopt($ch, opt) can set some timeout settings, mainly including:
*(Important) CURLOPT_TIMEOUT sets the maximum number of seconds cURL is allowed to execute.
*(Important) CURLOPT_TIMEOUT_MS sets the maximum number of milliseconds that cURL is allowed to execute. (Added in cURL 7.16.2. Available from PHP 5.2.3 onwards. )
CURLOPT_CONNECTTIMEOUT The time to wait before initiating a connection. If set to 0, it will wait indefinitely.
CURLOPT_CONNECTTIMEOUT_MS The time, in milliseconds, to wait for a connection attempt. If set to 0, wait infinitely. Added in cURL 7.16.2. Available starting with PHP 5.2.3.
CURLOPT_DNS_CACHE_TIMEOUT sets the time to save DNS information in memory, the default is 120 seconds.
Curl ordinary second-level timeout:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 60); //Just set the number of seconds
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, $defined_vars['HTTP_USER_AGENT']);
Curl normal second-level timeout use:
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
If curl needs to perform millisecond timeout, you need to add:
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
Or:
curl_setopt ($ch, CURLOPT_NOSIGNAL, true); can support millisecond-level timeout settings
An example of curl's millisecond timeout:
if (!isset($_GET['foo'])) {
// Client
$ch = curl_init('http://example.com/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
CURL_SETOPT ($ CH, CURLOPT_NOSIGNAL, 1); // Note that you must set this
when the millisecond time goes off
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200); //Timeout in milliseconds, added in cURL 7.16.2. Available from PHP 5.2.3
$data = curl_exec($ch);
$curl_errno = curl_errno($ch);
$curl_error = curl_error($ch);
curl_close($ch);
If ($curl_errno > 0) {
echo "cURL Error ($curl_errno): $curl_errorn";
} else {
echo "Data received: $datan";
}
} else {
// Server
sleep(10);
echo "Done.";
}
?>
Some other tips:
1. According to the experience summary: cURL version >= libcurl/7.21.0 version, the millisecond timeout will definitely take effect, remember.
2. There is also a problem with the millisecond timeout of curl_multi. . A single access supports ms-level timeout, and curl_multi will be inaccurate if multiple calls are made in parallel
[Access HTTP via streaming]
In addition to curl, we often use fsockopen or file operation functions to process the HTTP protocol, so our timeout processing is also necessary.
Generally, the connection timeout can be set directly, but the stream read timeout needs to be handled separately.
Write your own code to handle it:
$tmCurrent = gettimeofday();
$intUSGone = ($tmCurrent['sec'] - $tmStart['sec']) * 1000000
+ ($tmCurrent['usec'] - $tmStart['usec']);
if ($intUSGone > $this->_intReadTimeoutUS) {
return false;
}
Or use the built-in stream processing functions stream_set_timeout() and stream_get_meta_data() to process:
// Timeout in seconds
$timeout = 5;
$fp = fsockopen("example.com", 80, $errno, $errstr, $timeout);
if ($fp) {
fwrite($fp, "GET / HTTP/1.0rn");
fwrite($fp, "Host: example.comrn");
fwrite($fp, "Connection: Closernrn");
stream_set_blocking($fp, true); //重要,设置为非阻塞模式
stream_set_timeout($fp,$timeout); //设置超时
$info = stream_get_meta_data($fp);
while ((!feof($fp)) && (!$info['timed_out'])) {
$data .= fgets($fp, 4096);
$info = stream_get_meta_data($fp);
ob_flush;
flush();
}
if ($info['timed_out']) {
echo "Connection Timed Out!";
} else {
echo $data;
}
}
file_get_contents 超时:
$timeout = array(
'http' => array(
'timeout' => 5 //设置一个超时时间,单位为秒
)
);
$ctx = stream_context_create($timeout);
$text = file_get_contents("http://example.com/", 0, $ctx);
?>
fopen 超时:
$timeout = array(
'http' => array(
'timeout' => 5 //设置一个超时时间,单位为秒
)
);
$ctx = stream_context_create($timeout);
if ($fp = fopen("http://example.com/", "r", false, $ctx)) {
while( $c = fread($fp, 8192)) {
echo $c;
}
fclose($fp);
}
?>
【MySQL】
php中的mysql客户端都没有设置超时的选项,mysqli和mysql都没有,但是libmysql是提供超时选项的,只是我们在php中隐藏了而已。
那么如何在PHP中使用这个操作捏,就需要我们自己定义一些MySQL操作常量,主要涉及的常量有:
MYSQL_OPT_READ_TIMEOUT=11;
MYSQL_OPT_WRITE_TIMEOUT=12;
这两个,定义以后,可以使用 options 设置相应的值。
不过有个注意点,mysql内部实现:
1. 超时设置单位为秒,最少配置1秒
2. 但mysql底层的read会重试两次,所以实际会是 3 秒
重试两次 + 自身一次 = 3倍超时时间,那么就是说最少超时时间是3秒,不会低于这个值,对于大部分应用来说可以接受,但是对于小部分应用需要优化。
查看一个设置访问mysql超时的php实例:
//自己定义读写超时常量
if (!defined('MYSQL_OPT_READ_TIMEOUT')) {
define('MYSQL_OPT_READ_TIMEOUT', 11);
}
if (!defined('MYSQL_OPT_WRITE_TIMEOUT')) {
define('MYSQL_OPT_WRITE_TIMEOUT', 12);
}
//设置超时
$mysqli = mysqli_init();
$mysqli->options(MYSQL_OPT_READ_TIMEOUT, 3);
$mysqli->options(MYSQL_OPT_WRITE_TIMEOUT, 1);
//连接数据库
$mysqli->real_connect("localhost", "root", "root", "test");
if (mysqli_connect_errno()) {
printf("Connect failed: %s/n", mysqli_connect_error());
exit();
}
//执行查询 sleep 1秒不超时
printf("Host information: %s/n", $mysqli->host_info);
if (!($res=$mysqli->query('select sleep(1)'))) {
echo "query1 error: ". $mysqli->error ."/n";
} else {
echo "Query1: query success/n";
}
//Execute query and sleep will time out after 9 seconds
if (!($res=$mysqli->query('select sleep(9)'))) {
echo "query2 error: ". $mysqli->error ."/n";
} else {
echo "Query2: query success/n";
}
$mysqli->close();
echo "close mysql connection/n";
?>
Extended reading:
http://blog.csdn.net/heiyeshuwu/article/details/5869813
【Memcached】
[PHP extension]
php_memcache client:
Connection timeout: bool Memcache::connect ( string $host [, int $port [, int $timeout ]] )
When getting and setting, there is no clear timeout setting parameter.
libmemcached client: There is no obvious timeout parameter in the php interface.
Note: Therefore, there are many problems when accessing Memcached in PHP. You need to hack some operations by yourself, or refer to online patches.
[C&C++ access Memcached]
Client: libmemcached client
Note: The memcache timeout configuration can be configured smaller, for example, 5 or 10 milliseconds is enough. If it exceeds this time, it is better to query from the database.
The following is a C++ example of timeout for connecting and reading set data:
//Create connection timeout (connect to Memcached)
memcached_st* MemCacheProxy::_create_handle()
{
memcached_st * mmc = NULL;
memcached_return_t prc;
If (_mpool != NULL) { // get from pool
mmc = memcached_pool_pop(_mpool, false, &prc);
if (mmc == NULL) {
__LOG_WARNING__("MemCacheProxy", "get handle from pool error [%d]", (int)prc);
}
return mmc;
}
memcached_st* handle = memcached_create(NULL);
If (handle == NULL){
__LOG_WARNING__("MemCacheProxy", "create_handle error");
return NULL;
}
//Set connection/read timeout
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_HASH, MEMCACHED_HASH_DEFAULT);
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_NO_BLOCK, _noblock); //Set the parameter MEMCACHED_BEHAVIOR_NO_BLOCK to 1 to make the timeout configuration take effect. If the timeout is not set, it will not take effect. It will be tragic at critical times and can easily cause an avalanche
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, _connect_timeout); //Connection timeout
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, _read_timeout); memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, _read_timeout);
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_SND_TIMEOUT, _send_timeout); memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_SND_TIMEOUT, _send_timeout);
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, _poll_timeout);
// Set consistent hash
memcached_behavior_set_distribution(handle, MEMCACHED_DISTRIBUTION_CONSISTENT);
memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_DISTRIBUTION, MEMCACHED_DISTRIBUTION_CONSISTENT);
memcached_return rc;
for (uint i = 0; i < _server_count; i++){
rc = memcached_server_add(handle, _ips[i], _ports[i]);
If (MEMCACHED_SUCCESS != rc) {
__LOG_WARNING__("MemCacheProxy", "add server [%s:%d] failed.", _ips[i], _ports[i]);
}
}
_mpool = memcached_pool_create(handle, _min_connect, _max_connect);
If (_mpool == NULL){
__LOG_WARNING__("MemCacheProxy", "create_pool error");
return NULL;
}
mmc = memcached_pool_pop(_mpool, false, &prc);
if (mmc == NULL) {
__LOG_WARNING__("MyMemCacheProxy", "get handle from pool error [%d]", (int)prc);
}
//__LOG_DEBUG__("MemCacheProxy", "get handle [%p]", handle);
return mmc;
}
//设置一个key超时(set一个数据到memcached)
bool MemCacheProxy::_add(memcached_st* handle, unsigned int* key, const char* value, int len, unsigned int timeout)
{
memcached_return rc;
char tmp[1024];
snprintf(tmp, sizeof (tmp), "%u#%u", key[0], key[1]);
//有个timeout值
rc = memcached_set(handle, tmp, strlen(tmp), (char*)value, len, timeout, 0);
if (MEMCACHED_SUCCESS != rc){
return false;
}
return true;
}
//Memcache读取数据超时 (没有设置)
libmemcahed 源码中接口定义:
LIBMEMCACHED_API char *memcached_get(memcached_st *ptr,const char *key, size_t key_length,size_t *value_length,uint32_t *flags,memcached_return_t *error);
LIBMEMCACHED_API memcached_return_t memcached_mget(memcached_st *ptr,const char * const *keys,const size_t *key_length,size_t number_of_keys);
从接口中可以看出在读取数据的时候,是没有超时设置的。
延伸阅读:
http://hi.baidu.com/chinauser/item/b30af90b23335dde73e67608
http://libmemcached.org/libMemcached.html
【如何实现超时】
程序中需要有超时这种功能,比如你单独访问一个后端Socket模块,Socket模块不属于我们上面描述的任何一种的时候,它的协议也是私有的,那么这个时候可能需要自己去实现一些超时处理策略,这个时候就需要一些处理代码了。
[PHP中超时实现]
一、初级:最简单的超时实现 (秒级超时)
思路很简单:链接一个后端,然后设置为非阻塞模式,如果没有连接上就一直循环,判断当前时间和超时时间之间的差异。
php socket 中实现原始的超时:(每次循环都当前时间去减,性能会很差,cpu占用会较高)
$host = "127.0.0.1";
$port = "80";
$timeout = 15; //timeout in seconds
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
or die("Unable to create socketn");
socket_set_nonblock($socket) //务必设置为阻塞模式
or die("Unable to set nonblock on socketn");
$time = time();
//循环的时候每次都减去相应值
while (!@socket_connect($socket, $host, $port)) //如果没有连接上就一直死循环
{
$err = socket_last_error($socket);
if ($err == 115 || $err == 114)
{
if ((time() - $time) >= $timeout) //每次都需要去判断一下是否超时了
{
socket_close($socket);
die("Connection timed out.n");
}
sleep(1);
continue;
}
die(socket_strerror($err) . "n");
}
Socket_set_block($this->socket) //Restore blocking mode
or die("Unable to set block on socketn");
?>
2. Upgrade: Use PHP’s own asynchronous IO to implement (millisecond timeout)
Description:
Asynchronous IO: The concept of asynchronous IO is relative to synchronous IO. When an asynchronous procedure call is issued, the caller does not get the result immediately. The component that actually handles the call notifies the caller through status, notifications, and callbacks when it is complete. Asynchronous IO transfers bits into small groups, which can be 8 bits, 1 character or longer. The sender can send these groups of bits at any time, and the receiver never knows when they will arrive.
Multiplexing: The multiplexing model detects multiple IO operations and returns an operable collection so that they can be operated on. This avoids the determination that blocking IO cannot handle each IO at any time and non-blocking occupancy of system resources.
Use socket_select() to implement timeouts
socket_select(…, floor($timeout), ceil($timeout*1000000));
Features of select: Ability to set timeout to microsecond level!
Use the timeout code of socket_select() (you need to know some knowledge of asynchronous IO programming to understand)
### Calling class ####
$server = new Server;
$client = new Client;
for (;;) {
foreach ($select->can_read(0) as $socket) {
If ($socket == $client->socket) {
// New Client Socket
$select->add(socket_accept($client->socket));
}
else {
//there's something to read on $socket
}
}
}
?>
### Asynchronous multiplexed IO & timeout connection processing class ###
class select {
var $sockets;
function select($sockets) {
$this->sockets = array();
foreach ($sockets as $socket) {
$this->add($socket);
}
}
function add($add_socket) {
Array_push($this->sockets,$add_socket);
}
function remove($remove_socket) {
$sockets = array();
foreach ($this->sockets as $socket) {
If($remove_socket != $socket)
$sockets[] = $socket;
}
$this->sockets = $sockets;
}
function can_read($timeout) {
$read = $this->sockets;
socket_select($read,$write = NULL,$except = NULL,$timeout);
Return $read;
}
function can_write($timeout) {
$write = $this->sockets;
socket_select($read = NULL,$write,$except = NULL,$timeout);
Return $write;
}
} ?>
[Timeout implementation in C&C++]
Generally in Linux C/C++, you can use alarm() to set a timer to achieve a second-level timeout, or asynchronous multiplexed IO such as select(), poll(), epoll(), etc. to achieve a millisecond-level timeout. You can also use secondary encapsulated asynchronous io libraries (libevent, libev) to achieve this.
1. Use signals in alarm to implement timeout (second-level timeout)
Note: The Linux kernel connect timeout is usually 75 seconds. We can set a smaller time such as 10 seconds to return from connect early. Here we use the signal processing mechanism, call alarm, and generate the SIGALRM signal after timeout (can also be implemented using select)
Use alarym to implement connect setting timeout code example in seconds:
//Signal processing function
static void connect_alarm(int signo)
{
debug_printf("SignalHandler");
Return;
}
//Alarm timeout connection implementation
static void conn_alarm()
{
Sigfunc * sigfunc; //Existing signal processing function
sigfunc=signal(SIGALRM, connect_alarm); //Create the signal processing function connect_alarm, (if any) save the existing signal processing function
int timeout = 5;
//Set alarm
if( alarm(timeout)!=0 ){
//... The alarm has been set and processed
}
//Perform connection operation
If (connect(m_Socket, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
If (errno == EINTR) { //If the error number is set to EINTR, it means a timeout interrupt
debug_printf("Timeout");
m_connectionStatus = STATUS_CLOSED;
errno = ETIMEDOUT; //Prevent the three-way handshake from continuing
return ERR_TIMEOUT;
}
else {
debug_printf("Other Err");
m_connectionStatus = STATUS_CLOSED;
return ERR_NET_SOCKET;
}
}
alarm(0);//Turn off the clock
signal(SIGALRM, sigfunc); //(if any) restore the original signal processing function
return;
}//Timeout setting for reading data
You can also set a timeout for recv, and it will interrupt if no response is received within 5 seconds
signal(…);
alarm(5);
recv( … );
alarm(0);
static void sig_alarm(int signo){return;}
When the client is blocked in reading (readline,...), if the server crashes at this time, the client TCP tries to receive an ACK from the server and continues to retransmit the data segments. It will take about 9 minutes to give up the retransmission and return an error. . Therefore, when the client's read is blocked, the call times out.
2. Use asynchronous multiplexed IO usage (millisecond timeout)
Asynchronous IO execution process:
1. First set the flag to Non-blocking mode and prepare to call the connect function in non-blocking mode
2. Call connect. Under normal circumstances, because the TCP three-way handshake takes some time; a non-blocking call will return an error as long as it cannot be completed immediately, so EINPROGRESS will be returned here, indicating that the connection is being established but has not been completed.
3. Set the current socket in the read socket descriptor set (fd_set rset) and write socket descriptor set (fd_set wset) (use FD_ZERO(), FD_SET() macros), and set the timeout (struct timeval *timeout)
4. Call select(socket, &rset, &wset, NULL, timeout)
Returning 0 indicates that the connect has timed out. If the timeout you set is greater than 75 seconds, there is no need to do this, because the timeout limit for connect in the kernel is 75 seconds.
//select Example of implementing millisecond timeout:
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 immediately 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;
}
//Set to non-blocking mode
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服务器层)的超时时间必须大于PHP(PHP-FPM)中设置的超时时间,不然后面没处理完,你前面就超时关闭了,这个会很杯具。还有就是PHP的超时时间要大于PHP本身访问后端(MySQL、HTTP、Memcached)的超时时间,不然结局同前面。
2. 超时设置原则是什么?
如果是希望永久不超时的代码(比如上传,或者定期跑的程序),我仍然建议设置一个超时时间,比如12个小时这样的,主要是为了保证不会永久夯住一个php进程或者后端,导致无法给其他页面提供服务,最终引起所有机器雪崩。
如果是要要求快速响应的程序,建议后端超时设置短一些,比如连接500ms,读1s,写1s,这样的速度,这样能够大幅度减少应用雪崩的问题,不会让服务器负载太高。
3. 自己开发超时访问合适吗?
一般如果不是万不得已,建议用现有很多网络编程框架也好、基础库也好,里面一般都带有超时的实现,比如一些网络IO的lib库,尽量使用它们内置的,自己重复造轮子容易有bug,也不方便维护(不过如是是基于学习的目的就当别论了)。
4. 其他建议
Timeouts are a big problem in all applications and should be taken into consideration when developing applications. I have seen some applications with timeout settings of hundreds of seconds. This performance is really poor. Let me give you an example:
For example, if your php-fpm has opened 128 php-cgi processes, and your timeout is set to 32s, then if our back-end service is poor, in extreme cases, the maximum number of requests that can be responded to per second is:
128 / 32 = 4
You read that right, only 4 requests can be processed in 1 second, so the service is too bad! Although we can increase the size of the php-cgi process, the memory usage and switching costs between processes will also increase. The CPU and memory will increase, and the service will be unstable. Therefore, try to set a reasonable timeout value, or urge the backend to improve performance.