看到第5章就会发现,作者用了好多包裹函数,封装原始的api,所以不可避免必须学习下unix里出错处理方面的知识。
例如:
sockfd = Socket(AF_INET, SOCK_STRAM, 0);
函数Socket是函数api socket的包裹函数,实现如下:
int Socket(int famlily, int type, int protocol)
{
int n;
if( ( n = socket(family, type, protocol) ) < 0 )
err_sys("socket error"); // 作者的自定义函数
}
而作者又自定义了一组err_sys或error_quit出错包裹函数,原因是这样可以只用一行C代码写出错误处理代码过程:
if(出错条件)
err_sys(带任意参数数目的printf格式字符串);
而不是使用如下的繁琐的方式:
if(出错条件)
{
char buff[200];
snprintf(buff, sizeof(buff), 带任意参数数目的printf格式字符串);
perror(buff); // perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串
exit(1);
}
下面贴出作者常用的自定义出错包裹函数的代码(在作者给本书附带源码里lib目录里的error.c文件可见):
#include "unp.h"
#include <stdarg.h> /* ANSI C header file */
#include <syslog.h> /* for syslog() */
int daemon_proc; /* set nonzero by daemon_init() */
static void err_doit(int, int, const char *, va_list);
/* Nonfatal error related to system call
* Print message and return */
void
err_ret(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, LOG_INFO, fmt, ap);
va_end(ap);
return;
}
/* Fatal error related to system call
* Print message and terminate */
void
err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, LOG_ERR, fmt, ap);
va_end(ap);
exit(1);
}
/* Fatal error related to system call
* Print message, dump core, and terminate */
void
err_dump(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, LOG_ERR, fmt, ap);
va_end(ap);
abort(); /* dump core and terminate */
exit(1); /* shouldn't get here */
}
/* Nonfatal error unrelated to system call
* Print message and return */
void
err_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, LOG_INFO, fmt, ap);
va_end(ap);
return;
}
/* Fatal error unrelated to system call
* Print message and terminate */
void
err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, LOG_ERR, fmt, ap);
va_end(ap);
exit(1);
}
/* Print message and return to caller
* Caller specifies "errnoflag" and "level" */
static void
err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
int errno_save, n;
char buf[MAXLINE + 1];
errno_save = errno; /* value caller might want printed */
#ifdef HAVE_VSNPRINTF
vsnprintf(buf, MAXLINE, fmt, ap); /* safe */
#else
vsprintf(buf, fmt, ap); /* not safe */
#endif
n = strlen(buf);
if (errnoflag)
snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
strcat(buf, "\n");
if (daemon_proc) {
syslog(level, buf);
} else {
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr);
}
return;
}
这些出错函数只有err_msg是最不严重的错误,因为他没有调用exit结束进程,或abort异常终止进程(和exit一样,也会让所有的流被关闭和冲洗),而只是return返回一下,没让进程终止。
这么多函数都围绕着error_doit 这个逻辑处理中心函数,其中第一个参数errnoflag把错误分为2种,和系统相关的错误标记为1,和系统无关的错误标记为0第二个参数level,fatal(致命的)错误标记为LOG_ERR,unfatal(不致命的)错误标记为LOG_INFO
要知道的是,这些包裹函数不见得多省代码量,以后就会发现线程函数遇到错误时并不设置标准Unix的errno变量,而是把errno的值作为函数的返回值返回调用者。这意味着每次调用以pthread_开头的某个函数时,我们必须分配一个变量来存放函数的返回值,以便在调用err_sys前把errno变量设置成该值,为了避免引入花括号把代码弄得混乱,我们可以使用C语言的逗号操作符,把errno的赋值与err_sys的调用组合成一条语句,如下所示:
int n;
if( (n = pthread_mutex_lock(&ndone_mutex)) != 0 )
errno = n, err_sys("pthread_mutex_lock error");
如果忘记了C/C++逗号符的使用,可以联想 for( ; ; i++, j++),或许就想起来了。
自习推敲C代码的编写,我们可以用宏来代替函数,从而稍微提高运行时效率,不过包裹函数很少是程序性能的瓶颈所在。使用包裹技术还有助于检查哪些错误返回值通常被忽略的函数是否出错,例如close和listen。
更多作者写的包裹函数见作者提供本书的代码的lib目录里的带wrap开头的文件名的文件。