小型tcp服务器--select

Posted xy913741894

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小型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个数很多的时候,效率不高

以上是关于小型tcp服务器--select的主要内容,如果未能解决你的问题,请参考以下文章

[Linux 高并发服务器]TCP通信流程

C ++选择函数过早中断[重复]

TCP服务器如何使用select处理多客户连接

select---基于TCP客户/服务端编程

我将 tcp 用于非常多的小型发送,我应该关闭 Nagles 算法吗? (人们也将其称为 TCP_NODELAY)

利用select实现IO多路复用TCP服务端