一个简单的百万并发的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内核
一个简单的百万并发的TCP服务器的实现。的更多相关文章
- UNP学习笔记2——从一个简单的ECHO程序分析TCP客户/服务器之间的通信
1 概述 编写一个简单的ECHO(回复)程序来分析TCP客户和服务器之间的通信流程,要求如下: 客户从标准输入读入一行文本,并发送给服务器 服务器从网络输入读取这个文本,并回复给客户 客户从网络输入读 ...
- MarioTCP:一个单机可日30亿的百万并发长连接服务器
原文:http://blog.csdn.net/everlastinging/article/details/10894493 注:如果用此服务器做变长data的传输,请在业务处理函数中为input ...
- 自己动手写一个简单的(IIS)小型服务器
因为第一次在博客园发表随笔,不太会用,这个笔记是我之前在印象笔记中写好的,然后直接copy过来,有兴趣自己做一个IIS服务器的小伙伴们可以参照下面的流程做一次,也可以叫我要源代码,不过要做完,我觉得花 ...
- 用python实现一个简单的聊天功能,tcp,udp,socketserver版本
基于tcp协议版本 服务器端 import socket server = socket.socket() server.bind(('127.0.0.1', 8001)) server.listen ...
- 如何写一个简单的webserver(一):最简实现
本文主要讲述如何用C/C++在Linux环境下写一个简单的支持并发的web服务器,并不考虑服务器的健壮性.安全性.性能等一系列因素. 在本文中,该服务器仅支持GET请求. 项目地址:https://g ...
- 使用.net core在Ubuntu构建一个TCP服务器
介绍和背景 TCP编程是网络编程领域最有趣的部分之一.在Ubuntu环境中,我喜欢使用.NET Core进行TCP编程,并使用本机Ubuntu脚本与TCP服务器进行通信.以前,我在.NET框架本身写了 ...
- 构建C1000K的服务器(2) – 实现百万连接的comet服务器
转自:http://www.ideawu.net/blog/archives/742.html 这是关于 C1000K 序列文章的第二篇, 在前一篇文章 构建C1000K的服务器(1) – 基础 中, ...
- 【RL-TCPnet网络教程】第13章 RL-TCPnet之TCP服务器
第13章 RL-TCPnet之TCP服务器 本章节为大家讲解RL-TCPnet的TCP服务器实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识.有了这些基础知识之后,再搞本 ...
- Node.js实战14:一个简单的TCP服务器。
本文,将会展示如何用Nodejs内置的net模块开发一个TCP服务器,同时模拟一个客户端,并实现客户端和服务端交互. net模块是nodejs内置的基础网络模块,通过使用net,可以创建一个简单的tc ...
- Golang学习-第二篇 搭建一个简单的Go Web服务器
序言 由于本人一直从事Web服务器端的程序开发,所以在学习Golang也想从Web这里开始学起,如果对Golang还不太清楚怎么搭建环境的朋友们可以参考我的上一篇文章 Golang的简单介绍及Wind ...
随机推荐
- Odoo—货运管理—主表获取明细表数据计算结果
在开发货运管理模块的时候,用到了两张表:主表[waybill]和明细表[waybill.detail],主表存放运单主体信息,明细表存放运单货物信息,如下图所示. 上图中红色方框标记的是明细表中行内的 ...
- 从CPU100%高危故障到稳定在10%:一个月的优化之旅,成功上线!
引言 经过三个月的开发,项目通过了所有测试并上线,然而,我们发现项目的首页几乎无法打开,后台一直发生超时错误,导致CPU过度负荷.在这次项目开发过程中,我制定了一份详细的技术优化方案.考虑到客户无法提 ...
- 3.4 CSP-J 补赛游寄
3.4 CSP-J 补赛游寄 Day -? 听说要去打比赛. Day -7 今天家长会,老师公布成绩 /fn/fn/fn.政治考废了,然后其他都挺好. 语文 $ 95 $,数学 $ 118 $,英语 ...
- SAM题目合集
一些SAM的 基础 题目.(主要是我不想写SAM的原理啊啊啊) 有的题目是SA的思维题,但是可以用SAM平推,基本上可以不动脑子. 除非有特殊说明,否则将字符集看作所有小写字母,构造SAM复杂度记为 ...
- Shell 特殊符号(变量)用法小结
Shell | 特殊变量 $n 基本语法: $n (功能描述:n 为数字,$0 代表该脚本名称,$1-$9 代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10}) 例如: ...
- 开年!5 款令人惊艳的开源项目「GitHub 热点速览」
朋友们开工大吉啊!我刚从假期模式切换回来,完全无心工作有些不在状态,比如开机密码错了好几次.闲话少叙,下面就让我们一起看看,春节这段时间 GitHub 上又出了什么有趣.好玩的开源项目. 今年上来就是 ...
- 用 WebClient 代替 RestTemplate
RestTemplate是用于执行 HTTP 请求的同步客户端,通过底层 HTTP 客户端库(例如 JDK HttpURLConnection.Apache HttpComponents 等)公开一个 ...
- VueRouter导航守卫
VueRouter导航守卫 vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航,简单来说导航守卫就是路由跳转过程中的一些钩子函数,路由跳转是一个大的过程,这个大的过程分为跳转前中后 ...
- 如何在C#中使用 Excel 动态函数生成依赖列表
前言 在Excel 中,依赖列表或级联下拉列表表示两个或多个列表,其中一个列表的项根据另一个列表而变化.依赖列表通常用于Excel的业务报告,例如学术记分卡中的[班级-学生]列表.区域销售报告中的[区 ...
- win32-FileTimeToSystemTime的使用
#include <Windows.h> #include <iostream> #include <string> #pragma warning(disable ...