一个简单的百万并发的TCP服务器的实现。

我们紧接着上篇文章,看看我们上节课的代码有什么问题?
可以明显的看出来上节课的代码公用了一个同样的缓冲区进行读写,正常的情况下我们需要封装一个结构体,让每个对应的客户端的FD都有独立的结构进行读写还有接收连接。
具体的结构如下:

struct sock_item
{
    //客户端的fd
    int fd; 
    //读取的缓冲区
    char * rbuffer;
    //缓冲区的大小
    int rlength;
    //写入的缓冲区
    char * wbuffer;
    //写入的缓冲区大小
    int wlength;

    //处理的事件类型
    int event;
    

    //回调函数
    void (*recv_cb)(int fd,char * buffer,int length);
    void (*send_cb)(int fd,char *buffer,int length);
    void (*accept_cb)(int fd,char * buffer,int length);


};
struct eventblock
{
    struct sock_item * items;
    struct eventblock*next;    
};

struct reactor
{
   int epfd;
   int blkcnt;

   struct eventblock *evblk;
};

首先看一下我的思路,如图所示

我们有一个总的reactor结构保存我们多个事件域。事件域中保存这我们的所有连接的客户端,而事件域就是一个链表的数据结构,我们无法知道我们要连入多少台客户端,因此我们要使用链表,当有超过我们的设置的1024个sock_item项,就再去申请一个新的事件域,保存新的sockitem。
上面,暂时解决了连接了很多个客户端FD的保存的问题,还有一个问题,就是我们一个IP端口可以连接的最大数为65535.
我们如何解决那?
一个连接都有5个元素来确定,一个源IP,一个源端口,一个目的IP,一个目的端口,协议的种类。
这里我们有3个方式来解决这个问题
第一个就是通过有多个网卡,就可以接受多个客户端的接入。
第二个就是通过绑定多个端口的方式,也可以接受多个客户端的接入。
第三个就是通过多个目的ip的端口更改,也是可以解决这个问题的,
我这个例子就是通过使用绑定多个端口来实现的。

初始化100个端口的代码

int init_server(short port){
    int listenfd= socket(AF_INET,SOCK_STREAM,0);
    if (listenfd==-1)
    {
        return -1;
    }
    struct sockaddr_in servaddr;
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(port);
    if (-1==bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))
    {
        return -2;
    }
    
    //设置非阻塞
    int flag = fcntl(listenfd,F_GETFL,0);
    flag |=O_NONBLOCK;
    fcntl(listenfd,F_SETFL,flag);

    listen(listenfd,10);

    return listenfd;

}

判断是否是监听的FD的代码

int is_listenfd(int *fds,int connfd){
    int i=0;
    for (int i = 0; i < PORT_COUNT; i++)
    {
       if (fds[i] == connfd) {
			return 1;
		}
    }
    return 0;
}

现在初始化我们ractor结构并保存我们的fd。(主要就是链表的操作)

int reactor_resize(struct reactor *r ){
    if (r==NULL)
    {
        return -1;
    }
    struct eventblock* blk=r->evblk;
    while (blk!=NULL && blk->next!=NULL)
    {
        blk=blk->next;
    }
    
    struct sock_item * item=(struct sock_item *)malloc(ITEM_LENGTH * sizeof(struct sock_item));
    if (item==NULL)
    {
        return -2;
    }
    
    memset(item,0,ITEM_LENGTH*sizeof(struct sock_item));
    //链表增加的操作
    struct eventblock * block=malloc(sizeof(struct eventblock));
    if (block==NULL)
    {
        free(item);
        return -3;
    }
    memset(block,0,sizeof(struct eventblock));
    block->items=item;
    block->next=NULL;

    if (blk==NULL)
    {
        r->evblk=block;
    }else
    {
       blk->next = block;
    }
    
    r->blkcnt ++;

	return 0;
    
}

struct sock_item * reactor_lookup(struct reactor * r ,int sockfd)
{
    if (r==NULL)
    {
        return NULL;
    }
    printf("currrent eventblock num:%d\n",r->blkcnt);
    int blkidx=sockfd/ITEM_LENGTH;
    while (blkidx >= r->blkcnt)
    {
        //如果当前的数目超过我们的最大数目 r->blkcnt * 1024; 重新申请一个block_event;
        reactor_resize(r);
    }
    int i = 0;
    struct eventblock* blk=r->evblk;
    while (i ++ < blkidx && blk != NULL) {
		blk = blk->next;
	}

	return  &blk->items[sockfd % ITEM_LENGTH];
}

最后就是我们整个的一个代码

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


#define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128

#define PORT_COUNT  200
#define ITEM_LENGTH 1024

struct sock_item
{
    //客户端的fd
    int fd; 
    //读取的缓冲区
    char * rbuffer;
    //缓冲区的大小
    int rlength;
    //写入的缓冲区
    char * wbuffer;
    //写入的缓冲区大小
    int wlength;

    //处理的事件类型
    int event;
    

    //回调函数
    void (*recv_cb)(int fd,char * buffer,int length);
    void (*send_cb)(int fd,char *buffer,int length);
    void (*accept_cb)(int fd,char * buffer,int length);


};
struct eventblock
{
    struct sock_item * items;
    struct eventblock*next;    
};

struct reactor
{
   int epfd;
   int blkcnt;

   struct eventblock *evblk;
};

int init_server(short port){
    int listenfd= socket(AF_INET,SOCK_STREAM,0);
    if (listenfd==-1)
    {
        return -1;
    }
    struct sockaddr_in servaddr;
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(port);
    if (-1==bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))
    {
        return -2;
    }
    
    //设置非阻塞
    int flag = fcntl(listenfd,F_GETFL,0);
    flag |=O_NONBLOCK;
    fcntl(listenfd,F_SETFL,flag);

    listen(listenfd,10);

    return listenfd;

}

int is_listenfd(int *fds,int connfd){
    int i=0;
    for (int i = 0; i < PORT_COUNT; i++)
    {
       if (fds[i] == connfd) {
			return 1;
		}
    }
    return 0;
}

int reactor_resize(struct reactor *r ){
    if (r==NULL)
    {
        return -1;
    }
    struct eventblock* blk=r->evblk;
    while (blk!=NULL && blk->next!=NULL)
    {
        blk=blk->next;
    }
    
    struct sock_item * item=(struct sock_item *)malloc(ITEM_LENGTH * sizeof(struct sock_item));
    if (item==NULL)
    {
        return -2;
    }
    
    memset(item,0,ITEM_LENGTH*sizeof(struct sock_item));
    //链表增加的操作
    struct eventblock * block=(struct eventblock*)malloc(sizeof(struct eventblock));
    if (block==NULL)
    {
        free(item);
        return -3;
    }
    memset(block,0,sizeof(struct eventblock));
    block->items=item;
    block->next=NULL;

    if (blk==NULL)
    {
        r->evblk=block;
    }else
    {
       blk->next = block;
    }
    
    r->blkcnt ++;

	return 0;
    
}

struct sock_item * reactor_lookup(struct reactor * r ,int sockfd)
{
    if (r==NULL)
    {
        return NULL;
    }
    printf("currrent eventblock num:%d\n",r->blkcnt);
    int blkidx=sockfd/ITEM_LENGTH;
    while (blkidx >= r->blkcnt)
    {
        //如果当前的数目超过我们的最大数目 r->blkcnt * 1024; 重新申请一个block_event;
        reactor_resize(r);
    }
    int i = 0;
    struct eventblock* blk=r->evblk;
    while (i ++ < blkidx && blk != NULL) {
		blk = blk->next;
	}

	return  &blk->items[sockfd % ITEM_LENGTH];
}

int main()
{

    int ret;
    //申请 reactor 结构
    struct reactor * r= (struct reactor * )calloc(1,sizeof(struct reactor));
    if (r==NULL)
    {
        return -3;
    }

    //开始进行EPOLL的创建
    r->epfd=  epoll_create(1);
    struct epoll_event ev, events[EVENTS_LENGTH];
    int sockfds[PORT_COUNT] = {0};
    for (int i = 0; i < PORT_COUNT; i++)
    {
        sockfds[i] = init_server(9999 + i);
        ev.events = EPOLLIN;
		ev.data.fd = sockfds[i];
        epoll_ctl(r->epfd, EPOLL_CTL_ADD, sockfds[i], &ev);
    }
    
    //接下来开始接受 我们的客户端的连接请求
    while (1)
    {
        //我们需要详细讲解一下这个函数的里面的各个参数的意义 ,以及它什么时候是阻塞的,什么时候是非阻塞的,
        //第一个参数我们的EPFD的文件描述符,第二个我们的接收事件的缓冲器,第三个是我们事件数量的多少,最后一个参数就是我们等待的时长了。
        //当是-1的时候就是一直等待连接的意思,没有连接就会 一直被阻塞住,
        //当是0的时候就是一直有连接直接返回的意思,
        //当是大于0的数的时候,就是在轮询查看是否有事件的时长,单位是MS。
        int nready = epoll_wait(r->epfd,events,EVENTS_LENGTH,-1);
        printf("----------%d\n",nready);


        //开始遍历我们的事件
        int i =0;
        for (int i = 0; i < nready; i++)
        {
            int clientfd=events[i].data.fd;
            if (is_listenfd(sockfds,clientfd))
            {
               //如果是我们的监听的FD,说明是有客户端连入的事件
               struct sockaddr_in client;
               socklen_t len=sizeof(client);
               //接受客户端的请求,
               int connfd=accept(clientfd,(struct sockaddr*)&client,&len);
               if (connfd==-1)
               {
                    break;
               }
               int flag = fcntl(connfd, F_GETFL, 0);
				flag |= O_NONBLOCK;
				fcntl(connfd, F_SETFL, flag);
               //增加到我们的快递柜中
               ev.events=EPOLLIN;
               ev.data.fd=connfd;
               epoll_ctl(r->epfd,EPOLL_CTL_ADD,connfd,&ev);   
               //如果是读的请求            
               struct sock_item *item = reactor_lookup(r, connfd);
				item->fd = connfd;
				item->rbuffer = (char*)calloc(1, BUFFER_LENGTH);
				item->rlength = 0;

				item->wbuffer = (char*)calloc(1, BUFFER_LENGTH);
				item->wlength = 0;
            }
            else if (events[i].events & EPOLLIN)
            {
               	struct sock_item *item = reactor_lookup(r, clientfd);

				char *rbuffer = item->rbuffer;
				char *wbuffer = item->wbuffer;
				
				int n = recv(clientfd, rbuffer, BUFFER_LENGTH, 0);
				if (n > 0) {
					//rbuffer[n] = '\0';

					printf("recv: %s, n: %d\n", rbuffer, n);

					memcpy(wbuffer, rbuffer, BUFFER_LENGTH);

					ev.events = EPOLLOUT;
					ev.data.fd = clientfd;

					epoll_ctl(r->epfd, EPOLL_CTL_MOD, clientfd, &ev);
					
				} else if (n == 0) {

					free(rbuffer);
					free(wbuffer);
					
					item->fd = 0;

					close(clientfd);
				}
            }
            else if(events[i].events & EPOLLOUT)
            {
                struct sock_item *item = reactor_lookup(r, clientfd);
				char *wbuffer = item->wbuffer;
				
				int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0); //
				printf("sent: %d\n", sent);

				ev.events = EPOLLIN;
				ev.data.fd = clientfd;

				epoll_ctl(r->epfd, EPOLL_CTL_MOD, clientfd, &ev);
				
            }
                       
            
        }
        
    }
    
    return 0;

}


推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器
音视频
dpdk
Linux内核

posted @ 2022-10-20 12:51  飘雨的河  阅读(285)  评论(0编辑  收藏  举报