75、服务器源码解析---------云视频服务项目

服务器源码解析

主函数流程

绑定tcp 监听描述符

listenfd = Tcp_listen(argv1, argv2, &addrlen);

开辟资源

cpp 复制代码
maxfd = listenfd;
    int nthreads = atoi(argv[argc - 2]);
    nprocesses = atoi(argv[argc-1]);

    //init room
    //根据进程的个数创建房间
    room = new Room(nprocesses);

    printf("total threads: %d  total process: %d\n", nthreads, nprocesses);

    //动态开辟线程对象
    tptr = (Thread *)Calloc(nthreads, sizeof(Thread));

开辟子进程

cpp 复制代码
//根据进程数量开辟进程
    for(i = 0; i < nprocesses; i++)
    {
        process_make(i, listenfd);
        //将和子进程关联的描述符id放入主进程集合中监听
        FD_SET(room->pptr[i].child_pipefd, &masterset);
        maxfd = max(maxfd, room->pptr[i].child_pipefd);
    }

接下来讲解process_make函数

cpp 复制代码
int process_make(int i, int listenfd)
{
    int sockfd[2];
    pid_t pid;
    void process_main(int, int);
    //管道, sockfd[0]作为发送和接收端
    Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
    if((pid = fork()) > 0)
    {
        Close(sockfd[1]);
        room->pptr[i].child_pid = pid;
        //父进程和子进程通信,用sockfd[0],将来放入主进程的select模型的集合中
        //子进程发送消息就会通知到父进程
        room->pptr[i].child_pipefd = sockfd[0];
        room->pptr[i].child_status = 0;
        room->pptr[i].total = 0;
        return pid; // father
    }

    Close(listenfd); // child not need this open
    //子进程使用sockfd[1]和父进程通信
    Close(sockfd[0]);
    process_main(i, sockfd[1]); /* never returns */
}

综上所述,processmake就是开辟子进程

在主进程中,将子进程通信的sockfd0加入到主进程的select集合中

在子进程中,调用process_main函数

cpp 复制代码
void process_main(int i, int fd) // room start
{
    //create accpet fd thread
    printf("room %d starting \n", getpid());
    Signal(SIGPIPE, SIG_IGN);
    pthread_t pfd1;
    void* accept_fd(void *);
    void* send_func(void *);
    void  fdclose(int, int);

    int *ptr = (int *)malloc(4);
    *ptr = fd;
    //子进程启动子线程,读取父进程消息
    Pthread_create(&pfd1, NULL, accept_fd, ptr); // accept fd
    for(int i = 0; i < SENDTHREADSIZE; i++)
    {
        //在子进程中,创建5个线程用来发送
        Pthread_create(&pfd1, NULL, send_func, NULL);
    }

    //listen read data from fds
    //子进程监听所有客户端发送的消息
    for(;;)
    {
        fd_set rset = user_pool->fdset;
        int nsel;
        struct timeval time;
        memset(&time, 0, sizeof(struct timeval));
        while((nsel = Select(maxfd + 1, &rset, NULL, NULL, &time))== 0)
        {
            rset = user_pool->fdset; // make sure rset update
        }
        for(int i = 0; i <= maxfd; i++)
        {
            //check data arrive
            if(FD_ISSET(i, &rset))
            {
                char head[15] = {0};
                int ret = Readn(i, head, 11); // head size = 11
                if(ret <= 0)
                {
                    printf("peer close\n");
                    fdclose(i, fd);
                }
                else if(ret == 11)
                {
                    if(head[0] == '$')
                    {
                        //solve datatype
                        MSG_TYPE msgtype;
                        memcpy(&msgtype, head + 1, 2);
                        msgtype = (MSG_TYPE)ntohs(msgtype);

                        MSG msg;
                        memset(&msg, 0, sizeof(MSG));
                        msg.targetfd = i;
                        memcpy(&msg.ip, head + 3, 4);
                        int msglen;
                        memcpy(&msglen, head + 7, 4);
                        msg.len = ntohl(msglen);

                        if(msgtype == IMG_SEND || msgtype == AUDIO_SEND || msgtype == TEXT_SEND)
                        {
                            msg.msgType = (msgtype == IMG_SEND) ? IMG_RECV : ((msgtype == AUDIO_SEND)? AUDIO_RECV : TEXT_RECV);
                            msg.ptr = (char *)malloc(msg.len);
                            msg.ip = user_pool->fdToIp[i];
                            if((ret = Readn(i, msg.ptr, msg.len)) < msg.len)
                            {
                                err_msg("3 msg format error");
                            }
                            else
                            {
                                int tail;
                                Readn(i, &tail, 1);
                                if(tail != '#')
                                {
                                    err_msg("4 msg format error");
                                }
                                else
                                {
                                    sendqueue.push_msg(msg);
                                }
                            }
                        }
                        else if(msgtype == CLOSE_CAMERA)
                        {
                            char tail;
                            Readn(i, &tail, 1);
                            if(tail == '#' && msg.len == 0)
                            {
                                msg.msgType = CLOSE_CAMERA;
                                sendqueue.push_msg(msg);
                            }
                            else
                            {
                                err_msg("camera data error ");
                            }
                        }
                    }
                    else
                    {
                        err_msg("1 msg format error");
                    }
                }
                else
                {
                    err_msg("2 msg format error");
                }
                if(--nsel <= 0) break;
            }
        }
    }
}

上面我们谈到了,子线程调用accept_fd

cpp 复制代码
void* accept_fd(void *arg) //accept fd from father
{
    uint32_t getpeerip(int);
    //让子线程分离
    Pthread_detach(pthread_self());
    int fd = *(int *)arg, tfd = -1;
    free(arg);
    while(1)
    {
        int n, c;
        //相当于子进程的子线程从sockfd[1]读取父进程传输的消息
        if((n = read_fd(fd, &c, 1, &tfd)) <= 0)
        {
            err_quit("read_fd error");
        }
        if(tfd < 0)
        {
            printf("c = %c\n", c);
            err_quit("no descriptor from read_fd");
        }

        //add to poll

        if(c == 'C') // create
        {
            Pthread_mutex_lock(&user_pool->lock); //lock
            //子进程将父进程传输过来的客户端fd,放入集合,监听读写
            FD_SET(tfd, &user_pool->fdset);
            user_pool->owner = tfd;
            user_pool->fdToIp[tfd] = getpeerip(tfd);
            user_pool->num++;
//            user_pool->fds[user_pool->num++] = tfd;
            user_pool->status[tfd] = ON;
            maxfd = MAX(maxfd, tfd);
            //printf("c %d\n", maxfd);
            //write room No to  tfd
            roomstatus = ON; // set on

            Pthread_mutex_unlock(&user_pool->lock); //unlock

            MSG msg;
            msg.msgType = CREATE_MEETING_RESPONSE;
            msg.targetfd = tfd;
            int roomNo = htonl(getpid());
            msg.ptr = (char *) malloc(sizeof(int));
            memcpy(msg.ptr, &roomNo, sizeof(int));
            msg.len = sizeof(int);
            sendqueue.push_msg(msg);

//            printf("create meeting: %d\n", tfd);

        }
        else if(c == 'J') // join
        {
            Pthread_mutex_lock(&user_pool->lock); //lock
            if(roomstatus == CLOSE) // meeting close (owner close)
            {
                close(tfd);
                Pthread_mutex_unlock(&user_pool->lock); //unlock
                continue;
            }
            else
            {
               //子进程将父进程传输过来的客户端fd,放入集合,监听读写
                FD_SET(tfd, &user_pool->fdset);
                user_pool->num++;
//                user_pool->fds[user_pool->num++] = tfd;
                user_pool->status[tfd] = ON;
                maxfd = MAX(maxfd, tfd);
                user_pool->fdToIp[tfd] = getpeerip(tfd);
                Pthread_mutex_unlock(&user_pool->lock); //unlock

                //broadcast to others
                MSG msg;
                memset(&msg, 0, sizeof(MSG));
                msg.msgType = PARTNER_JOIN;
                msg.ptr = NULL;
                msg.len = 0;
                msg.targetfd = tfd;
                msg.ip = user_pool->fdToIp[tfd];
                sendqueue.push_msg(msg);

                //broadcast to others
                MSG msg1;
                memset(&msg1, 0, sizeof(MSG));
                msg1.msgType = PARTNER_JOIN2;
                msg1.targetfd = tfd;
                int size = user_pool->num * sizeof(uint32_t);

                msg1.ptr = (char *)malloc(size);
                int pos = 0;

                for(int i = 0; i <= maxfd; i++)
                {
                    if(user_pool->status[i] == ON && i != tfd)
                    {
                        uint32_t ip = user_pool->fdToIp[i];
                        memcpy(msg1.ptr + pos, &ip, sizeof(uint32_t));
                        pos += sizeof(uint32_t);
                        msg1.len += sizeof(uint32_t);
                    }
                }
                sendqueue.push_msg(msg1);

                printf("join meeting: %d\n", msg.ip);
            }
        }
    }
    return NULL;
}

总结

我们能可以理解为子进程开辟了子线程,

这个子线程就是接收父进程传输的描述fd,这个描述符fd,是父进程accept连接返回的新连接的描述符

子线程将这个描述符放入到自己的监听集合中

将来子进程通过select模型不断地去轮询监听客户端的读写请求。

子进程收发逻辑

cpp 复制代码
void process_main(int i, int fd) // room start
{
    //create accpet fd thread
    printf("room %d starting \n", getpid());
    Signal(SIGPIPE, SIG_IGN);
    pthread_t pfd1;
    void* accept_fd(void *);
    void* send_func(void *);
    void  fdclose(int, int);

    int *ptr = (int *)malloc(4);
    *ptr = fd;
    //子进程启动子线程,读取父进程消息
    Pthread_create(&pfd1, NULL, accept_fd, ptr); // accept fd
    for(int i = 0; i < SENDTHREADSIZE; i++)
    {
        //在子进程中,创建5个线程用来发送
        Pthread_create(&pfd1, NULL, send_func, NULL);
    }

    //listen read data from fds
    //子进程监听所有客户端发送的消息
    for(;;)
    {
        fd_set rset = user_pool->fdset;
        int nsel;
        struct timeval time;
        memset(&time, 0, sizeof(struct timeval));
        while((nsel = Select(maxfd + 1, &rset, NULL, NULL, &time))== 0)
        {
            rset = user_pool->fdset; // make sure rset update
        }
        for(int i = 0; i <= maxfd; i++)
        {
            //check data arrive
            if(FD_ISSET(i, &rset))
            {
                char head[15] = {0};
                int ret = Readn(i, head, 11); // head size = 11
                if(ret <= 0)
                {
                    printf("peer close\n");
                    fdclose(i, fd);
                }
                else if(ret == 11)
                {
                    if(head[0] == '$')
                    {
                        //solve datatype
                        MSG_TYPE msgtype;
                        memcpy(&msgtype, head + 1, 2);
                        //解析消息类型,将消息id转为本地字节序
                        msgtype = (MSG_TYPE)ntohs(msgtype);

                        MSG msg;
                        memset(&msg, 0, sizeof(MSG));
                        msg.targetfd = i;
                        memcpy(&msg.ip, head + 3, 4);
                        int msglen;
                        memcpy(&msglen, head + 7, 4);
                        msg.len = ntohl(msglen);

                        if(msgtype == IMG_SEND || msgtype == AUDIO_SEND || msgtype == TEXT_SEND)
                        {
                            //服务器统一将各种资源进行转发
                            msg.msgType = (msgtype == IMG_SEND) ? IMG_RECV : ((msgtype == AUDIO_SEND)? AUDIO_RECV : TEXT_RECV);
                            msg.ptr = (char *)malloc(msg.len);
                            //服务器给客户端多回复了4个字节的消息,作为ip
                            msg.ip = user_pool->fdToIp[i];
                            if((ret = Readn(i, msg.ptr, msg.len)) < msg.len)
                            {
                                err_msg("3 msg format error");
                            }
                            else
                            {
                                int tail;
                                Readn(i, &tail, 1);
                                if(tail != '#')
                                {
                                    err_msg("4 msg format error");
                                }
                                else
                                {
                                    sendqueue.push_msg(msg);
                                }
                            }
                        }
                        else if(msgtype == CLOSE_CAMERA)
                        {
                            char tail;
                            Readn(i, &tail, 1);
                            if(tail == '#' && msg.len == 0)
                            {
                                msg.msgType = CLOSE_CAMERA;
                                sendqueue.push_msg(msg);
                            }
                            else
                            {
                                err_msg("camera data error ");
                            }
                        }
                    }
                    else
                    {
                        err_msg("1 msg format error");
                    }
                }
                else
                {
                    err_msg("2 msg format error");
                }
                if(--nsel <= 0) break;
            }
        }
    }
}
  1. 子进程只有一个线程处理接收,保证接受的顺序是一致的,并且通过TLV解析
  2. 解析完成后将消息回传给客户端,将消息投递给发送队列
  3. 子进程有五个线程从发送队列取出数据回传给客户端。

发送线程

发送队列

封装

cpp 复制代码
void push_msg(MSG msg)
    {
        //对于队列为空还是满,分别使用两个条件变量才合适,否则一个条件变量容易逻辑混乱和卡死
        Pthread_mutex_lock(&lock);
        while(send_queue.size() >= MAXSIZE)
        {
            Pthread_cond_wait(&cond, &lock);
        }
        send_queue.push(msg);
        Pthread_mutex_unlock(&lock);
        Pthread_cond_signal(&cond);
    }

    MSG pop_msg()
    {
        Pthread_mutex_lock(&lock);
        while(send_queue.empty())
        {
            Pthread_cond_wait(&cond, &lock);
        }
        MSG msg = send_queue.front();
        send_queue.pop();
        Pthread_mutex_unlock(&lock);
        Pthread_cond_signal(&cond);
        return msg;
    }
  1. 新增两个条件变量,cond_full条件变量控制满挂起, cond_empty条件变量控制空挂起

发送逻辑

cpp 复制代码
void *send_func(void *arg)
{
    Pthread_detach(pthread_self());
    char * sendbuf = (char *)malloc(4 * MB);
    /*
     * $_msgType_ip_size_data_#
    */

    for(;;)
    {
        memset(sendbuf, 0, 4 * MB);
        MSG msg = sendqueue.pop_msg();
        int len = 0;

        sendbuf[len++] = '$';
        short type = htons((short)msg.msgType);
        memcpy(sendbuf + len, &type, sizeof(short)); //msgtype
        len+=2;

        if(msg.msgType == CREATE_MEETING_RESPONSE || msg.msgType == PARTNER_JOIN2)
        {
            len += 4;
        }
        else if(msg.msgType == TEXT_RECV || msg.msgType == PARTNER_EXIT || msg.msgType == PARTNER_JOIN || msg.msgType == IMG_RECV || msg.msgType == AUDIO_RECV || msg.msgType == CLOSE_CAMERA)
        {
            memcpy(sendbuf + len, &msg.ip, sizeof(uint32_t));
            len+=4;
        }

        int msglen = htonl(msg.len);
        memcpy(sendbuf + len, &msglen, sizeof(int));
        len += 4;
        memcpy(sendbuf + len, msg.ptr, msg.len);
        len += msg.len;
        sendbuf[len++] = '#';


        Pthread_mutex_lock(&user_pool->lock);
        //创建会议只给创建者回复
        if(msg.msgType == CREATE_MEETING_RESPONSE)
        {
            //send buf to target
            if(writen(msg.targetfd, sendbuf, len) < 0)
            {
                err_msg("writen error");
            }
        }//退出,图片传输,音频传输等要广播给其他人
        else if(msg.msgType == PARTNER_EXIT || msg.msgType == IMG_RECV || msg.msgType == AUDIO_RECV || msg.msgType == TEXT_RECV || msg.msgType == CLOSE_CAMERA)
        {
            for(int i = 0; i <= maxfd; i++)
            {
                if(user_pool->status[i] == ON && msg.targetfd != i)
                {
                    if(writen(i, sendbuf, len) < 0)
                    {
                        err_msg("writen error");
                    }
                }
            }
        }//伙伴加入也要广播给其他人
        else if(msg.msgType == PARTNER_JOIN)
        {
            for(int i = 0; i <= maxfd; i++)
            {
                if(user_pool->status[i] == ON && i != msg.targetfd)
                {
                    if(writen(i, sendbuf, len) < 0)
                    {
                        err_msg("writen error");
                    }
                }
            }
        }
        else if(msg.msgType == PARTNER_JOIN2)
        {
            for(int i = 0; i <= maxfd; i++)
            {
                if(user_pool->status[i] == ON && i == msg.targetfd)
                {
                    if(writen(i, sendbuf, len) < 0)
                    {
                        err_msg("writen error");
                    }
                }
            }
        }

        Pthread_mutex_unlock(&user_pool->lock);

        //free
        if(msg.ptr)
        {
            free(msg.ptr);
            msg.ptr = NULL;
        }
    }
    free(sendbuf);

    return NULL;
}

创建线程

cpp 复制代码
void  Pthread_create(pthread_t * tid, const pthread_attr_t * attr,
                       THREAD_FUNC * func, void *arg)
{
    int n;
    if( (n = pthread_create(tid, attr, func, arg)) != 0)
    {
        errno = n;
        err_quit("pthread create error");
    }
}
  1. tid 线程id
  2. attr线程属性
  3. 线程回调函数
  4. 线程回调函数用到的参数

arg不可以是局部变量地址,否则线程触发回调函数时,可能会丢失数据。

分离线程

cpp 复制代码
void Pthread_detach(pthread_t tid)
{
    int n;
    if((n = pthread_detach(tid)) == 0)
    {
        return;
    }
    else
    {
        errno = n;
        err_quit("pthread detack error");
    }
}
  1. detach 会将子线程放入后台运行
  2. 主进程退出时,detach的线程也会被回收
  3. 主进程退出时,要设置退出标记,通知子线程退出。

joindetach

join就是汇合线程

主线程等待子线程结束时才回收资源。

加锁和解锁

cpp 复制代码
void Pthread_mutex_lock(pthread_mutex_t *mptr)
{
    int n;
    if((n = pthread_mutex_lock(mptr))  == 0)
    {
        return;
    }
    else
    {
        errno = n;
        err_quit("pthread_mutex_lock error");
    }
}

void Pthread_mutex_unlock(pthread_mutex_t *mptr)
{
    int n;
    if((n = pthread_mutex_unlock(mptr))  == 0)
    {
        return;
    }
    else
    {
        errno = n;
        err_quit("pthread_mutex_unlock error");
    }
}

条件变量的等待和激活

等待

cpp 复制代码
void Pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t *lock)
{
    int n;
    if((n = pthread_cond_wait(cond, lock)) == 0)
    {
        return;
    }
    else
    {
        errno = n;
        err_quit("Pthread_cond_wait error");
    }
}

激活一个

cpp 复制代码
void Pthread_cond_signal(pthread_cond_t *cond)
{
    int n;
    if((n = pthread_cond_signal(cond)) == 0)
    {
        return;
    }
    else
    {
        errno = n;
        err_quit("Pthread_cond_signal error");
    }
}

激活所有

cpp 复制代码
void Pthread_cond_broadcast(pthread_cond_t* cond){
    int n ;
    if((n = pthread_cond_broadcast(cond)) == 0){
        return;
    }else{
         errno = n;
        err_quit("Pthread_cond_signal error");
    }
}

用户池

cpp 复制代码
//用户池,所有用户都存储在这个里面
typedef struct pool
{
    fd_set fdset;
    pthread_mutex_t lock;
    int owner;
    int num;
    int status[1024 + 10];
    std::map<int, uint32_t> fdToIp;
    pool()
    {
        memset(status, 0, sizeof(status));
        owner = 0;
        FD_ZERO(&fdset);
        lock = PTHREAD_MUTEX_INITIALIZER;
        num = 0;
    }

    void clear_room()
    {
        Pthread_mutex_lock(&lock);
        roomstatus = CLOSE;
        for(int i = 0; i <= maxfd; i++)
        {
            if(status[i] == ON)
            {
                Close(i);
            }
        }
        memset(status, 0, sizeof(status));
        num = 0;
        owner = 0;
        FD_ZERO(&fdset);
        fdToIp.clear();
        sendqueue.clear();
        Pthread_mutex_unlock(&lock);
    }
}Pool;

接收连接

cpp 复制代码
void* thread_main(void *arg)
{
    void dowithuser(int connfd);
    int i = *(int *)arg;
    free(arg); //free
    int connfd;
    Pthread_detach(pthread_self()); //detach child thread

//    printf("thread %d starting\n", i);

    SA *cliaddr;
    socklen_t clilen;
    cliaddr = (SA *)Calloc(1, addrlen);
    char buf[MAXSOCKADDR];
    for(;;)
    {
        clilen = addrlen;
        //lock accept
        Pthread_mutex_lock(&mlock);
        connfd = Accept(listenfd, cliaddr, &clilen);
        //unlock accept
        Pthread_mutex_unlock(&mlock);

        printf("connection from %s\n", Sock_ntop(buf, MAXSOCKADDR, cliaddr, clilen));

        dowithuser(connfd); // process user


    }
    return NULL;
}

在主进程中接收连接,放入子进程中的fdset中

进程之间通信框架图

重构思路

将主进程由select监听子进程信息,改为主进程用epoll监听子进程信息

cpp 复制代码
int main(int argc, char **argv)
{

    Signal(SIGCHLD, sig_chld);

    //listenfd将来用来监听对方连接
    if (argc == 4)
    {
        listenfd = Tcp_listen(NULL, argv[1], &addrlen);
    }
    else if (argc == 5)
    {
        listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
    }
    else
    {
        err_quit("usage: ./app [host] <port #> <#threads> <#processes>");
    }

    int nthreads = atoi(argv[argc - 2]);
    nprocesses = atoi(argv[argc - 1]);

    //init room
    room = new Room(nprocesses);

    printf("total threads: %d  total process: %d\n", nthreads, nprocesses);

    tptr = (Thread *)Calloc(nthreads, sizeof(Thread));

    //创建epoll
    int epfd = epoll_create1(0);
    if (epfd < 0)
        err_quit("epoll_create1 error");

    //process pool----room
    for (int i = 0; i < nprocesses; i++)
    {
        pid_t pid = process_make(i, listenfd);
        //父进程
        if (pid > 0)
        {
            int pipefd = room->pptr[i].child_pipefd;
            struct epoll_event ev;
            ev.events = EPOLLIN;
            //房间号
            ev.data.u32 = i;

            if (epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd, &ev) < 0)
                err_quit("epoll_ctl add pipefd error");
        }
    }

    //thread pool
    for (int i = 0; i < nthreads; i++)
    {
        thread_make(i);
    }

    const int MAX_EVENTS = 64;
    std::vector<struct epoll_event> events(MAX_EVENTS);

    for (;;)
    {

        int nfds = epoll_wait(epfd, events.data(), MAX_EVENTS, -1);
        if (nfds < 0)
        {
            if (errno == EINTR)
                continue;
            err_quit("epoll_wait error");
        }

        for (int i = 0; i < nfds; i++)
        {
            int idx = events[i].data.u32;
            int pipefd = room->pptr[idx].child_pipefd;
            char rc;
            ssize_t n = Readn(pipefd, &rc, 1);
            if (n <= 0)
            {
                err_quit("child %d terminated unexpectedly", idx);
            }
            printf("c = %c\n", rc);

            if (rc == 'E') // room empty
            {
                pthread_mutex_lock(&room->lock);
                room->pptr[i].child_status = 0;
                room->navail++;
                printf("room %d is now free\n", room->pptr[i].child_pid);
                pthread_mutex_unlock(&room->lock);
            }
            else if (rc == 'Q') // partner quit
            {
                Pthread_mutex_lock(&room->lock);
                room->pptr[i].total--;
                Pthread_mutex_unlock(&room->lock);
            }
            else // trash data
            {
                err_msg("read from %d error", room->pptr[i].child_pipefd);
            }
        }
    }
    return 0;
}

// create threads
void thread_make(int i)
{
    void *thread_main(void *);
    int *arg = (int *)Calloc(1, sizeof(int));
    *arg = i;
    Pthread_create(&tptr[i].thread_tid, NULL, thread_main, arg);
}

int process_make(int i, int listenfd)
{
    int sockfd[2];
    pid_t pid;
    void process_main(int, int);

    Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
    if ((pid = fork()) > 0)
    {
        Close(sockfd[1]);
        room->pptr[i].child_pid = pid;
        room->pptr[i].child_pipefd = sockfd[0];
        room->pptr[i].child_status = 0;
        room->pptr[i].total = 0;
        return pid; // father
    }

    Close(listenfd); // child not need this open
    Close(sockfd[0]);
    process_main(i, sockfd[1]); /* never returns */
}
  1. epoll使用LT模式
  2. 主进程和子进程仍然采用管道通信,子进程发送消息,主进程通过epoll监听管道信息
  3. 创建子进程时采用process_make,将管道信息注册到epoll表中。
  4. 主进程主要处理客户端返回的退出信息,从而清除房间。所以房间的管理是放在主进程中的。

子进程重构为epoll模型

cpp 复制代码
typedef struct pool
{
    pthread_mutex_t lock;
    int owner;
    int num;
    std::map<int, int> fdToStatus;
    std::map<int, uint32_t> fdToIp;
    // 使用epoll
    int epollfd;
    pool()
    {
        fdToStatus.clear();
        owner = 0;
        lock = PTHREAD_MUTEX_INITIALIZER;
        num = 0;
        // 创建epoll实例
        epollfd = epoll_create1(0);
        if (epollfd < 0)
        {
            err_quit("epoll_create1 error");
        }
    }

    void clear_room()
    {
        Pthread_mutex_lock(&lock);
        roomstatus = CLOSE;
        for (auto iter = fdToStatus.begin(); iter != fdToStatus.end();
             iter++)
        {
            int sock_fd = iter->first;
            epoll_ctl(epollfd, EPOLL_CTL_DEL, sock_fd, NULL);
            Close(sock_fd);
        }

        fdToStatus.clear();
        num = 0;
        owner = 0;
        fdToIp.clear();
        sendqueue.clear();
        Pthread_mutex_unlock(&lock);
    }

    void clear_status(int fd){
        auto iter = fdToStatus.find(fd);
        if(iter != fdToStatus.end()){
            fdToStatus.erase(iter);
        }
    }

    void add_status(int fd){
        fdToStatus[fd] = STATUS::ON;
    }
} Pool;

Pool *user_pool = new Pool();
  1. 子进程epoll采用LT模式,每一个房间(每一个进程)独立有一个epoll表
  2. 在子进程启动时,首先启动一个子线程,然后子线程死循环阻塞从管道中读取主进程发送的消息。读取消息后,就是新建的socket连接,将socket放入子进程的epoll表。
  3. 首先主进程还是获取客户端连接,将新生成socket通过管道发送给子进程,子进程收到后,将这个socket放入epoll表,以后这个socket发过来的数据,交给子进程自己处理。子进程epoll_wait就会返回读就绪事件列表。

当发现客户端socket关闭或者异常时,需要将其从epoll表中移除

待改进问题

希望大家重写send_func,将发送改为判断发送长度,如果未发完就继续发送。

    1. 死循环轮询发送
    2. 配合发送队列,将未发送完成的消息放入队列即可。
相关推荐
阿米亚波1 小时前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
张飞飞飞飞飞1 小时前
Tmux命令使用教程
linux·服务器·ubuntu
碧海蓝天20221 小时前
C++法则24:在标准 C++ 中,没有任何可移植的方式判断指针 T* pt 指向的内存位置是否已经 构造了对象,程序员必须手动跟踪哪些元素已构造。
java·开发语言·c++
江华森1 小时前
TCP/IP 协议栈实战 — 7 个实验详解
网络·tcp/ip·智能路由器
charlie1145141912 小时前
现代C++指南:Lambda,让我们用另一种方式持有函数
开发语言·c++
Fcy6482 小时前
Linux下 可重入函数、volatile关键字和SIGCHLD信号
linux·可重入函数·volatile关键字·sigchld
森G2 小时前
77、线程池原理和实现------服务器源码解析----云视频服务项目
服务器·c++·qt
qeen872 小时前
【Linux】Linux简单介绍与基本指令(上)
linux·运维·服务器·学习
酉鬼女又兒2 小时前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php