小型tcp服务器--select

在前面的博客中,我用多线程,多进程编写了一个小型服务器,之所以会使用多进程/多线程是因为服务器往往需要服务多个客户端,提高并发性和效率是十分重要的,然而缺点是系统开销较大,系统需要创建多个进程/线程。

然而,现实生活中,一个程序,或者一个网络服务器的真正的性能瓶颈往往在IO,事实上,网络IO进行读写数据之时,往往因为就绪条件不满足而出于等待的状态,这大大就降低了我们性能。

对于网络IO,可分为两步:

I/O请求分两步:
1)进行等待:等待读或者写事件准备就绪。
2)进行数据搬迁:将数据从存储介质拷贝到内存缓冲区,此时数据已经准备好了,可以被用户应用程序进行读写,进行用户应用程序拷贝内核缓冲区中的数据到用户缓冲区。

为了提高性能,如果让等待的时间少一点就好了。IO复用(多路转接)正是这样的一种计数帮我们减少等待时间,提高性能的技术。那么它是基于什么样的原理呢?

I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
但select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

打个比方,就好比钓鱼,本来是拿一个鱼竿钓鱼,现在我拿100个鱼竿钓鱼,是不是等待的时间相对就少,钓到鱼的可能性就大了。

今天,我们主要实现一个基于select的tcp服务器,其实和之前多线程多进程服务器很相似的~

makefile

.PHONY:all
all:server_select client_select
client_select:client_select.c
    gcc -o $@ $^
server_select:server_select.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f server_select

server

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

#define N (sizeof(fd_set)*8)

int fd_array[N];

static void usage(const char* s)
{
    printf("correct usage : %s [local_ip] [local_port]\n", s);
}

int get_socket(const char* ip, const char* port)
{
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0)
    {
        fprintf(stderr, "socket failure\n");
        exit(1);
    }

    int opt = 1;
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
    {
        fprintf(stderr, "setsockopt failure\n");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(port));
    inet_aton(ip, &local.sin_addr); 
    // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    //
    if (bind(sock_fd, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        fprintf(stderr, "bind failure\n");
        exit(2);
    }
    if (listen(sock_fd, 6) < 0)
    {
        fprintf(stderr, "listen failure\n");
        exit(3);
    }
    return sock_fd;
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(0);
    }

    int sock_fd = get_socket(argv[1], argv[2]); 

    int index = 0;
    for (; index < N; ++index)
    {
        fd_array[index] = -1;
    }
    fd_array[0] = sock_fd;
    int max_fd = -1;

    struct timeval timeout = {5, 0}; //3s
    // void FD_CLR(int fd, fd_set *set);
    // int  FD_ISSET(int fd, fd_set *set);
    // void FD_SET(int fd, fd_set *set);
    // void FD_ZERO(fd_set *set);
    //
    while (1)
    {
        fd_set my_fd_set;
        FD_ZERO(&my_fd_set);

        int i = 0;
        for (;i < N; i++)
        {
            if (fd_array[i] > 0)
            {
                FD_SET(fd_array[i], &my_fd_set);
                if (fd_array[i] > max_fd)   
                {
                    max_fd = fd_array[i];
                }
            }
        }
        // int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 
        int flag = select(max_fd+1, &my_fd_set, NULL, NULL, 0);//最后一个参数表示是否阻塞等,0表示阻塞,timeout表示timeout时间等待

        switch (flag)
        {
        case 0:
            printf("timeout...\n");
            break;
        case -1:
            printf("select error...\n");
            break;
        default:
            {
                for (i = 0; i < N; i++)
                {
                    if (fd_array[i] < 0)
                    {
                        continue;
                    }

                    //找到一个有效的fd进行操作
                    if (i == 0 && FD_ISSET(sock_fd, &my_fd_set))
                    {
                        //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
                        struct sockaddr_in client;
                        socklen_t len = sizeof(client);
                        int new_fd = accept(sock_fd, (struct sockaddr*)&client, &len);
                        if (new_fd < 0)
                        {
                            //fprintf(stderr, "accept failure\n");
                            continue;
                        }
                        char* ip = inet_ntoa(client.sin_addr);
                        int port = ntohs(client.sin_port);
                        printf("get a new client %s:%d\n", ip, port);

                        int j = 1;
                        for (; j < N; j++)
                        {
                            if (fd_array[j] == -1)
                                break;
                        }
                        if (j == N)
                        {
                            printf("server is full\n");
                            close(new_fd);
                        }
                        else
                        {
                            fd_array[j] = new_fd;
                        }
                    }
                    else if (i > 0 && FD_ISSET(fd_array[i], &my_fd_set)) //如果是普通套接字
                    {
                        //读取client数据
                        char buf[1024];
                        ssize_t s = read(fd_array[i], buf, sizeof(buf)-1);
                        if (s > 0)
                        {
                            buf[s] = '\0';
                            printf("client # %s\n", buf);
                            write(fd_array[i], buf, strlen(buf));
                        }
                        else if (s == 0)
                        {
                            printf("client quits\n");
                            close(fd_array[i]);
                            fd_array[i] = -1;
                        }
                        else
                        {
                            fprintf(stderr, "read error\n");
                        }
                    }
                    else
                    {}
                }

            }//default
            break;
        }
    }

    return 0;
}

client

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



static void usage(const char* s)
{
    printf("correct usage : %s [remote_ip] [remote_port]\n", s);
}

int get_socket(const char* ip, const char* port)
{
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0)
    {
        fprintf(stderr, "socket failure\n");
        exit(1);
    }

    struct sockaddr_in remote;
    remote.sin_family = AF_INET;
    remote.sin_port = htons(atoi(port));
    inet_aton(ip, &remote.sin_addr);

    // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    int flag = connect(sock_fd, (struct sockaddr*)&remote, sizeof(remote));
    if (flag < 0)
    {
        fprintf(stderr, "connect failure\n");
        exit(2);
    }
    return sock_fd;
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(0);
    }

    int sock_fd = get_socket(argv[1], argv[2]); 


    //利用重定向输出到网络
    //dup()

    char buf[1024];
    while (1)
    {
        memset(buf, '\0', sizeof(buf));
        printf("please enter # ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if (s > 0)
        {
            buf[s-1] = '\0';
            write(sock_fd, buf, strlen(buf)); 
            s = read(sock_fd, buf, sizeof(buf)-1);
            if (s > 0)
            {
                printf("server echo # %s\n", buf);
            }
        }
    }

}

写完select之后,我们总结一下它的优缺点:

优点:

系统开销小,不必创建多线程多进程;
属于IO复用的一种,因此效率比较好,IO的等待时间相对减小了
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点

缺点:

单个进程可监视的fd数量被限制,默认是1024
需要维护一个用来存放大量fd的数据结构(比如数组),这样会使得用户空间和内核空间在传递该结构时复制开销大
每次调用fd都是线性扫描的,当fd个数很多的时候,效率不高

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值