使用Dtrace检查recv()的返回值

这周在新的产品版本上线后,发现监控日志总是会报recv()返回error,并且errno是131(ECONNRESET)。查了一下man手册,发现并没有说recv()会返回ECONNRESET,于是自己便打算一探究竟。想到最近正在学习Dtrace,于是便写了下面这个简单脚本(check_recv.d):

#!/usr/sbin/dtrace -qs

syscall::recv:return
/(int)arg0 <=0 && pid == $1/
{
    printf("recv return: tid=%d, arg0=%d, errno=%d\n", tid, arg0, errno);
}

第四行是触发探针条件:当recv返回0或者-1并且进程号等于输入监控的进程号。
第六行是输出:线程ID,recv()返回值,errno。
使用方法 :check_recv.d 19771(监控进程号)
通过运行脚本,我发现其实recv()返回的是0,而errno也是0,那么为什么监控日志会输出errno是131呢?我又查了一下这个版本新加的代码,发现了下面的逻辑:

if (recv() <= 0)
{
    log(errno)
}

原来在recv()返回0时,也会输出errno。而在recv()返回0时,是不会更新errno的值,只有在recv()返回-1时,才会更新errno的值。所以现在监控日志里的errno其实是以前某个系统调用错误时设置的errno。所以代码应该改为:

if (recv() < 0)
{
    log(errno)
}

问题解决!

客户端—服务器通信模型浅析

客户端—服务器(Client-Server)是我们平时最常见的通信方式,本文就对这一通信模型做个简单介绍。

(一)TCP连接方式:短连接方式?长连接方式

目前,多数的客户端—服务器选择TCP做为传输层协议,也有少数选择UDP或SCTP协议的。而TCP连接有两种工作方式:短连接方式(Short-Live Connection)和长连接方式(Long-Live Connection)。

(1)短连接方式:

当客户端有请求时,会建立一个TCP连接,接收到服务器响应后,就断开连接。下次有请求时,再建立连接,收到响应后,再断开。如此循环。这种方式主要有两个缺点:

a)建立TCP连接需要3次“握手”,拆除TCP连接需要4次“挥手”,这就需要7个数据包。如果请求和响应各占1个数据包,那么一次短连接的交互过程,有效的传输仅占2/9,这个利用率太低了。

b)主动断开TCP连接的一端,TCP状态机会进入TIME_WAIT状态。如果频繁地使用短连接方式,就有可能使客户端的机器产生大量的处于TIME_WAIT状态TCP连接(UNIX系统下,可以使用netstat命令来查看)。

(2)长连接方式:

客户端和服务器建立TCP连接后,会一直使用这条连接进行数据交互,直到没有数据传输或异常断开。在空闲期间,通常会使用心跳数据包(Keep-Alive)保持链路不断开。目前长连接方式应用范围比较广泛。

(二)消息交互方式

(1)一个请求,一个响应

这种消息交互方式如下图所示:


当客户端发出请求后,程序就阻塞在那里,直到收到服务器的响应或者超时。很多对数据库的访问方式都是这样的(像Redis的C程序客户端hiredis)。

(2)多个请求,多个响应

这种消息交互方式如下图所示:


这种方式下,客户端一次可以发送多个请求,而每个请求会带有一个消息标示(Message ID)。这样在客户端收到响应后,就可以根据响应的消息标示,和请求对应起来。

Solaris操作系统网络编程经验分享

自从2010年我开始在Solaris操作系统进行应用程序开发算起,到现在已经超过3年的时间了。在这里我想把在Solaris操作系统上做网络编程开发的一些特有的经验分享出来,希望给别人有所帮助。

经验1:链接动态库选项:-lresolv -lnsl -lsocket。
也许在其它的UNIX系统上,一个”-lsocket”链接选项就能解决所有问题。但是在Solaris系统上,要链接这三个库。

经验2:不支持SO_SNDTIMEO和SO_RCVTIMEO socket选项。
在我用过的Solaris 9和Solaris 10上,这两个socket选项都不支持(我没用过Open Solaris,所以不确定Open Solaris是否支持)。所以尽管这两个宏定义在sys/socket.h,但是却是不起作用的。如果在程序中用到这两个选项,一定要注意这一点。我在使用MySQL,hiredis这些开源代码时都遇到过这个问题。

经验3:使用sctp_bindx之前,一定要先调用bind。
这个可能也是Solaris系统特有的了。我曾经遇到一个使用SCTP的应用程序,在其它系统上跑的好好的,到Solaris上就不行。后来查了一下man手册,才发现需要在调用sctp_bindx之前,要先调用bind。

经验4:shutdown()函数作用于一个listen socket时,会产生ENOTCONN错误
我曾经写过下面的程序:
第一个线程:

{  
    ......  
    FD_ZERO(&fd_sets);  
    FD_SET(sock_fd, &fd_sets);  

    ret_val = select(sock_fd + 1, &fd_sets, NULL, NULL, NULL);  

    if (ret_val > 0)  
    {  
         accept(sock_fd, NULL, NULL);  
         ......  
    }  
    else  
    {  
        ......  
    }  
}  

第二个线程:

{  
    ......  
    shutdown(sock_fd, SHUT_RD);  
    ......  
} 

其中第一个线程阻塞在select函数,sock_fd是一个listen socket。我本意是在第二个线程中调用shutdown函数,使第一个线程的select返回,结果却发现没有生效,后来才发现shutdown返回了一个ENOTCONN错误。感兴趣的朋友也可以参考这里

以上就是我在Solaris上进行网络程序开发的一点心得,希望分享出来给需要的朋友一点帮助。如果大家有更好的经验,也欢迎分享出来。