疯狂的bug【select -IO复用中】

    select版本的回显服务,初期的bug是,客户端可以连接上服务器,但是客户端发送字符串过来以后,服务器的select却阻塞在那里,死活没反映。。。gdb调试查看了fd_set类型的值,也发现的确是有两个值的叠加的——一个是监听套接字,一个是刚刚连接上的客户套接字。这就奇怪了!直到看到 http://bbs.csdn.net/topics/230024080 上别人的提问,最后他说:解决了,原因是我的socket集中混进了一个 错误的socket,select一直报10038的错误,

于是我也开始找这方面的错误,但是找不到,最后与标准代码对比,才发现错误的原因,是select第一个参数设置错误。

错误代码:服务器ser.c

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#define CONN_MAX 10000
#define cerror(str) do{perror(str); exit(EXIT_FAILURE);}while(0)

int sock_init_ser(int is_any, 
            const char *addr,
            int port,
            int backlog);
            
void do_client_sock(int *pcd);

int conn_fd[CONN_MAX];
int conn_n;   // 当前数组中最大可用的元素序号+1,检查时,>=conn_n的元素可以跳过
int fd_max;

/*
 *   MAIN
 *   回显服务的服务器端
 *   使用select实现
 */
int main(int argc, char * argv[])
{
    int sd;
    fd_set fs;
    int i;
    
    if(argc<3)
    {
        printf("Usage: argv[0] port backlog [ipaddr]\n");
        exit(0);
    }
    
    if (argc>=4)
    {
        sd=sock_init_ser(0,argv[3],atoi(argv[1]), atoi(argv[2]));
    }else
    {
        sd=sock_init_ser(1,NULL,atoi(argv[1]), atoi(argv[2]));
    }
     
                   
    for(i=0; i<CONN_MAX; i++) // init to -1
    {
        conn_fd[i]=-1;
    }
    conn_n=0;
    fd_max=sd;
    while(1)
    {
        int sr;
        FD_ZERO(&fs);
        FD_SET(sd, &fs);
        fd_max=sd;
        for (i=0; i<conn_n; i++)
        {
            if (conn_fd[i]!=-1)
            {               // continue;
                FD_SET(conn_fd[i], &fs);
                if(i>fd_max)
                {
                    fd_max=i;//conn_fd[i]; // 不是fd_max=i  !!!
                } 
            } 
        } 
        sr = select(fd_max+1, &fs, NULL, NULL, NULL);  // select
        if (sr<0)   // select error
        {
            perror("select");
            continue;
        }
        if(sr==0)  // select timeout
        {
            continue;
        }
        
        for (i=0; i<conn_n; i++)
        {
            if(conn_fd[i]!=-1 && FD_ISSET(conn_fd[i], &fs))
                 do_client_sock(conn_fd+i);  // echo back
        }
        
        // listen socket
        if (FD_ISSET(sd, &fs))
        {
            int cd=accept(sd, NULL, NULL);
            if(cd<0)
            {
                if(errno == EINTR)
                {
                    continue;
                }
                else
                {
                    perror("accept");
                    continue;
                }
            }
            // 
            for(i=0; i<conn_n+1; i++) // or i<CONN_MAX
            {
                if(conn_fd[i]==-1)
                {
                     conn_fd[i]=cd;
                     printf("add a new client\n");
                     break;
                }
            }
            if(i>=CONN_MAX-1)
            {
                close(cd);
                conn_fd[i]=-1;
                printf("conn_fd full\n");
                continue;
            }
            if(i>=conn_n)
                conn_n=i+1;
                
            printf("conn_n= %d\n",conn_n);
        }
        
    }
    
    close(sd);
    return 0;
}

/*
 * sock_init_ser
 * is_any为0时,需要指定IP地址,为非零时,IP地址字段被忽略,使用INADDR_ANY
 * port为端口号,backlog为监听个数
 */
int sock_init_ser(int is_any, 
            const char *addr,
            int port,
            int backlog)
{
    int sd;
    struct sockaddr_in sin;
    
    sd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sd<0)
    {
        cerror("socket");
    }
    memset(&sin, 0, sizeof(sin) );
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    if (is_any)
    {
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
    }else
    {
        inet_pton(AF_INET, addr, &(sin.sin_addr));
    }
    if (bind(sd, (struct sockaddr*)&sin,sizeof(sin))<0)  
    {
        cerror("bind");
    }
    
    if(listen(sd,backlog)<0)
        cerror("listen");
    printf("server open addr: %d\n", ntohs(sin.sin_port));
    return sd;
    
}

/*
 * do_client_sock
 * 监听客户套接子,有消息到来则read,然后打印到标准输出,
 * 并发回给客户,同时需要处理关闭和出错,此时需要将客户套接子从数组里删除。
 */
void do_client_sock(int *pcd)
{
    char buf[256];
    int nr = read(*pcd, buf, 256-1);
    if(nr<0)
    {
        perror("read\n");
        close(*pcd);
        *pcd=-1;
        printf("del a client\n");
        return;
    }
    if (nr==0)
    {
        close(*pcd);
        *pcd=-1;
        printf("del a client\n");
        return;
    }
    printf("recv: %s\n", buf);
    write(*pcd, buf, 256-1);
}
客户端:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#define cerror(str) do{perror(str); exit(EXIT_FAILURE);}while(0)


int sock_init_client( const char *addr,  int port);
            
void do_client_sock(int cd);

/*
 *   MAIN
 *   回显服务的客户端程序,使用格式形如client 5778 192.168.1.105
 *   连接以后,发送some test string by %d, seq: %d字符串给服务器,第一个整数是进程id
 *   第二个整数是序号,从1开始递增。完成一次回显以后,休眠10s,随后开始下一个。
*/
int main(int argc, char * argv[])
{
    int cd;
   
    if(argc<3)
    {
        printf("Usage: argv[0] port ipaddr");
    }
    
    cd= sock_init_client(argv[2], atoi(argv[1])) ;
    do_client_sock(cd);
    
    return 0;                  
}

/*
 * do_client_sock
 * 连接完成以后执行循环回显
*/
void do_client_sock(int cd)
{
    while(1)
    {
        char buf[256];
        static int seqnum=1;
        int nr;
        snprintf(buf, sizeof(buf), "some test string by %d, seq: %d",getpid(), seqnum++);
        nr=write(cd, buf, strlen(buf));
        if(nr!=strlen(buf))
            printf("write error\n");
        nr = read(cd, buf, 256-1);
        if(nr<0)
        {
            perror("read\n");
            close(cd);
            exit(1);
        }
        if (nr==0)
        {
            close(cd);
            exit(1);
        }
        buf[strlen(buf)]='\0';
        printf("echo back: %s\n", buf);
        sleep(10);
    }
}

/*
 * sock_init_client
 * 给定IP地址和端口号,执行连接
 */
int sock_init_client( const char *addr,  int port)
{
    int cd;
    struct sockaddr_in sin;
    
    cd=socket(AF_INET, SOCK_STREAM, 0);
    if(cd<0)
    {
        cerror("socket");
    }
    memset(&sin, 0, sizeof(sin) );
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    inet_pton(AF_INET, addr, &(sin.sin_addr));
    
    if (connect(cd, (struct sockaddr*)&sin, sizeof(sin) )<0)
    {
        cerror("connect");
    }
    
    return cd;
    
}


错误的地方在ser.c:

  for (i=0; i<conn_n; i++)
        {
            if (conn_fd[i]!=-1)
            {               // continue;
                FD_SET(conn_fd[i], &fs);
                if(i>fd_max)
                {
                    fd_max=i;//conn_fd[i]; // 不是fd_max=i  !!!
                } 
            } 
        } 
套接字描述是一个个分配的,监听套接字先分配,accept进来的客户套接字后分配,所以客户套接字会比监听套接字大。而conn_n在第一次accept进一个或若干个客户后,值为1或2,3.。。。因此,用i去于fd_max比,是不正确的。假如只有一个客户,那么循环一次就退出,i=0肯定小于fd_max,因此fd_max最后只囊括了监听套接字,客户套接字忽略了。

因此应该改为

  for (i=0; i<conn_n; i++)
        {
            if (conn_fd[i]!=-1)
            {               // continue;
                FD_SET(conn_fd[i], &fs);
                if(conn_fd[i]>fd_max)
                {
                    fd_max=conn_fd[i]; // 不是fd_max=i  !!!
                } 
            } 
        } 

运行:

服务器:

administrator@ubuntu:~/mytest$ ./ser 8887 5 192.168.1.105
server open addr: 8887
add a new client
conn_n= 1
recv: some test string by 5757, seq: 1
recv: some test string by 5757, seq: 2
recv: some test string by 5757, seq: 3
recv: some test string by 5757, seq: 4
recv: some test string by 5757, seq: 5
recv: some test string by 5757, seq: 6


客户:

administrator@ubuntu:~/mytest$ ./client 8887 192.168.1.105
echo back: some test string by 5757, seq: 1
echo back: some test string by 5757, seq: 2
echo back: some test string by 5757, seq: 3
echo back: some test string by 5757, seq: 4
echo back: some test string by 5757, seq: 5
echo back: some test string by 5757, seq: 6



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值