測試Linux下tcp最大鏈接數限制

如今作服務器開發不加上高併發根本沒臉出門,因此爲了之後吹水被別人懟「每天提升併發,你本身實現的最高併發是多少」的時候能義正言辭的懟回去,趁着元旦在家沒事決定本身寫個demo搞一搞。golang

這個測試主要是想搞明白Linux下哪些參數配置限制了鏈接數的最大值,上限是多少。shell

1、先說下demo的思路:

服務端用epoll實現,就是簡簡單單的接收鏈接,而後客戶端用go的goroutine,每一個goroutine就是簡單的創建鏈接,而後什麼也不作。服務器

上代碼:併發

server:app

  1 /*
  2  * g++ -o test_epoll ./test_epoll.c
  3  */
  4 #include <unistd.h>
  5 #include <sys/types.h>
  6 #include <sys/socket.h>
  7 #include <sys/epoll.h>
  8 #include <netinet/in.h>
  9 #include <arpa/inet.h>
 10 
 11 #include <stdio.h>
 12 #include <stdlib.h>
 13 #include <string.h>
 14 #include <errno.h>
 15 
 16 int SetReuseAddr(int fd)
 17 {
 18     int optval = 1;
 19     socklen_t optlen = sizeof(optval);
 20     return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
 21 }
 22 
 23 int main()
 24 {
 25     int fd = socket(AF_INET, SOCK_STREAM, 0);
 26     int iRet = SetReuseAddr(fd);
 27     if (iRet != 0)
 28     {
 29         printf("setsockopt for SO_REUSEADDR failed, error:%s\n", strerror(iRet));
 30         return iRet;
 31     }
 32 
 33     struct sockaddr_in addr;
 34     memset(&addr, 0, sizeof(addr));
 35     addr.sin_family = AF_INET;
 36     addr.sin_port = htons(8080);
 37     addr.sin_addr.s_addr = INADDR_ANY;
 38     if (bind(fd, (struct sockaddr*)&addr, sizeof(addr))  == -1)
 39     {
 40         printf("bind failed, error:%s\n", strerror(errno));
 41         return errno;
 42     }
 43 
 44     if (listen(fd, 5) == -1)
 45     {
 46         printf("listen failed, error:%s\n", strerror(errno));
 47         return errno;
 48     }
 49     printf("Listening on 8080...\n");
 50 
 51     int epfd = epoll_create(102400);
 52     struct epoll_event event;
 53     event.events = EPOLLIN;
 54     event.data.fd = fd;
 55     epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
 56 
 57     struct epoll_event revents[102400];
 58     int iOnline = 0;
 59     while (1)
 60     {
 61         int num = epoll_wait(epfd, revents, 102400, 60 * 1000);
 62         printf("epoll_wait return %d\n", num);
 63         if (num > 0)
 64         {
 65             for (int i = 0; i < num; i++)
 66             {
 67                 if (revents[i].data.fd == fd)
 68                 {
 69                     int client;
 70                     struct sockaddr_in cli_addr;
 71                     socklen_t cli_addr_len = sizeof(cli_addr);
 72                     client = accept(fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
 73                     if (client == -1)
 74                     {
 75                         printf("accept failed, error:%s\n", strerror(errno));
 76                         if (errno == EMFILE)
 77                         {
 78                             printf("per-process limit reached\n");
 79                             exit(errno);
 80                         }
 81                         if (errno == ENFILE)
 82                         {
 83                             printf("system-wide limit reached\n");
 84                             exit(errno);
 85                         }
 86                         continue;
 87                     }
 88 
 89                     iOnline++;
 90                     printf("Receive a new connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);
 91                     event.events = EPOLLIN;
 92                     event.data.fd = client;
 93                     epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
 94                 }
 95             }
 96         }
 97         printf("Online number:%d\n", iOnline);
 98     }
 99 
100     return 0;
101 }

client:socket

 1 package main
 2 
 3 import (
 4     "net"
 5     "fmt"
 6     "time"
 7     "strconv"
 8     "runtime"
 9 )
10 
11 func Connect(host string, port int) {
12     _, err := net.Dial("tcp", host+":"+strconv.Itoa(port))
13     if err != nil {
14         fmt.Printf("Dial to %s:%d failed\n", host, port)
15         return
16     }
17 
18     for {
19         time.Sleep(30 * 1000 * time.Millisecond)
20     }
21 }
22 
23 func main() {
24     count := 0
25     for {
26         go Connect("192.168.63.128", 8080)
27         count++;
28         fmt.Printf("Gorutue num:%d\n", runtime.NumGoroutine())
29         time.Sleep(100 * time.Millisecond)
30     }
31 }

注:博客園的代碼編輯器竟然尚未支持go,如今go用的人挺多的啦,但願快點支持啊。tcp

2、開始測試

第一次:

先說結果,鏈接數達到1031時accept失敗了,當時尚未對errno作判斷,因此只打印輸出了accept失敗。編輯器

而後首先想到的是ulimit -n的限制,查看了一下,默認值1024,而後就是修改這個值,在/etc/security/limits.conf中添加一下內容:ide

1 *    soft    nofile 102400
2 *    hard    nofile 102400

而後關閉當前xshell鏈接,從新鏈接即生效,如今看ulimit -n就是102400了。高併發

這兩行的意思就是將每一個進程能打開的文件描述符個數的soft、hard限制調整爲102400,

注:ulimit -n 102400也能夠生效,可是這個修改是臨時的。

而後進行第二次測試。

第二次:

逗比了,其實鏈接數只有2000+,我以前還在奇怪爲啥Windows的默認鏈接數能有這麼高呢,原來有些鏈接已經斷了,可是由於我沒有作處理,因此覺得還在呢,看來我得再安裝一個虛擬機了[二哈]

待繼續。。。

安裝虛擬機去,

時間:2017-12-31 00:09:00

虛擬機安裝好了,接着搞,

此次是真的超過10K了。

鏈接數還在增長,不知道能不能最終達到10萬呢,小小的期待ing

時間:2017-12-31 00:41:00,最終上限卡在28232,golang一直報dial失敗,因爲忘了打印出具體錯誤信息了,因此無從知道爲何dial失敗,因此只能再跑一次T_T

 時間:2017-12-31 01:01:00,添加打印dial失敗的錯誤信息的,又跑了一遍,仍是在28232時出現dial失敗,錯誤信息:

golang的標準庫文檔中麼有對錯誤信息的解釋,從錯誤信息來看,是分配地址失敗,因而想是否是端口地址範圍限制了。

 查看了一下端口地址範圍,確認就是這個限制,因爲端口地址是16位,因此,就算把這個端口地址範圍修改成1024--65535,也最多能開啓64521個鏈接,而我如今只有一臺虛擬機做爲客戶端,因此想要實現10萬鏈接是不可能了,可是經過此次測試,也讓我搞明白了,到底哪些參數會限制鏈接的上限,這就是我想要的。

最後,感謝Linux內核團隊的大神們推出了epoll這麼牛逼的機制,才使得咱們如今想實現高併發是如此的容易,但願本身有一天也能這麼牛逼,哈哈。

元旦假期就這麼過了,挺happy的,解決問題以後的這種快感可能就是咱們作技術的之因此對技術這麼入迷的緣由吧。

明天要出去happy了,但願新的一年,本身和家人都好好的。

相關文章
相關標籤/搜索