2019独角兽企业重金招聘Python工程师标准>>>

前言

测试一个非常简单服务器如何达到100万(1M=1024K连接)的并发连接,并且这些连接一旦连接上服务器,就不会断开,一直连着。 
环境受限,没有服务器,刚开始都是在自己的DELL笔记本上测试,凭借16G内存,和优秀的vmware workstation虚拟机配合,另外还得外借别人虚拟机使用,最终还得搭上两台2G内存的台式机(安装centos),最终才完成1M并发连接任务。

  • 测试程序也很简陋,一个C语言所写服务器程序,没有任何业务存在,收到请求后发送一些头部,不断开连接
  • 测试端程序也是使用C语言所写,发送请求,然后等待接收数据,仅此而已
  • 服务器端/测试端内存都受限(8G不够使用),要想完成1024K的目标,需要放弃一些东西,诸如业务不是那么完整
  • 一台分配10G内存Centos服务器,两台分配6G内存Centos测试端,两台2G内存Centos测试端
  • 假如热心的您可以提供丰富的服务器资源,那就再好不过了。
  • 理论上200万的并发连接(IO密集型),加上业务,40G-50G的内存大概能够保证

说明

以前也做过类似的工作,量不大,没记录下来,一些压力测试和调优,随着时间流逝,早已忘记。这次是从零开始,基本上所有过程都会记录,一步一步,每一步都会遇到问题,并且给出相关解决问题的方法,最终完成目标。 
为了方便,服务器端程序和客户端测试程序,都是使用C语言,不用像JAVA一样需要预先指定内存,感觉麻烦。使用较为原始的语言来写,可以避免不必要的调优工作。这中间,可能会穿插Java代码的思考方式。

可能需要懂点Linux,C,Java,假如您有更好的做法,或者建议,请直接告知,谢谢。

Linux系统

测试端和服务器端都选用较为熟悉的64位Centos 6.4,32位系统最多支持4G内存,太受限。IO密集型应用,对CPU要求不是很高。另外服务器确保安装上gcc,那就可以开工了。 
所有端系统一旦安装完之后,默认不做任何设置。

服务器端程序

服务器端程序依赖libev框架,需要提前编译,然后存放到相应位置。下面是具体服务器端代码:

#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>#include <unistd.h>#include "../include/ev.h"#define HTMLFILE_RESPONSE_HEADER \"HTTP/1.1 200 OK\r\n" \"Connection: keep-alive\r\n" \"Content-Type: text/html; charset=utf-8\r\n" \"Transfer-Encoding: chunked\r\n" \"\r\n"
#define HTMLFILE_RESPONSE_FIRST \"<html><head><title>htmlfile chunked example</title><script>var _ = function (msg) { document.getElementById('div').innerHTML = msg; };</script></head><body><div id=\"div\"></div>                                                                                                                                                                                                                                                                                                                                         "static int server_port = 8000;struct ev_loop *loop;
typedef struct {int fd;ev_io ev_read;
} client_t;ev_io ev_accept;static int usr_num;
static void incr_usr_num() {usr_num ++;printf("online user %d\n", usr_num);
}static void dec_usr_num() {usr_num --;printf("~online user %d\n", usr_num);
}static void free_res(struct ev_loop *loop, ev_io *ws);int setnonblock(int fd) {int flags = fcntl(fd, F_GETFL);if (flags < 0)return flags;flags |= O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) < 0)return -1;return 0;
}static int format_message(const char *ori_message, char *target_message) {return sprintf(target_message, "%X\r\n<script>_('%s');</script>\r\n", ((int)strlen(ori_message) + 23), ori_message);
}static void write_ori(client_t *client, char *msg) {if (client == NULL) {fprintf(stderr, "the client is NULL !\n");return;}write(client->fd, msg, strlen(msg));
}static void write_body(client_t *client, char *msg) {char body_msg[strlen(msg) + 100];format_message(msg, body_msg);write_ori(client, body_msg);
}static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {client_t *client = w->data;int r = 0;char rbuff[1024];if (revents & EV_READ) {r = read(client->fd, &rbuff, 1024);}if (EV_ERROR & revents) {fprintf(stderr, "error event in read\n");free_res(loop, w);return ;}if (r < 0) {fprintf(stderr, "read error\n");ev_io_stop(EV_A_ w);free_res(loop, w);return;}if (r == 0) {fprintf(stderr, "client disconnected.\n");ev_io_stop(EV_A_ w);free_res(loop, w);return;}write_ori(client, HTMLFILE_RESPONSE_HEADER);char target_message[strlen(HTMLFILE_RESPONSE_FIRST) + 20];sprintf(target_message, "%X\r\n%s\r\n", (int)strlen(HTMLFILE_RESPONSE_FIRST), HTMLFILE_RESPONSE_FIRST);write_ori(client, target_message);incr_usr_num();
}static void accept_cb(struct ev_loop *loop, ev_io *w, int revents) {struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int client_fd = accept(w->fd, (struct sockaddr *) &client_addr, &client_len);if (client_fd == -1) {fprintf(stderr, "the client_fd is  NULL !\n");return;}client_t *client = malloc(sizeof(client_t));client->fd = client_fd;if (setnonblock(client->fd) < 0)err(1, "failed to set client socket to non-blocking");client->ev_read.data = client;ev_io_init(&client->ev_read, read_cb, client->fd, EV_READ);ev_io_start(loop, &client->ev_read);
}int main(int argc, char const *argv[]) {int ch;while ((ch = getopt(argc, argv, "p:")) != -1) {switch (ch) {case 'p':server_port = atoi(optarg);break;}}printf("start free -m is \n");system("free -m");loop = ev_default_loop(0);struct sockaddr_in listen_addr;int reuseaddr_on = 1;int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd < 0)err(1, "listen failed");if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)err(1, "setsockopt failed");memset(&listen_addr, 0, sizeof(listen_addr));listen_addr.sin_family = AF_INET;listen_addr.sin_addr.s_addr = INADDR_ANY;listen_addr.sin_port = htons(server_port);if (bind(listen_fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0)err(1, "bind failed");if (listen(listen_fd, 5) < 0)err(1, "listen failed");if (setnonblock(listen_fd) < 0)err(1, "failed to set server socket to non-blocking");ev_io_init(&ev_accept, accept_cb, listen_fd, EV_READ);ev_io_start(loop, &ev_accept);ev_loop(loop, 0);return 0;
}static void free_res(struct ev_loop *loop, ev_io *w) {dec_usr_num();client_t *client = w->data;if (client == NULL) {fprintf(stderr, "the client is NULL !!!!!!");return;}ev_io_stop(loop, &client->ev_read);close(client->fd);free(client);
}

编译

gcc server.c -o server ../include/libev.a -lm

运行

./server -p 8000

在源码中默认指定了8000端口,可以通过-p进行指定新的端口。 开启了8000端口进行监听请求,http协议处理类似于htmlfile chunked块编码传输。

测试服务器端程序

测试程序使用libevent框架,因其使用简单,提供丰富易用接口,但需要提前下载,手动安装:

wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar xvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr
make
make install

注意make和make install需要root用户。

测试端程序

client1.c 源码:

#include <sys/types.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <stdlib.h>
#include <err.h>
#include <event.h>
#include <evhttp.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>#define BUFSIZE 4096
#define NUMCONNS 62000
#define SERVERADDR "192.168.190.133"
#define SERVERPORT 8000
#define SLEEP_MS 10char buf[BUFSIZE];int bytes_recvd = 0;
int chunks_recvd = 0;
int closed = 0;
int connected = 0;void chunkcb(struct evhttp_request *req, void *arg) {int s = evbuffer_remove( req->input_buffer, &buf, BUFSIZE );bytes_recvd += s;chunks_recvd++;if (connected >= NUMCONNS && chunks_recvd % 10000 == 0)printf(">Chunks: %d\tBytes: %d\tClosed: %d\n", chunks_recvd, bytes_recvd, closed);
}void reqcb(struct evhttp_request *req, void *arg) {closed++;
}int main(int argc, char **argv) {event_init();struct evhttp *evhttp_connection;struct evhttp_request *evhttp_request;char path[32]; // eg: "/test/123"int i;for (i = 1; i <= NUMCONNS; i++) {evhttp_connection = evhttp_connection_new(SERVERADDR, SERVERPORT);evhttp_set_timeout(evhttp_connection, 864000); // 10 day timeoutevhttp_request = evhttp_request_new(reqcb, NULL);evhttp_request->chunk_cb = chunkcb;sprintf(&path, "/test/%d", ++connected);if (i % 100 == 0)  printf("Req: %s\t->\t%s\n", SERVERADDR, &path);evhttp_make_request( evhttp_connection, evhttp_request, EVHTTP_REQ_GET, path );evhttp_connection_set_timeout(evhttp_request->evcon, 864000);event_loop( EVLOOP_NONBLOCK );if ( connected % 200 == 0 )printf("\nChunks: %d\tBytes: %d\tClosed: %d\n", chunks_recvd, bytes_recvd, closed);usleep(SLEEP_MS * 1000);}event_dispatch();return 0;
}

备注:这部分代码参考了A Million-user Comet Application with Mochiweb, Part 3 ,根据需要有所修改。

编译

gcc -o client1 client1.c -levent

运行

./client1

可能在64位系统会遇到找不到libevent-2.0.so.5情况,需要建立一个软连接

ln -s /usr/lib/libevent-2.0.so.5 /lib64/libevent-2.0.so.5

即可自动连接IP地址为192.168.190.133:8000的服务器端应用。

第一个遇到的问题:文件句柄受限

测试端程序输出

看看测试端程序client1输出的错误信息:

Chunks: 798 Bytes: 402990 Closed: 0
Req: 192.168.190.133 -/test/900
Req: 192.168.190.133 -/test/1000
Chunks: 998 Bytes: 503990 Closed: 0
[warn] socket: Too many open files
[warn] socket: Too many open files
[warn] socket: Too many open files

服务器端程序输出

服务器端最后一条日志为

online user 1018

两边都遇到了文件句柄打开的情况。 
在服务器端查看已经连接,并且端口号为8000的所有连接数量:

netstat -nat|grep -i "8000"|wc -l
1019

但与服务器端输出数量对不上,增加所有已经建立连接的选项:

netstat -nat|grep -i "8000"|grep ESTABLISHED|wc -l
1018

那么剩下的一条数据到底是什么呢?

netstat -nat|grep -i "8000"|grep -v ESTABLISHED
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN

也就是server.c监听的端口,数量上对的上。

在测试服务器端,查看测试进程打开的文件句柄数量

lsof -n|grep client1|wc -l
1032

再次执行

ulimit -n
1024

也是就是client1应用程序共打开了1032个文件句柄,而不是1024,为什么? 
把当前进程所有打开的文件句柄保存到文件中,慢慢研究 lsof -n|grep client1 > testconnfinfo.txt

导出的文件可以参考: https://gist.github.com/yongboy/5260773
除了第一行,我特意添加上供友善阅读的头部列定义,也就是1032行信息,但是需要注意头部:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
client1 3088 yongboy cwd DIR 253,0 4096 800747 /home/yongboy/workspace/c_socket.io_server/test
client1 3088 yongboy rtd DIR 253,0 4096 2 /test_conn
client1 3088 yongboy txt REG 253,0 9697 799991 /home/yongboy/workspace/c_socket.io_server/test/test_conn_1
client1 3088 yongboy mem REG 253,0 156872 50404 /lib64/ld-2.12.so
client1 3088 yongboy mem REG 253,0 1922152 78887 /lib64/libc-2.12.so
client1 3088 yongboy mem REG 253,0 145720 76555 /lib64/libpthread-2.12.so
client1 3088 yongboy mem REG 253,0 47064 69491 /lib64/librt-2.12.so
client1 3088 yongboy mem REG 253,0 968730 26292 /usr/lib/libevent-2.0.so.5.1.9
client1 3088 yongboy 0u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 1u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 2u CHR 136,2 0t0 5 /dev/pts/2
client1 3088 yongboy 3u REG 0,9 0 4032 anon_inode
client1 3088 yongboy 4u unix 0xffff88007c82f3c0 0t0 79883 socket
client1 3088 yongboy 5u unix 0xffff880037c34380 0t0 79884 socket
client1 3088 yongboy 6u IPv4 79885 0t0 TCP 192.168.190.134:58693->192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 7u IPv4 79889 0t0 TCP 192.168.190.134:58694->192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 8u IPv4 79891 0t0 TCP 192.168.190.134:58695->192.168.190.133:irdmi (ESTABLISHED)
client1 3088 yongboy 9u IPv4 79893 0t0 TCP 192.168.190.134:58696->192.168.190.133:irdmi (ESTABLISHED)

可以看到文件句柄是从0u开始,0u上面的8个(5个mem + 3个启动)进程,1032 - 8 = 1024个文件句柄,这样就和系统限制的值吻合了。

root用户编辑/etc/security/limits.conf文件添加:

* soft nofile 1048576
* hard nofile 1048576
  • soft是一个警告值,而hard则是一个真正意义的阀值,超过就会报错。
  • soft 指的是当前系统生效的设置值。hard 表明系统中所能设定的最大值
  • nofile - 打开文件的最大数目
  • 星号表示针对所有用户,若仅针对某个用户登录ID,请替换星号

注意: 
1024K x 1024 = 1048576K = 1M,1百万多一点。

备注:测试端和服务器端都需要作此设置,保存退出,然后reboot即可生效。

第一个问题,就这样克服了。再次运行 /client1测试程序,就不会出现受打开文件句柄的限制。但大概在测试端打开对外28200个端口时,会出现程序异常,直接退出。

段错误

这个也是程序没有处理端口不够用的异常,但可以通过增加端口进行解决。

备注: 但测试端单机最多只能打开6万多个连接,是一个问题,如何克服,下一篇解决此问题,并且还会遇到文件句柄的受限问题。

转载于:https://my.oschina.net/kaixindewo/blog/151218

100万并发连接服务器笔记之准备篇相关推荐

  1. 100万并发连接服务器笔记之处理端口数量受限问题

    第二个遇到的问题:端口数量受限 一般来说,单独对外提供请求的服务不用考虑端口数量问题,监听某一个端口即可.但是向提供代理服务器,就不得不考虑端口数量受限问题了.当前的1M并发连接测试,也需要在客户端突 ...

  2. 100万并发连接服务器笔记之测试端就绪

    重新编写测试端程序 测试端程序需要增加绑定本机IP和本地端口的功能,以尽可能的向外发出更多的tcp请求.需要对client1.c重构,增加参数传递. 下面是client2.c的代码 123456789 ...

  3. 100万并发连接服务器笔记之1M并发连接目标达成

    第四个遇到的问题:tcp_mem 在服务端,连接达到一定数量,诸如50W时,有些隐藏很深的问题,就不断的抛出来. 通过查看dmesg命令查看,发现大量TCP: too many of orphaned ...

  4. 100万并发连接服务器笔记之Erlang完成1M并发连接目标

    http://www.blogjava.net/yongboy/archive/2013/04/28/398558.html 转载于:https://www.cnblogs.com/ziyouchut ...

  5. 100万并发连接服务器

    100万并发连接服务器笔记之准备篇 前言 测试一个非常简单服务器如何达到100万(1M=1024K连接)的并发连接,并且这些连接一旦连接上服务器,就不会断开,一直连着.  环境受限,没有服务器,刚开始 ...

  6. 访问量100万的网站服务器,100万访问量 服务器配置

    100万访问量 服务器配置 内容精选 换一换 弹性负载均衡有不同的负载均衡,分别是共享型负载均衡和独享型负载均衡,便于用户根据不同的应用场景和功能需求选择合适的负载均衡器类型.共享型负载均衡:适用于访 ...

  7. MySQL单机并发量_mysql百万并发量-MySQL集群能支持100万个并发请求吗

    当然支持100万并发. 首先,我们必须做出决定,把阅读和写作分开. 然后,它取决于你需要分配多少个单元用于写作和阅读. 我的SQL集群不建议您使用它,因为有太多的错误. 所有这些都需要先进行压力测试. ...

  8. php万人同时连接,5G能容纳多少人同时上网?号称100万,实际1万人就卡了!

    不到1万人连接5G就卡了? 今年的世界互联网大会,5G成了理所当然的焦点,然而互联网大会上的5G速度却很卡. 大会现场雷军发出互联网上大会的5G连网速度,下行速度是81.5M,上行速度是32.49M. ...

  9. [NewLife.Net]单机400万长连接压力测试

    目标 对网络库NewLife.Net进行单机百万级长连接测试,并持续收发数据,检测网络库稳定性. [2020年8月1日晚上22点] 先上源码:https://github.com/NewLifeX/N ...

最新文章

  1. vue总结 08状态管理vuex
  2. jquery中this与$(this)的用法区别.
  3. 云计算之KVM虚拟化实战
  4. android json 解析图片路径,Android 使用Gson解析Asset 目录下的.json文件
  5. PHP常量:define和const的不同之处
  6. linux-history历史命令-光标的移动操作-命令行上的字符删除操作
  7. Python-cvxopt库的使用(2)(解决QP问题)
  8. 远控免杀从入门到实践(2)工具总结篇
  9. FL Studio 20.9水果编曲软件中文汉化补丁包
  10. [问题已处理]-centos7 history命令没有任何记录
  11. 书写NDIS过滤钩子驱动实现ip包过滤
  12. 飞行CSS3导航菜单
  13. [2019.7.31~2019.8.15]纪中集训游记
  14. 如何基于 RISC-V CPU 集成一个 RISC-V SoC 呢?(下)
  15. SAP-MM科目自动分配解析-(4-1)- 物料的配置影响
  16. 微信小程序——点击不同的标签,弹出显示不同的内容
  17. 职场上,不知道这些,活该工资比人低一倍
  18. 恶意软件与反病毒技术
  19. 康德、孔子和休谟的对话
  20. 女生挖别人家男朋友大攻略

热门文章

  1. Set集合框架(HashSet and TreeSet)
  2. linux kvm图标需要安装的软件,KVM 图形化安装
  3. termux添加php镜像,termux | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror...
  4. 线性回归(二)---多元线性回归
  5. 零基础学python看什么书好?
  6. 前端开发者正在被迫成为全栈开发人员
  7. 一个声称南方985普通学生,前端学习10个月,秋招斩获字节跳动offer的分享!
  8. 自从知道了这几个 JavaScript 技巧,下班都变早了!
  9. mysql数据库文件上传大小控制_[mysql数据库文件大小限制]mysql导入数据库文件最大限制设置...
  10. python新手入门到放弃_python萌新:从零基础入门到放弃