基于管道通知的百万并发长连接server模型
0、前言
最近突然想了解怎样设计一个支持百万连接的后台server架构。
要设计一个支持百万连接的后台server,我们首先要知道会有哪些因素限制后台server的高并发连接,这里想到的因素有以下几点:
1、操作系统的参数设置能否支持百万并发连接;
2、操作系统维持百万并发长连接需要多少内存;
3、应用层面上维持百万并发长连接需要多少内存;
4、百万并发长连接的吞吐量是否超过了硬件网卡的限制。
在学习的过程中,主要针对的是1、2、4,第3点一般跟业务相关,这里暂时没有考虑。
本篇文章估计需要多次才能完成,现在初步的想法是先写一个demo程序,然后后面再慢慢测试优化。
1、后台设计
1.1 后台设计图
如下为后台的设计结构:
1、首先主进程根据机器CPU个数,创建对应数量的管道;
2、创建完对应的管道之后,再创建一样数量的线程,每个线程绑定一个CPU;
3、主进程开始初始化socket,然后accept,当接收到一个客户端连接时,就把conn_fd写到某个pipe中;
3、每个线程创建epoll,然后监听对应pipe的写端fd,当监听到pipe中有数据时,就读取该数据,格式化为fd,将该fd加入epoll进行监听。
1.2 编码实现
根据1.1的设计,我们编写代码,包括server模块和worker模块。server模块负责创建pipe、线程、和监听客户端连接;worker模块负责处理每个客户端的连接。代码如下所示:
1.2.0 common
#ifndef _SERV_COMMON_H
#define _SERV_COMMON_H typedef struct {
int id;
int fd;
} thread_arg; #define SERV_PORT 9876
#define MAX_LINE 1024 #endif
1.2.1 worker
worker.h
#ifndef _SERV_WORKER_H
#define _SERV_WORKER_H void *worker(void *arg); #endif
worker.cc
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h> #include "common.h" #define MAXFDS 1000000
#define EVENTSIZE 1000 int taskset_thread_core(int core_id)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset); pthread_t curr_tid = pthread_self();
return pthread_setaffinity_np(curr_tid, sizeof(cpu_set_t), &cpuset);
} int setnonblocking(int fd)
{
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, ) | O_NONBLOCK) == -) {
printf("fd %d set non blocking failed\n", fd);
return -;
} return ;
} void handle_req(int cli_fd)
{
char in_buff[MAX_LINE];
int ret, rs = ; while (rs) {
ret = recv(cli_fd, in_buff, , ); if (ret < ) {
if (errno == EAGAIN) {
printf("EAGAIN\n");
break;
} else {
printf("recv error: %d\n", errno);
close(cli_fd);
break;
}
} else if (ret == ) {
rs = ;
} if (ret == sizeof(in_buff))
rs = ;
else
rs = ;
} if (ret > ) {
send(cli_fd, in_buff, strlen(in_buff), );
}
} void run_epoll(int epfd, int pipe_fd)
{
int i, cli_fd, nfds;
struct epoll_event ev, events[EVENTSIZE];
char buff[]; ev.events = EPOLLIN | EPOLLET; while () {
nfds = epoll_wait(epfd, events, EVENTSIZE , -);
for (i = ; i < nfds; i++) {
// pipe msg, add connected fd to epoll
if (events[i].data.fd == pipe_fd) {
read(pipe_fd, buff, );
cli_fd = atoi(buff);
setnonblocking(cli_fd);
ev.data.fd = cli_fd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, cli_fd, &ev) < ) {
printf("epoll add fd %d failed\n", cli_fd);
}
} else { // socket msg
cli_fd = events[i].data.fd;
handle_req(cli_fd);
}
}
}
} void *worker(void *arg)
{
int epfd, pipe_fd;
struct epoll_event ev; taskset_thread_core(((thread_arg*) arg)->id); pipe_fd = ((thread_arg*) arg)->fd;
epfd = epoll_create(MAXFDS);
setnonblocking(pipe_fd);
ev.data.fd = pipe_fd;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd, &ev) < ) {
printf("epoll add mq fail\n");
} run_epoll(epfd, pipe_fd); return ;
}
1.2.2 server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h> #include "common.h"
#include "worker.h" int start_listen()
{
int fd, opt = ;
struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if ((fd = socket(AF_INET, SOCK_STREAM, )) < ) {
printf("open socket failed!\n");
exit();
} setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if ((bind(fd, (struct sockaddr*) &servaddr, sizeof(servaddr))) < ) {
printf("bind failed!\n");
exit();
} if (listen(fd, SOMAXCONN) < ) {
printf("listen failed!\n");
exit();
} return fd;
} int main(int argc, char **argv)
{
int i, num_cores, listen_fd, cli_fd;
char name[]; listen_fd = start_listen(); num_cores = sysconf(_SC_NPROCESSORS_ONLN);
printf("core num: %d\n", num_cores); int pipe_fd[num_cores][];
thread_arg targ[num_cores];
pthread_t tid[num_cores];
pthread_attr_t attr; if (pthread_attr_init(&attr) != ) {
perror("pthrad attr init error: ");
exit();
} if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != ) {
perror("pthread set attr detached error: ");
exit();
} for (i = ; i < num_cores; i++) {
pipe(pipe_fd[i]);
targ[i] = (thread_arg) {i, pipe_fd[i][]}; if (pthread_create(&tid[i], &attr, worker, &targ[i]) != ) {
perror("pthread create error: ");
exit();
}
} pthread_attr_destroy(&attr);
sleep();
printf("server started\n\n"); while ((cli_fd = accept(listen_fd, NULL, NULL)) > ) {
sprintf(name, "%d", cli_fd);
i = cli_fd % num_cores;
write(pipe_fd[i][], name, strlen(name));
} close(listen_fd); for (i = ; i < num_cores; i++) {
close(pipe_fd[i][]);
} return ;
}
写完后台代码之后,开始测试能支持多少连接,但测试过程中一直有问题,会报如下的错误:error: Cannot assign requested address。
google了一下,说是因为短时间内大量短连接造成TIME_WAIT耗尽端口问题,不明白我的测试代码怎么是短连接,而不是长连接。
我的客户端代码如下,不知道是哪里出问题了。
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h> void process_conn_svr(const char *svr_ip, int svr_port); int connections = ; #define MAX_CONN 1005000
int fd[MAX_CONN]; int main(int argc, char **argv)
{
if (argc <= ) {
printf("usage: %s ip port\n", argv[]);
exit();
}
const char *ip = argv[];
int port = atoi(argv[]); pid_t pid = fork();
if (pid == ) {
process_conn_svr(ip, port);
} const char buf[] = "keepalive!";
for (;;) {
usleep(*);
for (int i = ; i < MAX_CONN; ++i) {
if (fd[i] != ) {
send(fd[i], buf, sizeof(buf), );
}
}
}
return ; } void process_conn_svr(const char *svr_ip, int svr_port)
{
int conn_idx = ;
for (;;) {
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, svr_ip, &serv_addr.sin_addr); serv_addr.sin_port = htons(svr_port);
int cli_fd = socket(AF_INET, SOCK_STREAM, );
if (cli_fd == -) {
goto sock_err;
} if (connect(cli_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -) {
goto sock_err;
} fd[conn_idx] = cli_fd;
conn_idx++; connections++;
printf("connections: %d, fd: %d\n", connections, cli_fd); if (connections % == ) {
printf("press Enter to continue: ");
getchar();
}
usleep(*);
} sock_err:
printf("error: %s\n", strerror(errno));
}
基于管道通知的百万并发长连接server模型的更多相关文章
- MarioTCP:一个单机可日30亿的百万并发长连接服务器
原文:http://blog.csdn.net/everlastinging/article/details/10894493 注:如果用此服务器做变长data的传输,请在业务处理函数中为input ...
- HttpAsyncClient 做并发长连接的一个实例
HttpAsyncClient 做并发长连接的一个实例 import java.util.concurrent.CountDownLatch; import org.apache.http.HttpR ...
- TCP 百万并发 数据连接测试 python+locust
过程笔记和总结 尝试一.locust 测试百万Tcp并发 另一种方式是使用jmeter 基础环境 服务端 虚拟机:Centos7.2 jdk 1.8 客户端 虚拟机: Centos7.2 python ...
- 基于netty实现的长连接,心跳机制及重连机制
技术:maven3.0.5 + netty4.1.33 + jdk1.8 概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...
- 微言Netty:百万并发基石上的epoll之剑
说道本章标题,相信很多人知道我在暗喻石中剑这个典故,在此典故中,天命注定的亚瑟很容易的就拔出了这把石中剑,但是由于资历不被其他人认可,所以他颇费了一番周折才成为了真正意义上的英格兰全境之王,亚瑟王.说 ...
- LinkedIn的即时消息:在一台机器上支持几十万条长连接
最近我们介绍了LinkedIn的即时通信,最后提到了分型指标和读回复.为了实现这些功能,我们需要有办法通过长连接来把数据从服务器端推送到手机或网页客户端,而不是许多当代应用所采取的标准的请求-响应模式 ...
- Comet技术详解:基于HTTP长连接的Web端实时通信技术
前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...
- 转:基于ASP.NET的Comet长连接技术解析
原文来自于: Comet技术原理 来自维基百科:Comet是一种用于web的技术,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询和iframe流. 简单的 ...
- Comet:基于 HTTP 长连接的“服务器推”技术解析
原文链接:http://www.cnblogs.com/deepleo/p/Comet.html 一.背景介绍 传统web请求,是显式的向服务器发送http Request,拿到Response后显示 ...
随机推荐
- jvm参数优化
一.HotSpot JVM 提供了三类参数 现在的JVM运行Java程序(和其它的兼容性语言)时在高效性和稳定性方面做的非常出色.例如:自适应内存管理.垃圾收集.及时编译.动态类加载.锁优化等.虽然有 ...
- ubuntukylin(64bit)安装推荐
UbuntuKylin是Ubuntu社区中面向中文用户的Ubuntu衍生版本,与麒麟系统没有关系.它是由工信部软件.集成电路促进中心(CSIP).国防科技大学(NUDT)与国际著名开源社区UBUNTU ...
- YII安装smarty-view-renderer扩展
smarty-view-renderer http://www.yiiframework.com/extension/smarty-view-renderer/ 相关下载及介绍:https://git ...
- 鸟哥的Linux私房菜 第十八章、认识系统服务 (daemons)
什么是 daemon 与服务 (service) Linux Daemon (守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些事件.它不需要用户输入就能运行 ...
- nyoj 757 期末考试【优先队列+贪心】
期末考试 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 马上就要考试了,小T有许多作业要做,而且每个老师都给出来了作业要交的期限,如果在规定的期限内没 交作业就会扣 ...
- DiscreteSeekBar---->SeekBar的使用
build: compile 'org.adw.library:discrete-seekbar:1.0.0' 在布局中的使用: <org.adw.library.widgets.discret ...
- 学习 opencv---(9)形态学图像处理(一):膨胀和腐蚀
本篇文章中,我们一起探究了图像处理中,最基本的形态学运算--膨胀与腐蚀.浅墨在文章开头友情提醒,用人物照片做腐蚀和膨胀的素材图片得到的效果会比较惊悚,毁三观的,不建议尝试.......... 一.理论 ...
- Mysql分表和分区的区别
一,什么是mysql分表,分区 什么是分表,从表面意思上看呢,就是把一张表分成N多个小表,具体请看mysql分表的3种方法 什么是分区,分区呢就是把一张表的数据分成N多个区块,这些区块可以在同一个磁盘 ...
- 墙裂推荐 iOS 资源大全
这是个精心编排的列表,它包含了优秀的 iOS 框架.库.教程.XCode 插件.组件等等. 这个列表分为以下几个部分:框架( Frameworks ).组件( Components ).测试( Tes ...
- JDK8新特性之接口
在JDK7及以前的版本中,接口中都是抽象方法,不能定义方法体,但是从jdk8开始,接口中可以定义静态的非抽象的方法,直接使用接口名调用静态方法,但是它的实现类的类名或者实例却不可以调用接口中的静态方法 ...