web服务器(线程池实现)

我们先要准备线程池的模板参数类,封装对逻辑任务的处理

先是三个同步机制的封装

#ifndef LOCKER_H
#defien LOCKER_H

#include<exception>
#include<pthread.h>
#include<semaphore.h>

class sem
{
pubilc:
    创建初始化
    sem()
    {
        if(sem_init(&m_sem,0,0)!= 0)
        {
            throw std::exception();
        }
    }
    ~sem()
    {
        sem_destroy(&m_sem);
    }
    等待
    bool wait()
    {
        return sem_wait(&m_sem) === 0;
    }
    增加
    bool post()
    {
        return sem_post(&m_sem) == 0;
    }
private:
    sem_t m_sem;
};

class locker
{
public:
    locker()
    {
        if(pthread_mutex_init(&m_mutex,NULL)!= 0)
        {
            throw std::exception();
        }
    }
    ~locker()
    {
        pthread_mutex_destory(&m_mutex);
    }
    获取
    bool lock()
    {
        return pthread_mutex_lock(&m_mutex) == 0;
    }
    bool unlock()
    {
        return pthread_mutex_unlock(&m_mutex) == 0;
    }
private:
    pthread_mutex_t m_mutex;
};

class cond
{
pubilc:
    cond()
    {
        if(pthread_mutex_init(&m_mutex,NULL)!= 0)
        {
            throw std::exception();
        }
        if(pthread_cond_init(&m_cond,NULL)!= 0)
        {
            pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }
    ~cond()
    {
        pthread_mutex_destroy(&m_mutex);
        pthread_cond_destroy(&m_cond);
    }
    等待条件变量
    bool wait()
    {
        int ret = 0;
        pthread_mutex_lock(&m_mutex);
        ret = pthread_cond_wait(&m_cond,&m_mutex);
        pthread_mutex_unlock(&m_mutex);
        return ret = 0;
    }
    唤醒
    bool signal()
    {
        return pthread_cond_signal(&m_cond) == 0;
    }
private:
pthread_mutex_t m_mutex;
pthread_cond_t cond;
};

#endif

下来是

#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H

#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<sys/stat.h>
#include<string.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<stdarg.h>
#include<errno.h>
#include"locker.h"

class http_conn
{
public:
    /*文件的最大长度*/
    static const int FILENAME_LEN = 200;
    /*读缓冲区大小*/
    static const int READ_BUFFER_SIZE = 2048;
    /*写缓冲区大小*/
    static const int WRITE_BUFFER_SIZE = 1024;
    /*HTTP请求方法,我们仅支持GET*/
    enum METHOD{ GET = 0 ,POST, HEAD, PUT, DELETE,
                TARCE,OPTIONS,CONNECT,PATCH};
    /*解析客户请求,主状态机的状态*/
    enum CHECK_STATE{ CHECK_STATE_REQUESTLINE = 0,
                    CHECK_STATE_HEADER,
                    CHECK_STATE_CONTENT};
    /*服务器处理请求的可能结果*/
    enum HTTP_CODE{ NO_REQUEST,GET_REQUEST,BAD_REQUEST,
                    NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,
                    INTERNAL_ERROR,CLOSED_CONNECTION};
    /*行的读取状态*/
    enum LINE_STATUS { LINE_OK = 0,LINE_BAD,LINE_OPEN };
    
public:
    http_conn(){}
    ~http_conn(){}
public:
    void init(int sockfd,const sockaddr_in&addr);/*初始化新接受的连接*/
    void close_conn(bool real_close = true);
    /*处理客户请求*/
    void process();
    /*非阻塞读操作*/
    bool read();
    bool write();
private:
    /*初始化连接*/
    void init();
    /*解析HTTP请求*/
    HTTP_CODE process_read();
    /*填充HTTP应答*/
    bool process_write(HTTP_CODE ret);
    
    /*这一组函数被process_read调用来分析请求*/
    HTTP_CODE parse_request_line(char* text);
    HTTP_CODE parse_headers(char* text);
    HTTP_CODE parse_content(char* text);
    HTTP_CODE do_request();
    char* get_line(){return m_read_buf + m_start_line;}
    LINE_STATUS parse_line();
    
    /*这组用来被process_write填充HTTP应答*/
    void unmap();
    bool add_response(const char* format,...);
    bool add_content(const char* content);
    bool add_status_line(int status,const char* title);
    bool add_headers(int content_length);
    bool add_content_length(int content_length);
    bool add_linger();
    bool add_blank_line();
    
public:
    static int m_epollfd;
    static int m_user_count;

private:
    /*该HTTP连接的socket和对方的socket地址*/
    int m_sockfd;
    sockaddr_in m_address;
    
    char m_read_buf[READ_BUFFER_SIZE];
    /*标识读缓冲区中已经读入的客户数据的最后一个字节的下一位置*/
    int m_read_idx;
    /*当前正在分析的字符在读缓冲区的位置*/
    int m_checked_idx;
    /*当前正在解析的行的起始位置*/
    int m_start_line;
    
    char m_write_buf[WRITE_BUFFER_SIZE];
    /*写缓冲区待发送的字节数*/
    int m_write_idx;
    
    /*主状态机所处状态*/
    CHECK_STATE m_check_state;
    /*请求方法*/
    METHOD m_method;
    
    /*客户请求的目标文件完整路径*/
    char m_real_file[FILENAME_LEN];
    /*目标文件名*/
    char* m_url;
    /*HTTP协议版本号,我们只用1.1*/
    char* m_version;
    char* m_host;
    /*请求消息体的长度*/
    int m_content_length;
    /*是否要求保持连接*/
    bool m_linger;
    
    /*客户请求的目标文件被mmap到内存的起始位置*/
    char* m_file_address;
    /*目标文件的状态,判断是否存在,目录,可读?文件大小*/
    struct stat m_file_stat;
    /*用writev执行写操作*/
    struct iovec m_iv[2];
    int m_iv_count;
};

#endif

再就是http处理阶段

#include "http_conn.h"

/*定义HTTP响应的状态信息*/
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is         inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The request file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";

/*网站根目录*/
const char* doc_root = "/var/www/html";

int setnonblocking(int fd)
{
    int old_option = fcntl(fd,F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
}

void addfd(int epollfd,int fd,bool one_shot)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    if(one_shot)
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    setnonblocking(fd);
}

void removefd(int epollfd,int fd)
{
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
    close(fd);
}

void modfd(int epollfd,int fd,int ev)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}

int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;

void http_conn::close_conn(bool real_close)
{
    if(real_close && (m_sockfd != -1))
    {
        removefd(m_epollfd,m_sockfd);
        m_sockfd = -1;
        m_user_count--;/*关闭一个连接,用户-1*/
    }
}

void http_conn::init(int sockfd,const sockaddr_in& addr)
{
    m_sockfd = sockfd;
    m_address = addr;
    /*为了避免TIME_WAIT状态,仅用于调试,使用时应去掉*/
    int reuse = 1;
    setsockopt(m_sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    addfd(m_epollfd,sockfd,true);
    m_user_count++;
    
    init();
}

void http_conn::init()
{
    m_check_state = CHECK_STATE_REQUESTLINE;
    m_linger = false;
    
    m_method = GET;
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    memset(m_read_buf,'\0',READ_BUFFER_SIZE);
    memset(m_write_buf,'\0',WRITE_BUFFER_SIZE);
    memset(m_real_file,'\0',FILENAME_LEN);
}

http_conn::LINE_STATUS http_conn::parse_line()
{
    char temp;
    for(;m_checked_idx < m_read_idx;++m_checked_idx)
    {
        temp = m_read_buf[m_checked_idx];
        if(temp == '\r')
        {
            if((m_checked_idx + 1)== m_read_idx)
            {
                return LINE_OPEN;
            }
            else if(m_read_buf[m_checked_idx+1]=='\n')
            {
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if(temp == '\n')
        {
            if((m_checked_idx > 1)&&(m_read_buf[m_checked_idx-1]=='\r'))
            {
                m_read_buf[m_checked_idx-1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

/*循环读取客户数据,直到无数据可读或对方关闭连接*/
bool http_conn::read()
{
    if(m_read_idx >= READ_BUFFER_SIZE)
    {
        return false;
    }
    int bytes_read = 0;
    while(true)
    {
        bytes_read = recv(m_sockfd,m_read_buf + m_read_idx,READ_BUFFER_SIZE - m_read_idx,0);
        if(bytes_read == -1)
        {
            if(errno == EAGAIN || errno == EWOULDBLOCK)
            {
                break;
            }
            return false;
        }
        else if(bytes_read == 0)
        {
            return false;
        }
        m_read_idx += bytes_read;
    }
    return true;
}

/*解析HTTP请求行,获得请求方法,目标URL,版本号*/
http_conn::HTTP_CODE http_conn::parse_request_line(char* text)
{
    m_url = strpbrk(text,"\t");
    if(!m_url)
    {
        return BAD_REQUEST;
    }
    *m_url++ = '\0';
    
    char* method = text;
    if(strcasecmp(method,"GET")== 0)
    {
        m_method = GET;
    }
    else
    {
        return BAD_REQUEST;
    }
    
    m_url += strspn(m_url,"\t");
    m_version = strpbrk(m_url,"\t");
    if(!m_version)
    {
        return BAD_REQUEST;
    }
    *m_version++ = '\0';
    m_version += strspn(m_version,"\t");
    if(strcasecmp(m_version,"HTTP/1.1"!= 0)
    {
        return BAD_REQUEST;
    }
    if(strncasecmp(m_url,"http://",7)== 0)
    {
        m_url += 7;
        m_utl = strchr(m_url,'/');
    }
    if(!m_url || m_url[0]!= '/')
    {
        return BAD_REQUEST;
    }
    m_checked_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

/*分析头部字段*/
http_conn::HTTP_CODE http_conn::parse_headers(char* text)
{
    /*遇到空行,表示头部字段解析完毕*/
    if(text[0] == '\0')
    {
        /*如果HTTP请求有消息体,则还要读m_content_length字节的消息体,
        状态机转移到CHECK_STATE_CONTENT*/
        if(m_content_length != 0)
        {
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }
    /*处理connection头部字段*/
    else if(strncasecmp(text,"Connection:",11)== 0)
    {
        text += 11;
        text += strspn(text,"\t");
        if(strcasecmp(text,"keep-alive") == 0)
        {
            m_linger = true;
        }
    }
    else if(strncasecmp(text,"Content-length:",15)== 0)
    {
        text += 15;
        text += strspn(text,"\t");
        m_content_length = atol(text);
    }
    else if(strncasecmp(text,"Host:",5) == 0)
    {
        text += 5;
        text += strspn(text,"\t");
        m_host = text;
    }
    else
    {
        printf("oop! unknow header %s\n",text);
    }
    return NO_REQUEST;
}

/*没有真正解析HTTP请求的消息体,只是判断它是否完整的读入*/
http_conn::HTTP_CODE http_conn::parse_content(char* text)
{
    if(m_read_idx >= (m_content_length + m_checked_idx))
    {
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

/*主状态机*/
http_conn::HTTP_CODE http_conn::parse_read()
{
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;
    
    while(((m_check_state == CHECK_STATE_CONTENT)&&(line_status == LINE_OK)) || ((line_status = parse_line())== LINE_OK))
    {
        text = get_line();
        m_start_line = m_checked_idx;
        printf("got l http line :%s\n",text);
        
        switch(m_check_state)
        {
            case CHECK_STATE_REQUESTLINE:
            {
                ret = parse_request_line(text);
                    if(ret == BAD_REQUEST)
                    {
                        return BAD_REQUEST;
                    }
                    break;
            }
            case CHECK_STATE_HREAER:
            {
                 ret = parse_headers(text)
                    if(ret == BAD_REQUEST)
                    {
                        return BAD_REQUEST;
                    }
                    else if(ret == GET_REQUEST)
                    {
                        return do_request();
                    }
                    break;
             }
                case CHECK_STATE_CONTENT:
                {
                    ret = parse_content(text)
                    if(ret == GET_REQUEST)
                    {
                        return do_request();
                    }
                    line_status = LINE_OPEN;
                    break;
                }
                default:
                {
                    return INTERNAL_ERROR;
                }
        }
        
    }
    return NO_REQUEST;
}

/*当得到一个完整,正确的HTTP请求,就分析目标文件的属性。目标文件存在的话
,对所有用户可读,且不是目录,就用mmap将其映射到内存地址m_file_address处
告诉调用者文件获取成功*/
http_conn::HTTP_CODE http_conn::do_request()
{
    strcpy(m_real_file,doc_root);
    int len = strlen(doc_root);
    strncpy(m_real_file + len,m_url,FILENAME_LEN - len -1);
    if(stat(m_real_file,&m_file_stat)<0)
    {
        return NO_REQUEST;
    }
    if(!(m_file_stat.st_mode&S_IROTH))
    {
        return FORBIDDEN_REQUEST;
    }
    if(S_ISDIR(m_file_stat.st_mode))
    {
        return BAD_REQUEST;
    }
    int fd = open(m_real_file,O_RDONLY);
    m_file_address = (char*)mmap(0,m_file_stat.st_size,PROT_READ,MAP_PRIVATE,fd,0);
    close(fd);
    return FILE_REQUEST;
}

void http_conn::unmap()
{
    if(m_file_address)
    {
        munmap(m_file_address,m_file_stat.st_size);
        m_file_address = 0;
    }
}

/*写HTTP响应*/
bool http_conn::write()
{
    int temp = 0;
    int bytes_have_send = 0;
    int bytes_to_send = m_write_idx;
    if(bytes_to_send == 0)
    {
        modfd(m_epollfd,m_sockfd,EPOLLIN);
        init();
        return true;
    }
    while(1)
    {
        temp = writev(m_sockfd,m_iv,m_iv_count);
        if(temp <= -1)
        {
            /*如果tcp写缓冲没空间,则等待下一轮EPOLLOUT*/
            if(errno == EAGAIN)
            {
                modfd(m_epollfd,m_sockfd,EPOLLPOUT);
                return true;
            }
            unmap();
            return false;
        }
        bytes_to_send -= temp;
        bytes_have_send += temp;
        if(bytes_to_send <= bytes_have_send)
        {
            /*发送HTTP响应成功,根据请求中的字段决定是否关闭连接*/
            unmap();
            if(m_linger)
            {
                init();
                modfd(m_epollfd,m_sockfd,EPOLLIN);
                return true;
            }
            else
            {
                modfd(m_epollfd,m_sockfd,EPOLLIN);
                return false;
            }
        }
    }
}

/*往写缓冲写入待发送数据*/
bool http_conn::add_response(const char* fromat,...)
{
    if(m_write_idx >= WRITE_BUUFER_SIZE)
    {
        return fales;
    }
    va_list arg_list;
    va_start(arg_list,format);
    int len = vsnprintf(m_write_buf + m_write_idx,WRITE_BUFFER_SIZE -1 -m_write_idx,format,arg_list);
    if(len >= (WRITE_BUFFER_SIZE -1 -m_write_idx))
    {
        return false;
    }
    m_write_idx += len;
    va_end(arg_list);
    return true;
}

bool http_conn::add_status_line(int status,const char* title)
{
    return add_response("%s %d %s\r\n","HTTP/1.1",status,title);
}

bool http_conn::add_headers(int content_len)
{
    add_content_length(content_len);
    add_linger();
    add_blank_line();
}

bool http_conn::add_content_length(int content_len)
{
    return add_response("Content-length:%d\r\n",content_len);
}

bool http_conn::add_linger()
{
    return add_response("Connection:%s\r\n",(m_linger == true)? "keep-alive" : "close");
}

bool http_conn::add_blank_line()
{
    return add_response("%s","\r\n");
}

bool http_conn::add_content(const char* content)
{
    return add_response("%s",content);
}

/*根据服务器处理请求的结果,决定返回给客户端的内容*/
bool http_conn::process_write(HTTP_CODE ret)
{
    switch(ret)
    {
        case INTERNAL_ERROR:
        {
            add_status_line(500,error_500_title);
            add_headers(strlen(error_500_form));
            if(!add_content(error_500_form))
            {
                return false;
            }
            break;
        }
        case BAD_REQUEST:
        {
            add_status_line(400,error_400_title);
            add_headers(strlen(error_400_form));
            if(!add_content(error_400_form))
            {
                return false;
            }
            break;
        }
        case NO_REQUEST:
        {
            add_status_line(404,error_404_title);
            add_headers(strlen(error_404_form));
            if(!add_content(error_404_form))
            {
                return false;
            }
            break;
        }
        case FORBIDDEN_REQUEST:
        {
            add_status_line(403,error_403_title);
            add_headers(strlen(error_403_form));
            if(!add_content(error_403_form))
            {
                return false;
            }
            break;
        }
        case FILE_REQUEST:
        {
            add_status_line(200,ok_200_title);
            if(m_file_stat.st_size != 0)
            {
                add_headers(m_file_stat.st_size);
                m_iv[0].iov_base = m_write_buf;
                m_iv[0].iov_len = m_write_idx;
                m_iv[1].iov_base = m_file_address;
                m_iv[1].iov_len = m_file_stat.st_size;
                m_iv_count = 2;
                return true;
            }
            else
            {
                const char* ok_string = "<html><body></body></html>";
                add_headers(strlen(ok_string));
                if(!add_content(ok_string))
                {
                    return false;
                }
            }
        }
        default:
        {
            return false;
        }
    }
    m_iv[0].iov_base = m_write_buf;
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    return true;
}

/*由线程池的工作线程调用,处理HTTP请求的入口函数*/
void http_conn::process()
{
    HTTP_CODE read_ret = process_read();
    if(read_ret == NO_REQUEST)
    {
        modfd(m_epollfd,m_sockfd,EPOLLIN);
        return ;
    }
    
    bool write_ret = process_write(read_ret);
    if(!write_ret)
    {
        close_conn();
    }
    modfd(m_epollfd,m_sockfd,EPOLLOUT);
}

最后是main()函数

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<cassert>
#include<sys/epoll.h>

#include"locker.h"
#include"threadpool.h"
#include"http_conn.h"

#define MAx_FD 65536
#define MAX_EVENT_NUMBER 10000

extern int addfd(int epollfd,int fd,bool one_shot);
extern int removefd(int epollfd,int fd);

void addsig(int sig,void(handler)(int),bool restart = true)
{
    struct sigaction sa;
    memset(&sa,'\0',sizeof(sa));
    sa.sa_handler = handler;
    if(restart)
    {
        sa.sa_flags |= SA_RESTART;
    }
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig,&sa,NULL)!= -1);
}

void show_error(int connfd,const char* info)
{
    printf("%s",info);
    send(connfd,info,strlen(info),0);
    close(connfd);
}

int main(int argc,char* argv[])
{
    if(argc <= 2)
    {
        printf("usage:%s ip_address port_number\n",basename(argv[0]);
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    
    addsig(SIGPIPE,SIG_IGN);/*忽略SIGPIPE*/
    /*创建线程池*/
    threadpool<http_conn>* pool = NULL;
    try
    {
        pool = new threadpool<http_conn>;
    }
    catch(...)
    {
        return 1;
    }
    
    /*预先为每个可能的客户连接分配一个http_conn对象*/
    http_conn* users = new http_conn[MAX_FD];
    assert(users);
    int user_count = 0;
    
    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    assert(listenfd >= 0);
    
    struct linger tmp = {1,0};
    setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp));
    
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);
    
    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret >= 0);
    
    ret = listen(listenfd,5);
    assert(ret >= 0);
    
    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    addfd(epollfd,listenfd,false);
    http_conn::m_epollfd = epollfd;
    
    while(true)
    {
        int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
        if(()number < 0)&&(errno != EINTR))
        {
            printf("epoll failure\n");
            break;
        }
        for(int i = 0;i < number; i++)
        {
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd)
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
                if(connfd < 0)
                {
                    printf("errno is:%d\n",errno);
                    continue;
                }
                if(http_conn::m_user_count >= MAX_FD)
                {
                    show_error(connfd,"Internal server busy");
                    continue;
                }
                /*初始化客户连接*/
                users[connfd].init(connfd,client_address);
            }
            else if(events[i].events & (EPOLLRDJUP | EPOLLHUP | EPOLLERR))
            {
                users[sockfd].close_conn();
            }
            else if(events[i].events & EPOLLIN)
            {
                /*根据读的结果,决定是将任务添加到线程池,还是关闭*/
                if(users[sockfd].read())
                {
                    pool->append(users + sockfd);
                }
                else
                {
                    users[sockfd].close_conn();
                }
            }
            else if(events[i].events & EPOLLOUT)
            {
                /*根据写的内容,决定是否关闭连接*/
                if(!users[sockfd].write())
                {
                    users[sockfd].close_conn();
                }
            }
            else
            {}
        }
    }
    close(epollfd);
    close(listenfd);
    delete [] users;
    delete pool;
    return 0;
}

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值