希望和广大热爱技术的童鞋一起交流,成长。
分类:
2011-07-22 22:46:01
原文地址:(10)异常处理 作者:g_programming
(10)异常处理
注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方
1. 本文所介绍的程序平台
开发板:arm9-mini2440
虚拟机为:Red Hat Enterprise Linux 5
开发板上系统内核版本:linux-2.6.32.2
2. 函数调用的错误处理
对于unix系统,大部分的系统的系统调用在非正常返回时返回-1,并设置全局变量errno,如socket(),bind(),accept()等等,变量errno存放的是一个正整数老表示错误的类型,对于线程,每个线程都要自己的errno值,所以不必考虑线程同步的问题。
错误值定义在头文件
#include
void perror(const char *s);
s:指向一个字符串,如果s为NULL,则直接将相应的错误信息显示在标准错误输出。
2.1定义错误处理函数
我们在原来函数的基础上加上错误处理,比如:
int myaccept(int listenfd, struct sockaddr *client, socklen_t *sin_size)
{
int connectfd;
while((connectfd = accept(listenfd, client, sin_size)) == -1)
{
if(errno == EINTR) continue;
……
perror(“accept error \n”);
break;
}
returnn connectfd;
}
3. I/O超时处理
3.1 使用alarm()函数
该韩式产生定时信号,然后通过信号处理器来处理超时,方法虽然简单但是不灵活。
static int stimeout = 0;
static void AlarmHandler(int sig)
{
Stimeout = 1;
}
…
signal(SIGALRM,AlarmHandler);
stimeout = 0;
alarm(CONNECT_TIMEOUT);
if(connect(sock, (struct sockaddr *)&server, sizeof(server)) = -1)
{
if(stimeout)
perror(“timeout connecting ”);
else
perror(“connect failed ”)
close(sock);
exit(1);
}
stimeout = 0;
alarm(0);
3.2使用select()函数
Select函数有一个时间选项,当I/O操作超时后,函数返回0。
…
int fd;
fd_set fdr, fdw;
struct timeval timeout;
int maxfd = MAXDESCRIPTOR + 1;
…
timeout.tv_sec = …;
timeout.tv_usec = 0;
…
switch(select(maxfd, &fdr, &fdw,NULL, &timeout))
{
case -1 : handle exception;
case 0: handle timeout;
default:
}
4. 服务器异常处理
4.1异常处理的系统调用
4.1.1 socket() 调用失败返回-1,并设置相应的errno错误码。
EMFILE:描述表已满。通常的处理方法是提示用户,并退出运行。
ENOMEM:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。
4.1.2 bind() 调用失败返回-1,并设置相应的errno错误码。
EADDRINUSE:地址已被使用。通常的方法是选用其他套接字地址或者提示用户,并退出运行。
4.1.3 listen() 调用失败是程序的逻辑错误引起的,不是系统或者网络异常,调用失败则显示错误并退出运行。
4.1.4 accept() 调用失败返回-1,并设置相应的errno错误码。
EINTR:由于信号的传递引起的中断,accept是慢系统调用,通常的方法是重新执行该函数。
EMFILE:描述表已满。通常的处理方法是提示用户,并退出运行。
ENOMEM:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。
4.1.5 recv() 调用失败返回-1,并设置相应的errno错误码。
EINTR:由于信号的传递引起的中断,通常的方法是重新执行该函数。
EIO:I/O错误,通常是重新执行函数调用。
ENOMEM:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。
4.1.6 send() 调用失败返回-1,并设置相应的errno错误码。
EINTR:由于信号的传递引起的中断,通常的方法是重新执行该函数。
EMSGSIZE:发送的消息太长,通常的处理方法是将消息分段再进行发送。
ENOMEM:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。
4.2 错误基本分为两类,一类是终止程序,另一类是重新启动函数。还有注意在客户和服务器建立连接后recv()函数由于网络故障或者客户异常长时间收不到客户的消息,此时recv()函数阻塞,为了不必要的系统开销应该设置超时函数。
4.3 服务器异常处理实例
该实例是一个多进程并发服务器,他对socket函数调用异常采用终止程序处理,对accept函数和send函数异常,如果是EINTR错误则重新调用函数,否则终止程序,对recv函数异常,如果是EINTR错误则重新调用函数,如果超时则终止连接并消失超时结束该进程。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234 /* Port that will be opened */
#define BACKLOG 2 /* Number of allowed connections */
#define MAXDATASIZE 1000
void process_cli(int connectfd, sockaddr_in client);
void sig_handler(int s);
int myrecv(int, char*, int,int);
int mysend(int,char*,int,int);
main()
{
int listenfd, connectfd; /* socket descriptors */
pid_t pid;
struct sockaddr_in server; /* server's address information */
struct sockaddr_in client; /* client's address information */
socklen_t sin_size;
struct sigaction act, oact;
act.sa_handler = sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
//处理僵死程序
if (sigaction(SIGCHLD, &act, &oact) < 0) {
perror("Sigaction failed!");
exit(1);
}
/* create TCP socket */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* handle exception */
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
/* handle exception */
perror("Bind error.");
exit(1);
}
if(listen(listenfd,BACKLOG) == -1){ /* calls listen() */
perror("listen() error\n");
exit(1);
}
sin_size=sizeof(struct sockaddr_in);
while(1)
{
/* Accept connection */
if ((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) {
if (errno == EINTR) continue;
perror("accept() error\n");
exit(1);
}
/* create child */
if ((pid=fork())>0) {
/* parent process */
close(connectfd);
continue;
}
else if (pid==0) {
/* child process */
close(listenfd);
process_cli(connectfd, client);
exit(0);
}
else {
printf("fork error\n");
exit(0);
}
}
close(listenfd); /* close listenfd */
}
void process_cli(int connectfd, sockaddr_in client)
{
int num;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
printf("You got a connection from %s. ",inet_ntoa(client.sin_addr) );
/* Get client's name from client */
num = myrecv(connectfd, cli_name, MAXDATASIZE,0);
if (num == 0) {
close(connectfd);
printf("Client disconnected.\n");
return;
}
cli_name[num - 1] = '\0';
printf("Client's name is %s.\n",cli_name);
while (num = myrecv(connectfd, recvbuf, MAXDATASIZE,0)) {
recvbuf[num] = '\0';
printf("Received client( %s ) message: %s",cli_name, recvbuf);
for (int i = 0; i < num - 1; i++) {
sendbuf[i] = recvbuf[num - i -2];
}
sendbuf[num - 1] = '\0';
mysend(connectfd,sendbuf,strlen(sendbuf),0);
}
close(connectfd); /* close connectfd */
}
void sig_handler(int s)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
#define TIMEOUT 120
int myrecv(int connfd, char* buf, int len ,int flag)
{
int num;
fd_set fdRSet;
struct timeval timeout;
int maxfd = connfd + 1;
timeout.tv_sec = TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO(&fdRSet);
FD_SET(connfd, &fdRSet);
switch (select(maxfd, &fdRSet,NULL,NULL, &timeout)) {
case -1: perror("select failed");
close(connfd);
exit(1);
case 0: printf("Receiving timeout.\n");
close(connfd);
exit(1);
default: while ((num = recv(connfd, buf, len, flag)) == -1) {
if (errno == EINTR) continue;
perror("recv failed");
close(connfd);
exit(1);
}
break;
}
return num;
}
int mysend(int connfd, char* buf, int len, int flag)
{
int num;
while ((num = send(connfd, buf, len, flag)) == -1) {
if (errno == EINTR) continue;
perror("send failed ");
close(connfd);
exit(1);
}
return num;
}
5.1 客户异常处理
对于TCP客户,主要涉及到得系统调用是:socket(),connect(),send(),recv(),其中只有connect函数与服务器不同,调用失败返回-1,否则设置相应的错误码。
EADDRNOTAVAIL:远程地址无效。通常是选择新的地址,并重新连接或者提示用户,关闭套接字终止程序。
ECONNREFUSED:连接被拒绝:当客户连接服务器的时候,而服务器端没有进程等待在相应的端口上(例如:服务器还未启动),通常是等待一段时间后再连接,如果仍然无法连接就退出
EINTR:由于信号的传递引起的中断,通常的方法是重新执行该函数。
ENETUNREACH:网络无法抵达。由于路由器故障导致无法返回ICMP的响应包,通常的方法是提示用户并退出。
ENXIO:服务器在建立连接成功之前退出。通常的方法是提示用户并退出。
ETIMEOUT:连接超时。当客户发送连接请求信号(SYN)后,服务器在指定的时间(75秒)里面没有响应。通常的方法是提示用户并退出。
ENOMEM:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。