select服务器编程综合

一、服务器端代码

#include
#include
#include
#include
#include
#include
#include
#include

static void Usage(const char *str)
{
    printf("usage: %s [server_ip][server_port]\n",str);
}

static int startup(const char *ip,int port)
{
    int new_socket = socket(AF_INET,SOCK_STREAM,0);
    if(new_socket < 0)
    {
        perror("socket");
        exit(2);
    }

    int op = 1;
    int ret = setsockopt(new_socket,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(op));
    if(ret < 0)
    {
        perror("setsockopt");
        exit(3);
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = inet_addr(ip);

    ret = bind(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret < 0)
    {
        perror("bind");
        exit(4);
    }
    ret = listen(new_socket,128);
    if(ret < 0)
    {
        perror("listen");
        exit(5);
    }

    return new_socket;

}

int array_fds[1024];  //定义一个全局数组。这个全局变量存放的是文件描述符。
int max_fd;   //最大的文件描述符。

int main(int argc ,char *argv[])
{

    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    int i = 0;
    for(; i < 1024; ++i)
        array_fds[i] = -1;   //将数组中每一个元素都置为-1.

    int listen_sock = startup(argv[1],atoi(argv[2]) );

    array_fds[0] = listen_sock;  //将监听套件字放在数组中0号位置。
    fd_set reads;  //创建只读集合。

    struct timeval timeout; 

    while(1) //服务器一直处于服务状态。
    {         //  int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);后面四个参数都是输入输出参数。

        FD_ZERO(&reads);  //因为select是输入输出参数,当select返回时,已经改变了read这个集合了,下次还需要监听read这个集合中的可读事件的话就需要重新赋值。

        max_fd = -1;  //每次这个得重新初始化

        timeout.tv_sec = 10;  //定时10秒,这个参数也是输入输出参数
        timeout.tv_usec = 0;

        for(i = 0; i < 1024; ++i)//遍历第三方数组
        {
            if(array_fds[i] >= 0)
            {
                FD_SET(array_fds[i], &reads); //将监听套接字加入到可读事件中。
                if(array_fds[i] > max_fd)
                    max_fd = array_fds[i];
            }
        }

        //准备工作做好后,开始真正的监听了。
        int j  = 0;
        switch(select(max_fd+1 ,&reads,NULL,NULL,&timeout))
        {
            case 0:
                printf("time out....\n");
                break;
            case -1:
                perror("select");
                exit(6);
            default: //有可读事件发生,但是不知道是那一个可读事件,需要遍历数组,查看数组中存放的描述符那一个可读了。

                for(; j < 1024; ++j)
                {
                    if(array_fds[j] < 0) //-1表示这个文件描述符没有可读事件发生。
                        continue;

                    char buf[BUFSIZ]; //接收数据缓冲区。

                    if(j== 0 && FD_ISSET(array_fds[0],&reads)) //监听套接字有可读事件发生,表示有客户连接了。
                    {
                        struct sockaddr_in clie_addr;
                        socklen_t  len = sizeof(clie_addr);
                        int connect_fd = accept(array_fds[0],(struct sockaddr*)&(clie_addr),&len);
                        if(connect_fd < 0)
                        {
                            perror("accept");
                            continue;  //这次连接失败,让它下次连接。
                        }
                        printf("get a new client :(%s:%d)\n",inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port));
                        //连接套接字后继续监听,看这个套接字是不是有数据发送。
                        int k = 0;
                        for(; k < 1024; ++k)
                        {
                            if(array_fds[k] == -1)
                            {
                                array_fds[k] = connect_fd;
                                break;
                            }
                        }

                    }  //if
                    else if(j != 0 && FD_ISSET(array_fds[j],&reads))
                    {
                        printf("======================read start==========\n");
                        ssize_t  s = read(array_fds[j],buf,sizeof(buf) - 1);
                        if(s < 0)
                        {
                            perror("read");
                            close(array_fds[j]);
                            array_fds[j] = -1;  //数组重新利用
                            break;
                        }
                        else if(s == 0)
                        {
                            printf("clinet quit\n");
                            close(array_fds[j]);
                            array_fds[j] = -1;  //数组重新利用
                            break;
                        }
                        else
                        {
                            buf[s]  = 0;
                            printf("clinet say:%s\n",buf);
                        }   
                    }


                }//for结束
        } //switch结束
    } //while(1)死循环

    return 0;

}

二、客户端代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

static void Usage(const char *str)
{
    printf("usage: %s [server_ip][server_port]\n",str);
}

int main(int argc, char*argv[] )
{

    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }


    int new_socket = socket(AF_INET,SOCK_STREAM,0);
    if(new_socket < 0)
    {
        perror("socket");
        exit(2);
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi (argv[2]) );
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]); 

    int ret = connect(new_socket,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret < 0)
    {
        perror("connect");
        exit(3);
    }

    char buf[BUFSIZ];

    while(1)
    {
        printf("please enter#:");
        fflush(stdout);

        ssize_t s = read(0,buf,sizeof(buf) - 1);
        if(s > 0)
        {
            int outfd = dup(1);  //outfd 指向标志输出(保存标准输出好恢复)。

            buf[s-1] = 0;  //去掉换行符。
        //  write(new_socket,buf,strlen(buf));
            dup2(new_socket, 1);  //1号文件描述符去指向new_socket指向的内容了。
            printf("%s",buf);  //本来是将buf中的东西写入到标准输出中,但是现在1号描述符已经重新定向了,指向套接字的缓冲区,所以现在就是讲buf中的东西写入到套接字缓冲区了。
            fflush(stdout);

            dup2(outfd, 1);  //让1重新指向标准输出。
        }
        else
            break;

//      ssize_t s2 = read(new_socket,buf,sizeof(buf) - 1);
//      buf[s2] = 0;

//      printf("sever echo # %s\n",buf);

    }

    close(new_socket);

    return 0;
}

三、与其他类型服务器对比

多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。线程间内存无法共享,因为所有线程处在同一个地址空间中。
多进程模型擅长处理并发长连接,但却不适用于连接频繁产生和关闭的情形

select服务器
优点
(1)select()的可移植性更好
(2)select() 对于超时值提供了更好的精度:微秒,而poll是毫秒
缺点
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 
(3)select支持的文件描述符数量太小了,默认是1024

你可能感兴趣的:(编程脚印,服务器,select,编程)