Chinaunix首页 | 论坛 | 博客
  • 博客访问: 154143
  • 博文数量: 32
  • 博客积分: 2053
  • 博客等级: 大尉
  • 技术积分: 382
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-09 12:45
文章分类

全部博文(32)

文章存档

2011年(12)

2010年(20)

分类: LINUX

2011-01-14 13:47:18

曾经也使用过select,但没有很好的理解它,今天看了网上几个实例,有了更深的体会.
下面的模型是client读取用户输入,发送到server,直到用户输入exit为止。server端仅对输入进行输出显示。
下面这个程序copy自网络,它是一个select函数在server的实例。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 1234 // the port users will be connecting to


#define BACKLOG 5 // how many pending connections queue will hold


#define BUF_SIZE 10

int fd_A[BACKLOG]; // accepted connection fd

int conn_amount; // current connection amount


int main(void)
{
    int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd

    struct sockaddr_in server_addr; // server address information

    struct sockaddr_in client_addr; // connector's address information

    socklen_t sin_size;
    int yes = 1;
    char buf[BUF_SIZE];
    int ret;
    int i;

    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    
    server_addr.sin_family = AF_INET; // host byte order

    server_addr.sin_port = htons(MYPORT); // short, network byte order

    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP

    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sock_fd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    printf("listen port %d\n", MYPORT);

    fd_set fdsr;
    int maxsock;
    struct timeval tv;

    conn_amount = 0;
    sin_size = sizeof(client_addr);
    maxsock = sock_fd;
    while (1) {
        // initialize file descriptor set

        FD_ZERO(&fdsr);
        FD_SET(sock_fd, &fdsr);

        // timeout setting

        tv.tv_sec = 30;
        tv.tv_usec = 0;

        // add active connection to fd set

        for (i = 0; i < BACKLOG; i++) {
            if (fd_A[i] != 0) {
                FD_SET(fd_A[i], &fdsr);
            }
        }

        ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
printf("select\n");
        if (ret < 0) {
            perror("select");
            break;
        } else if (ret == 0) {
            printf("timeout\n");
            continue;
        }

        // check every fd in the set

        for (i = 0; i < conn_amount; i++) {
            if (FD_ISSET(fd_A[i], &fdsr)) {
                ret = recv(fd_A[i], buf, sizeof(buf) - 1, 0);
                if (ret <= 0) { // client close

                    printf("client[%d] close\n", i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i], &fdsr);
                    fd_A[i] = 0;
                } else { // receive data

                       buf[ret] = 0;
                    printf("client[%d] send:%s\n", i, buf);
                }
            }
        }

        // check whether a new connection comes

        if (FD_ISSET(sock_fd, &fdsr)) {
            new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
            if (new_fd <= 0) {
                perror("accept");
                continue;
            }

            // add to fd queue

            if (conn_amount < BACKLOG) {
                fd_A[conn_amount++] = new_fd;
                printf("new connection client[%d] %s:%d\n", conn_amount,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                if (new_fd > maxsock)
                    maxsock = new_fd;
            }
            else {
                printf("max connections arrive, exit\n");
                send(new_fd, "bye", 4, 0);
                close(new_fd);
                break;
            }
        }
    }

    // close other connections

    for (i = 0; i < BACKLOG; i++) {
        if (fd_A[i] != 0) {
            close(fd_A[i]);
        }
    }

    exit(0);
}

client.c

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define LEN 2000
char buf[LEN];
void err(char *s)
{
        printf("%s\n", s);
        exit(0);
}
int main(int argc, char* argv[])
{
        struct sockaddr_in addr;
        int sockfd, len;
    int i;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sock %d\n", sockfd);
        if(sockfd < 0)
                err("socket");

        addr.sin_family = AF_INET;
        addr.sin_port = htons(atoi(argv[1]));
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&(addr.sin_zero), 8);
    
    if(connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
            err("conn");

    while(1)
    {
        scanf("%s", buf);
        if(strcmp(buf, "exit") == 0)
            break;
        send(sockfd, buf,strlen(buf) , 0);
    }
    close(sockfd);
    return 0;
}


运行server

supertool@supertool-desktop:/tmp$ ./server
listen port 1234
select
new connection client[1] 127.0.0.1:45308
select
client[0] send:123456789
select
client[0] send:123456789
select
client[0] send:123
select
client[0] close

运行client
supertool@supertool-desktop:/home/work/test/socket$ ./client 1234
sock 3
123456789
123456789123
exit



第二个程序对上述服务器端进行修改

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 1234 // the port users will be connecting to


#define BACKLOG 10 // how many pending connections queue will hold


#define BUF_SIZE 10

int fd_A[BACKLOG]; // accepted connection fd

int conn_amount; // current connection amount


void err(char *s)
{
        printf("%s\n", s);
        exit(0);
}

void set_fl(int fd, int flags)
{
        int val;
        if((val = fcntl(fd, F_GETFL, 0)) < 0)
                err("fcntl");
        val |= flags;
        if((val = fcntl(fd, F_SETFL, val)) < 0)
                err("fcntl2");
}


int main(void)
{
    int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd

        struct sockaddr_in server_addr; // server address information

        struct sockaddr_in client_addr; // connector's address information

        socklen_t sin_size;
        int yes = 1;
        char buf[BUF_SIZE];
        int ret;
        int i;

        if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            err("socket");

        if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0)
        err("setsockopt");
    
        server_addr.sin_family = AF_INET; // host byte order

        server_addr.sin_port = htons(MYPORT); // short, network byte order

        server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP

        memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

        if(bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
        err("bind");

        if(listen(sock_fd, BACKLOG) < 0)
        err("listen");

        printf("listen port %d\n", MYPORT);

        fd_set fdsr;
        int maxsock;
        struct timeval tv;

        conn_amount = 0;
        sin_size = sizeof(client_addr);
        maxsock = sock_fd;
        while(1)
    {
            // initialize file descriptor set

            FD_ZERO(&fdsr);
            FD_SET(sock_fd, &fdsr);

            // timeout setting

            tv.tv_sec = 30;
            tv.tv_usec = 0;

            // add active connection to fd set

            for(i = 0; i < BACKLOG; i++)
        {
                    if(fd_A[i] != 0)
            {
                        FD_SET(fd_A[i], &fdsr);
                    }
               }

            ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
        printf("\nselect\n");
            if(ret < 0)
        {
                    err("select");
            }
        else if(ret == 0)
        {
                    printf("timeout\n");
                    continue;
            }

            // check every fd in the set

            for (i = 0; i < conn_amount; i++)
        {
                    if(FD_ISSET(fd_A[i], &fdsr))
            {    
                while(1)
                {
                    errno = 0;
                    ret = recv(fd_A[i], buf, sizeof(buf) - 1, 0);
                    if(ret < 0)
                    {
                        if(errno == EAGAIN)
                        {
                            printf("EAGAIN\n");
                            break;
                        }
                        else
                        {
                            printf("may not be happen\n");
                            break;
                        }
                    }
                    else if(ret == 0)
                    {
                        printf("client[%d] close\n", i);
                        close(fd_A[i]);
                        FD_CLR(fd_A[i], &fdsr);
                        fd_A[i] = 0;    
                        break;
                    }
                    else
                    { // receive data

                        buf[ret] = 0;
                         printf("client[%d] send:%s\n", i, buf);
                    }
                }
                    }
            }

            // check whether a new connection comes

            if(FD_ISSET(sock_fd, &fdsr))
        {
                    new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
                    if(new_fd <= 0)
                        err("accept");

            set_fl(new_fd, O_NONBLOCK);

            // add to fd queue

            if(conn_amount < BACKLOG)
            {
                fd_A[conn_amount++] = new_fd;
                printf("new connection client[%d] %s:%d\n", conn_amount -1,
                inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                if(new_fd > maxsock)
                    maxsock = new_fd;
            }
            else
            {
                printf("max connections arrive, exit\n");
                send(new_fd, "bye", 4, 0);
                close(new_fd);
                break;
            }
        }
        }

        // close other connections

        for(i = 0; i < BACKLOG; i++)
    {
               if(fd_A[i] != 0)
        {
                    close(fd_A[i]);
            }
        }
        exit(0);
}


运行server
supertool@supertool-desktop:/home/work/test/socket$ ./server
listen port 1234

select
new connection client[0] 127.0.0.1:45324

select
client[0] send:123456789
EAGAIN

select
client[0] send:123456789
client[0] send:123
EAGAIN

select
client[0] close

运行client
supertool@supertool-desktop:/home/work/test/socket$ ./a.out 1234
sock 3
123456789
123456789123
exit

实现了nonblocking+multiplex,在其中 包含一个while循环,它会在一次可读事件发生后,把socket输入缓冲区中的所有可读输 入读取完毕(直到读到发生错误EAGAIN为止)

对上述进行总结,在设计的时候要决定以下两点
1.是否把socket设置为 nonblocking
2.在一次可读的事件发生时,怎么调用read函数(到底调用read多少次)

现在我对此的理解为
一. 如果设置socket为blocking,server在可读事件发生时有两种选择
1. 只读一次

ret = recv(fd, buf, sizeof(buf) - 1, 0);
if(ret <= 0)
{
    printf("client close\n");
    close(fd);
}
else
{
    buf[ret] = 0;
    printf("client send: %s\n", buf);
}

这 种情况,它只读取一次,如果输入缓存区仍有数据未读,则会立 即触发另一个可读事件

2.读多次

while(1)
{
    ret = recv(fd, buf, sizeof(buf) - 1, 0);
    if(ret <= 0)
    {
        printf("client close\n");
        close(fd);
        break;
    }
    else
    {
        buf[ret] = 0;
        printf("client send: %s\n", buf);
        if(ret < sizeof(buf) - 1)
            break;
    }
}

这 种情况在大部分情况下好使,但假如某次用户恰好只输入 size(buf) - 1个数据,则进程陷入阻塞状态,产生这种问题的原因是if(ret < sizeof(buf) - 1)这种判断是有缺陷的,最根本的原因是无法在fd是blocking
状态下判断是否读空了fd的输入缓冲区

二.设置 socket为nonblocking,server在可读事件时有两种选择
1.只读一次

ret = recv(fd, buf, sizeof(buf) - 1, 0)
if(ret < 0)
{
    printf("may not be happen\n");
}
else if(ret == 0)
{
    printf("client close\n");
    close(fd);
}
else
{
    buf[ret] = 0;
    printf("client send: %s\n", buf);
}

2. 读多次

while(1)
{
    errno = 0;
    ret = recv(fd, buf, sizeof(buf) - 1, 0);
    if(ret < 0)
    {
        if(errno == EAGAIN)
        {
            break;
        }
        else
        {
            printf("may not be happen\n");
        }
    }
    else if(ret == 0)
    {
        printf("client close\n");
        close(fd);
    }
    else
    {
        buf[ret] = 0;
        printf("client send: %s\n", buf);
    }
}

这 里的recv和一中的有所不同,它把recv的返回值进行了三种区分,而不是一中的二种区分。

最后说一句,一次可读事件发生后,怎么设计调用recv有所不同,这关系到select调用的次数和对client输入的反应速度问题。一次可读事件发生调用一次recv会增多select调用次数,对系统性能有所影响,而且对client反应有所迟钝。
阅读(1587) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~