1. 程式人生 > >單機最大TCP連線數

單機最大TCP連線數

今天寫程式用到epoll的模型,翻出原先的程式碼跑了一下,看到原來define的最大的處理使用者上限,感覺有些不妥,所以決定測試一下我的ubuntu 16.04,1G記憶體的單機上究竟可以建立多少個連線。雖然網上有很多這方面的案例,但是我還是決定自己測試一下,印象深刻,對問題場景有更深的印象。

如果文中有錯誤或者不全面的地方,希望大家指正。

糾正部落格中的錯誤:我的客戶端和伺服器在單機上進行,所以其實埠是被客戶端消耗掉的,所以我本文中說的埠限制只能限制開放的客戶端的上限,實際並不能限制伺服器處理的連線數的上限,理論上一臺伺服器處理的連線數在記憶體足夠多的情況下是可以處理無數連線的(2^48理論數量)。

這篇文章中我沒有理解的概念是listen的soccketfd和accept返回的socketfd的不同,listen的socket建立的socketfd繫結的埠,這個socketfd是告訴TCP/IP協議棧這個socketfd指向繫結的ip+port,以後有發往這個ip+port的資料包都是發給這個socket,accept返回的connfd是用來標識一個連線五元組的,所以,tcp伺服器能處理的連線數實際是由五元組來確定的,更準確的說是由客戶端的ip+port來決定連線數的,對於ipv4地址2^32,port是2^16,所以理論的連線數最多是2^48。但實際情況中這個和裝置的記憶體,一條tcp連線佔用的記憶體有關,所以,要切記,65535並不是單機伺服器處理的連線數上限。65535硬要說是上限,那就是單機開放不同客戶端的連線數。實際這也是不確切的,單機情況下,可以通過設定虛擬ip來突破單機65535這個上限。後續我會把我單機測試的伺服器最大連線數的測試例項、如何修改核心引數來增大這個連線數的方法發出來。

我測試程式碼是自己寫的,也很簡單,就是客戶端程式不停的請求連線(這裡都是短連結,長連線和需要資料互動的連線請求情況可能和本文的情況不同,長連線就不會有大量的time_wait情況產生),伺服器接收了客戶端的請求建立連線,這種情況下,客戶端不停的建立socket描述符,起初我的系統設定的上限是1024(ulimit -n檢視)。

測試程式程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)\
    do{\
        perror(m); \
        exit(1); \
    }while(0)
int main(void)
{

    int socketfd, connfd;
    while(1)
    {
    socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if(socketfd < 0)
        ERR_EXIT("SOCKET");
    printf("socketfd = %d\n", socketfd);
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serveraddr.sin_port = htons(6888);

    //無限迴圈建立連線,不做資料處理
    int opt = 1;
    int ret2 = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if(ret2 < 0)
        ERR_EXIT("setsockopt");
        printf("1\n");
        socklen_t socklen = sizeof(serveraddr);
        connfd = connect(socketfd, (const struct sockaddr*)&serveraddr, socklen);
        if(connfd < 0)
            ERR_EXIT("connfd");
    }
    close(socketfd);
}

伺服器程式碼:

#include  <unistd.h>
#include  <sys/types.h>       /* basic system data types */
#include  <sys/socket.h>      /* basic socket definitions */
#include  <netinet/in.h>      /* sockaddr_in{} and other Internet defns */
#include  <arpa/inet.h>       /* inet(3) functions */
#include <sys/epoll.h> /* epoll function */
#include <fcntl.h>     /* nonblocking */
#include <sys/resource.h> /*setrlimit */

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#define MAXEPOLLSIZE 65535
#define MAXLINE 10
int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
        return -1;
    }
    return 0;
}

static int iCount = 0;
static int iFaild = 0;
int main(void)
{
    int  servPort = 6888;
    int listenq = 65535;

    int listenfd, connfd, kdpfd, nfds, n, curfds, acceptCount = 0;
    struct sockaddr_in servaddr, cliaddr;
    socklen_t socklen = sizeof(struct sockaddr_in);
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    struct rlimit rt;
    char buf[MAXLINE];

    /* 設定每個程序允許開啟的最大檔案數 */
    rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
    if (setrlimit(RLIMIT_NOFILE, &rt) == -1){
        perror("setrlimit error");
        return -1;
    }
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET; 
    servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
    servaddr.sin_port = htons (servPort);

    listenfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (listenfd == -1) {
        perror("can't create socket file");
        return -1;
    }

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    if (setnonblocking(listenfd) < 0) {
        perror("setnonblock error");
    }

    if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1){
        perror("bind error");
        return -1;
    } 
    if (listen(listenfd, listenq) == -1){
        perror("listen error");
        return -1;
    }
    /* 建立 epoll 控制代碼,把監聽 socket 加入到 epoll 集合裡 */
    kdpfd = epoll_create(MAXEPOLLSIZE);
    //ev.events = EPOLLIN | EPOLLET;
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listenfd, &ev) < 0){
        fprintf(stderr, "epoll set insertion error: fd=%d\n", listenfd);
        return -1;
    }
    curfds = 1;

    printf("epollserver startup,port %d, max connection is %d, backlog is %d\n", servPort, MAXEPOLLSIZE, listenq);

    for (;;) {
        /* 等待有事件發生 */
        nfds = epoll_wait(kdpfd, events, curfds, -1);
        if (nfds == -1){
            perror("epoll_wait");
            continue;
        }
        printf("epoll_wait return\n");
        
        /* 處理所有事件 */
        for (n = 0; n < nfds; ++n){
            if (events[n].data.fd == listenfd){
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&socklen);
                if (connfd < 0){
                    iFaild ++;
                    if(iFaild >= 10)
                        close(listenfd);
                    perror("accept error");
                    continue;
                }
                iCount++;
                sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
                printf("%d:%s  %d", ++acceptCount, buf, iCount);
                close(connfd);
                continue;
                } 
        }
    }
    close(listenfd);
    return 0;
}

伺服器程式碼採用epoll,邏輯也簡單,有必要說明一點,需要注意close(connfd)的位置,連線建立立馬斷開連線。還有需要設定SO_REUSEADDR。

ulimit -n 1024情況下:


這時候建立的連線數是1021個短連結,客戶端請求了1021個socket描述符,012三個描述符被佔用。

單機處理1024個連線輕而易舉。

我們來看看提高file descriptors的上限,ulimit -n 65535(埠號是16位無符號整數)

我們再來執行,需要一點時間。

結果是:


意思是,沒有可用埠了,我們從伺服器端看看一共建立了多少次連線


你會不會想這個資料從何而來?我可以告訴你,其實如果你的機器速度快,這個資料在預設情況下應該是28232,為什麼這莫說呢?別忘了我們建立的是短連結,我們的伺服器是主動關閉連線的一方,熟悉tcp四揮手的你應該想知道主動關閉的一方在呼叫了close之後會進入TIME_WAIT狀態,我之所以說我的這個資料是28386是因為我的執行時間大於linux預設的timewait時間,有的埠可以複用了,所以這個數值大於28233,那麼28232是怎麼來的呢?

我們執行這一條命令:

sysctl -a | grep port 

得到如下結果:


注意這一行:系統給我們的可用埠的範圍是32768-60999,我們計算60999-32768+1正好是28232。所以說我們可以修改這個引數範圍來提高支援的連線數。上面還提到產生了大量的timewait,我通過命令:

netstat -nt | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}' 

檢視,也可以使用這個命令,netstat -a | grep -i TIME_WAIT | wc -l 結果相同。


用netstat -tcp更加直觀:


提一下,之前我的測試伺服器close(connfd)這一句並不是建立連線立即呼叫的,而是等客戶端關閉然後伺服器才呼叫,所以伺服器是被動關閉的一方,你也可以試一試,這時候伺服器會產生大量的closewait:

可能CLOSE_WAIT產生的原因:


大多數都是close的問題。