Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2422822
  • 博文数量: 298
  • 博客积分: 7876
  • 博客等级: 准将
  • 技术积分: 5500
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-23 13:39
文章存档

2013年(2)

2012年(142)

2011年(154)

分类: LINUX

2011-04-05 11:28:12

10)异常处理

 

注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方

 

1.     本文所介绍的程序平台

开发板:arm9-mini2440

虚拟机为:Red Hat Enterprise Linux 5

开发板上系统内核版本:linux-2.6.32.2

 

2.     函数调用的错误处理

对于unix系统,大部分的系统的系统调用在非正常返回时返回-1,并设置全局变量errno,如socket(),bind(),accept()等等,变量errno存放的是一个正整数老表示错误的类型,对于线程,每个线程都要自己的errno值,所以不必考虑线程同步的问题。

错误值定义在头文件,通常调用perror()来显示错误信息。

#include

void perror(const char *s);

s:指向一个字符串,如果sNULL,则直接将相应的错误信息显示在标准错误输出。

 

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           /* These are the usual header files */

    #include           /* for bzero() */

    #include          /* for close() */

    #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:没有足够的用户内存。通常的处理方法是提示用户,并退出运行。

 

 

 

阅读(1421) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~