select服务器编程综合

来源:互联网 发布:淘宝分享有礼的红包 编辑:程序博客网 时间:2024/06/06 08:25

一、服务器端代码

#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<string.h>#include<sys/time.h>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<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<unistd.h>#include<stdlib.h>#include<netinet/in.h>#include<string.h>#include<sys/time.h>#include<fcntl.h>#include<sys/stat.h>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