select服务器

selcet

特点:
1.用来让我们的程序监视多个文件句柄的状态变化,关心文件描述符的读、写、异常。
2.I/o当中select函数只负责等,一次等待多个文件描述符。
select函数原型inc select(int nfds,fd_set *readfds,fd _set *writefds,fd _set *exceptfds, struct timeval *timeout)
返回值:执行成功返回文件描述符状态已改变个数;如果返回0代表在描述符状态改变前已超过timeout时间,没有返回;有错误发生返回-1;

int nfds最大文件描述符值+1;如果需要关心1号,2号,3号,9号,则应该填10;
后四个参数全部都是输入输出型参数。
最后一个参数struct timeval *timeout虽然是输入输出型参数,但和事件没多大关系, 如果设置为NULL,一直等下去;设置成0就不等(非阻塞轮询);设置为有效时间,隔一段时间醒来一次;

中间三个参数是真正意义上的输入输出型参数,输入指的是关心特定文件描述符的指定的事件(关心哪些事件);输出时代表指定文件描述符上的指定事件已经发生或者没发生(哪些事件发生了);

3.fd叫文件描述符,fd _set叫文件描述符集,用位图表示非常合适。
4.每次调用select,都必须对参数重新设定(因为一个变量被定义输入又是输出)。
5.select能处理的文件描述符个数是有限的。

makefile

select_server:select_server.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f select_server

select_server.c

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




#define SIZE sizeof(fd_set)*8
int fds[SIZE];//定义文件描述符集合的数组,大小为1024;

static void usage(const char *proc)//提示手册,提示你在命令行如何输入
{
    printf("Usage: %s [local_ip] [local_port]\n",proc);
}

//获取文件描述符
int startup(const char *ip, int port)
{
    int sock = socket(AF_INET,SOCK_STREAM, 0);
    if(sock < 0)//创建套接字失败
    {
        perror("socket");
        exit(2);
    }
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//调用bind绑定本地地址和端口(服务器)
    {
        perror("bind");
        exit(3);
    }
    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;

}

int main(int argc, char *argv[])
{
    if(argc != 3)//命令行输入错误
    {
        usage(argv[0]);
        return 1;
    }

//监听套接字:获取是否有连接事件
    int listen_sock = startup(argv[1], atoi(argv[2]));
    //把要接收文件描述符集合的数组所有成员设置为无效
    int nums = sizeof(fds)/sizeof(fds[0]);
    int i = 0;
    for(;i < nums; i++)
    {
        fds[i] = -1;//-1为无效

    }
//
    fds[0] = listen_sock;
    while(1)
    {
        int max_fd = -1;//文件描述符值
        fd_set rfds;//定义读文件描述符集
        FD_ZERO(&rfds);//对文件描述符集初始化,放在循环内,防止select重复调用
        //遍历数组,知道哪些文件描述符需要添加至文件描述符集
        for(i = 0; i < nums; i++)
        {

            if(fds[i] == -1)
            {
                continue;
            }
            FD_SET(fds[i],&rfds);
            if(max_fd < fds[i])
            {
                max_fd = fds[i];
            }
        }
        struct timeval timeout = {0,0};//对timeout进行初始化,timeout参数为结构体类型,&timeout就是0;

//等(select函数)
        switch(select(max_fd+1, &rfds, NULL, NULL, /*&timeout*/NULL))
        {
            case 0:
                printf("timeout ...\n");
                break;
            case -1:
                perror("select");
                break;
            default:
                {
                //at least one fd ready!
                //至少有一个文件描述符就绪

                    for(i = 0; i<nums;i++)
                    {

                        if(fds[i] == -1)
                        {
                            continue;
                        }
                        //listen_sock 就绪,说明来了一个新连接
                        if(i == 0 && FD_ISSET(listen_sock, &rfds))
                        {

                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            int new_sock = accept(listen_sock,\
                                    (struct sockaddr*)&client,\
                                    &len);//调用accept从已连接队列中提取客户连接
                            //accept失败
                            if(new_sock < 0)
                            {
                                perror("accept");
                                continue;
                            }
                            //获得新连接
                            printf("get a new client:[%s:%d]\n",\

                                    inet_ntoa(client.sin_addr),\

                                    ntohs(client.sin_port));
                            //此时并不能直接读写,accept只能保证连接获得,读写事件是否就绪却并不知道(也就是说下一个连接有没有进来)
                            //重新让select负责,添加至读文件描述符集
                            int j = 1;//第一个已被占用,所以j =1;
                            for(; j < nums; j++)
                            {
                                if(fds[j] == -1)
                                {
                                    break;                          
                                }

                            }
                            if(j == nums)//rfds已经满了
                            {
                                printf("client is full!\n");
                                close(new_sock);
                                continue;
                            }
                            else
                            {
                                fds[j] = new_sock;
                            }
                        }else if(i != 0 && FD_ISSET(fds[i], &rfds))//普通套接字,普通事件就绪
                        {
                            //normal fd ready
                            char buf[1024];
                            ssize_t s = read(fds[i],buf,\
                                    sizeof(buf)-1);
                            if(s > 0)//读成功
                            {   
                                buf[s] = 0;
                                printf("client# %s\n",buf);
                            }
                            else if(s == 0)//client关闭
                            {                           
                                close(fds[i]);
                                fds[i] = -1;
                                printf("client is quit!\n");
                            }

                            else//读失败
                            {
                                perror("read");
                                close(fds[i]);
                                fds[i] = -1;
                            }

                        }
                        else
                        {
                            //文件描述符没有就绪

                        }               
                    }
                }
                break;
        }
    }
    return 0;

}

select_client.c

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

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

//./tcp_client server_ip server_port

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        return 1;
    }
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }


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

    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
    {
        perror("connect");
        return 3;

    }

    char buf[1024];
    while(1)
    {
        printf("Please Enter#");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf) - 1);
        if(s > 0)
        {
            buf[s-1]= 0;
            write(sock, buf, strlen(buf));
            //ssize_t _s = read(sock, buf, sizeof(buf)-1);
            //if(_s > 0)
            //{

            //  buf[_s] = 0;

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

    }

    close(sock);

    return 0;
}

select服务器优缺点(与多进程/多线程服务器进行对比)

与多进程/多线程服务器进行对比 它的优点在于:
1)不需要建立多个线程、进程(占资源少,单进程)就可以实现一对多的通信。
2)可以同时等待多个文件描述符,用户量较多时,效率比起多进程多线程来说要高很多。

与多进程/多线程服务器进行对比 它的缺点在于:

1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 ,循环次数有点多;
2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 。
3)select支持的文件描述符数量是有上限的,默认是1024;
4)select是输入输出型参数,输入输出是同一个变量,会频繁设定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值