参考书籍:《UNIX网络编程》《UNIX环境高级编程》

errno

只要在一个 系统调用 中有错误发生,全局变量 errno 就会被自动设置为一个特定的正值,用来反馈具体错误,而函数本身则会返回 -1,以说明函数发生了错误;如果函数返回正值,即没有错误发生,则 errno 没有定义。errno 包含在 errno.h 中。

  • 如果函数没有出错,则之前的 errno 可能不会被清除(未定义);只有发生错误时,才会覆盖之前的错误。

  • errno 不会为 0,这与多线程的 errno 处理有关。

    这是因为:线程函数(以 pthread_ 开头的函数)遇到错误时不会设置标准 Unix 的 errno 变量,而是将 errno 的值以函数返回值的形式交给调用者。也就是说,返回值大于 0 则说明发生了错误,那没有发生错误呢?自然也就是返回 0 了。

    1
    2
    3
    4
    int n;
    if((n=pthread_mutex_lock(&ndoen_mutex))!=0){
    fprintf(stderr,"error:%s",strerror(n));
    }

    你看,这意味着我们每次调用 pthread_ 函数时,都要事先分配一个整形来保存错误值,这很麻烦,所以我们可以把错误处理和 pthread_ 函数包裹起来以简化代码:

    1
    2
    3
    4
    5
    void Pthread_mutex_lock(pthread_mutex_t *mptr){
    int n;
    if((n=pthread_mutex_lock(mptr))!=0)
    fprintf(stderr,"error:%s",strerror(n));
    }
  • 虽然 errno 是一个全局变量,但是在多线程环境中,每个线程都会有自己独立的 errno 副本,这是通过线程本地存储(Thread-Local Storage,TLS)实现的,因此一般不用担心多线程会相互竞争 errno,可参见《APUE》P358

  • 虽然在多线程下不用担心 errno 的竞争问题,不过单线程下 errno 仍可能出现问题,比如在信号处理函数中被修改 。当发生信号时,执行流会跳转到信号处理函数,这感觉就像是多线程,但实际上它和之前的执行流位于同一个上下文,也就是说信号处理函数并不是新开的线程。因此,如果之前的执行流在系统调用出错后修改 errno,接着被信号中断,进入了信号处理函数,而信号处理函数中也调用了某些系统函数如 write,如果此时这个系统调用出错,那么 errno 就会被修改,这就使得之前的 errno 被覆盖! 因此,作为一个通用的规则: 在信号处理函数中,应首先保存 errno,退出时再恢复:

    1
    2
    3
    4
    5
    6
    7
    void sig_alarm(int signo)
    {
    int errno_cpy = errno;
    //do something...
    write(....);
    errno = errno_cpy;
    }

perror、strerror

C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr,其中 str 是自定义内容。该函数先输出 str,再输出错误描述符:

1
2
3
4
if(-1 == connect(fd, addr, len))
perror("connect");
//连接错误则输出以下结果:
connect: Connection refused

C 库函数 char *strerror(int errnum) 从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。

1
2
3
4
5
if(-1 == connect(fd, addr, len))
{
char* str = strerror(errno);
fputs(str, stderr);
}

包裹函数

网络编程很多时候都会遇到一些网络问题,并通过函数返回值或 errno 反馈错误,因此绝不能忽略对错误的处理。包裹函数一般用来处理致命性错误,此时能干的也就只有打印错误然后退出,对于非致命性错误,比如 EINTR 错误,就需要我们自己来处理失败情况:

1
2
3
4
5
6
7
8
while(1){
if((sock_conn=accept(sock_listen,NULL,NULL))<0){
if(errno==EINTR)
continue;
else
perror("accept");
}
}

这里我们需要自己重启被中断的系统调用。关于中断的系统调用,参见《APUE》第 260 页。

一些包裹函数如下:

1
2
3
4
5
6
7
8
9
10
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (bind(fd, sa, salen) < 0)
err_sys("bind error");
}
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (connect(fd, sa, salen) < 0)
err_sys("connect error");
}

其他包裹函数可参考UNP-sockwarp.c