Back
Featured image of post 简易Tcp服务器

简易Tcp服务器

基础知识

Tcp服务器

一请求一线程

首先说明问题: 已请求一线程能承载的请求数量极少,posix标准线程8M,请求数量多时极其占用内存

简单实现 实现一请求一线程很简单:

#define BUFFER_SIZE 1024
void *client_routine(void *arg)
{
    int clientfd = *(int *) arg;
    while(1)
    {
        char buffer[BUFFER_SIZE] = {0};
        int len = recv(clientfd, buffer, BUFFER_SIZE, 0);
        if(len <0 )
        {
            close(clientfd);
            break;
        }
        else if(len ==0 )
        {
            close(clientfd);
            break;
        }
        else{
            printf("Recv: %s, %d btye(s) from %d\n", buffer, len, clientfd);
        }
        
    }
}

int main(int argc, char *argv[])
{
    if(argc <2)
    return -1;

    int port = atoi(argv[1]);
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) <0)
    {
        perror("bind\n");
        return 2;
    }

    if(listen(sockfd, 5) <0 )
    {
        perror("listen\n");
        return 3;
    }

    while(1)
    {
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        socklen_t client_len = sizeof(client_addr);

        int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);

        pthread_t thread_id;
        
        pthread_create(&thread_id, NULL, client_routine, &clientfd);
        
    }

    return 0;



}

main函数中首先在指定端口处打开一个迎宾套接字sockfd用于对到来的请求分配线程(新建客户端套接字)来处理

int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); 就是迎宾套接字不断检测是否有新请求到来,如果到来就返回clientfd 后面新建立的线程将clientfd作为参数传递给线程执行函数即可: pthread_create(&thread_id, NULL, client_routine, &clientfd);

Epoll实现Tcp服务端

使用epoll来管理多个io到来的请求然后依次处理这些请求

首先理清逻辑

还是通过迎宾套接字来捕获请求,只是迎宾套接字应该首先加入epoll中 外层循环需要对epoll中有输入的套接字(io)遍历,如果有输入(有请求),就处理,这里需要判断套接字的类型:如果是迎宾套接字说明有新的请求,需要通过accpet来创建clientfd并交给epoll管理,如果不是就处理请求,处理完毕后从epoll中删除套接字(io),所以这个逻辑下可以知道,到来的请求是在下一轮中才处理的,并不是一到来就立即处理

C实现

#define EPOLL_SIZE 1024


#define BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
    if(argc <2)
    return -1;

    int port = atoi(argv[1]);
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) <0)
    {
        perror("bind\n");
        return 2;
    }

    if(listen(sockfd, 5) <0 )
    {
        perror("listen\n");
        return 3;
    }

    int epfd = epoll_create(1);

    struct epoll_event events[EPOLL_SIZE] = {0};
    
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    
    while(1)
    {
        int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5);
        //-1 events里没东西就不去
        if(nready == -1) continue;
        int i=0;
        for(i = 0;i <nready;++i)
        {
            if (events[i].data.fd == sockfd)
            { //迎宾的sock那就新产生一个clientfd然后交给epoll
                struct sockaddr_in client_addr;
                memset(&client_addr, 0, sizeof(struct sockaddr_in));
                socklen_t client_len = sizeof(client_addr);

                int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
                
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
            }
            else
            {
                int clientfd = events[i].data.fd;
                char buffer[BUFFER_SIZE] = {0};
                int len = recv(clientfd, buffer, BUFFER_SIZE, 0);
                if(len <0 )
                {
                    close(clientfd);
                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                    
                }
                else if(len ==0 )
                {
                    close(clientfd);
                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                }
                else{
                    printf("Recv: %s, %d btye(s) from %d\n", buffer, len, clientfd);
                }

            }
        }   
    }
    return 0;
}

关键的函数是epoll_ctl,epoll_create,epoll_wait

epoll_create的参数是一个int size这个参数在新版本linux内核中无意义,原来也只是告诉内核epoll大致的大小 epoll_ctl配合EPOLL_CTL_xxx的宏来对epoll进行操作(添加删除修改) epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 用来等待事件发生,其他参数比较简单,只说timeout参数:在没有检测到事件发生时最多等待的时间(单位为毫秒)

ET、LT工作模式 水平触发模式:

ev.events = EPOLLIN;

边缘触发模式:

ev.events = EPOLLIN | EPOLLET;

设置好触发模式后可以把event加入epoll中:

struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy