在做性能測試的時候,百萬並發的長連接是否會耗盡反向代理的端口號呢?答案:不會。
解決方法如下:
多個后端配成upstream,反向代理的端口會被復用,沒有問題。
如果后端是一對一的,源目的端口相同,那就沒必要用反向代理了。
如果是需要一個類似的中轉過濾的部件,那么你需要自己設計一個部件。
說到這個問題,還是要從當初還是一個新手的時候說起。
新手疑問
新手對於長連接的幾個疑問?
1.與一個服務端進行長連接是否會消耗服務端的端口號?
2.與多個服務端進行長連接是否會消耗客戶端的端口號?
答案:
1.完全不消耗;
2.可以不消耗:
TCP的連接的“主鍵”是(源IP,目的IP,源端口,目的端口)
也就是說,只要源IP、目的IP、源端口、目的端口四個因素只要有一個不同就可以標識不同的TCP連接。
對於問題1,大家都很常見,沒有什么異議。因為客戶端主動與服務端進行連接,服務端的端口是復用的,我們從來沒有為服務端的端口數擔心過。
對於問題2,大家不一定常見。比如說nginx作為反向代理進行長連接,這時,是否會消耗代理服務器的端口號?那肯定是消耗的。
為什么之前沒聽說有端口影響並發的?
但是我們通常沒有遇到這種問題,為什么呢?
因為這個問題完全不用擔心。我們分幾種情況討論。
我們假定有個業務(P2P的長連接)是這樣的:A->B->C->D
A: 表示大量不同IP的客戶端;
B: 表示一個類似反向代理的部件;
C: 表示后端的服務;
D: 表示大量不同IP的目的客戶端;
那么,分析如下:
A->B: B只提供一個端口服務,不會耗用B的端口;
B->C: 源IP端口和目的IP端口都相同,只要建立有數的幾個長連接就可以轉發了(即B->C的連接做成一個連接池),沒必要每個A的會話建立多個連接,因此也不用消耗端口:
C->D: 目的IP不同,C可以綁定一個端口去主動連接D,不會消耗端口,或者D反向連接C,這樣C變成了TCP的服務端,也不會消耗端口.
所以在長連接並發的情況下,端口不會成為整個軟件解決方案的瓶頸。
TCP指定客戶端的端口號
什么?你不知道客戶端可以主動綁定端口去連接服務端?
那可以參考我寫的Demo代碼:
結果如下:
./tcpserver(啟動監聽10001)
./tcpserver(啟動監聽10002)
./tcpcli 192.168.0.1 10001 hello
./tcpcli 192.168.0.1 10002 hello
然后可以看到如下結果:
[root@master135 ~]# netstat -lanp | grep tcpcli
tcp 0 0 192.168.0.1:12345 192.168.0.2:10002 ESTABLISHED 22808/./tcpcli2
tcp 0 0 192.168.0.1:12345 192.168.0.2:10001 ESTABLISHED 22805/./tcpcli2
服務端:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXLINE 1024
#define OPEN_MAX 16
#define SERV_PORT 10001
int main()
{
int i , maxi ,listenfd , connfd , sockfd ,epfd, nfds;
int n;
char buf[MAXLINE];
struct epoll_event ev, events[20];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr , servaddr;
listenfd = socket(AF_INET , SOCK_STREAM , 0);
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd , (struct sockaddr *) & servaddr, sizeof(servaddr));
listen(listenfd,10);
epfd = epoll_create(256);
ev.data.fd=listenfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
for(;;)
{
nfds=epoll_wait(epfd,events,20,500);
for(i=0; i<nfds; i++)
{
if (listenfd == events[i].data.fd)
{
clilen = sizeof(cliaddr);
connfd = accept(listenfd , (struct sockaddr *)&cliaddr, &clilen);
if(connfd < 0)
{
perror("connfd < 0");
exit(1);
}
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if (events[i].events & EPOLLIN)
{
if ( (sockfd = events[i].data.fd) < 0)
continue;
n = recv(sockfd,buf,MAXLINE,0);
if (n <= 0)
{
close(sockfd);
events[i].data.fd = -1;
}
else
{
buf[n]='\0';
printf("Socket %d said : %s\n",sockfd,buf);
ev.data.fd=sockfd;
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,connfd,&ev);
}
}
else if( events[i].events&EPOLLOUT )
{
sockfd = events[i].data.fd;
send(sockfd, "Hello!", 7, 0);
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else
{
printf("This is not avaible!");
}
}
}
close(epfd);
return 0;
}
客戶端:
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(int argc, char *argv[])
{
if (argc != 4)
{
cout << "usage: " << argv[0] << " ip port message" << endl;
return -1;
}
char *szIp = argv[1];
in_addr_t iIp = inet_addr(szIp);
if (iIp == INADDR_NONE)
{
cerr << "fail to parse ip: " << szIp << endl;
return -1;
}
char *pEnd = NULL;
uint16_t usPort = strtoul(argv[2], &pEnd, 10);
if (*pEnd != '\0')
{
cerr << "fail to parse port: " << argv[2] << endl;
return -1;
}
char *szMsg = argv[3];
size_t uiMsgLen = strlen(szMsg);
int iSockFd = socket(AF_INET, SOCK_STREAM, 0);
if (iSockFd < 0)
{
cerr << "fail to create socket, err: " << strerror(errno) << endl;
return -1;
}
cout << "create socket fd " << iSockFd << endl;
sockaddr_in client;
memset(&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_addr.s_addr = htonl(INADDR_ANY);
client.sin_port = htons(12345);
setsockopt(iSockFd,SOL_SOCKET,SO_REUSEADDR,&client,sizeof(client));
sockaddr_in oServerAddr;
memset(&oServerAddr, 0, sizeof(oServerAddr));
oServerAddr.sin_family = AF_INET;
oServerAddr.sin_addr.s_addr = iIp;
oServerAddr.sin_port = htons(usPort);
if (bind(iSockFd, (sockaddr *)&client, sizeof(client)) < 0)
{
cerr << "bind failed, err: " << strerror(errno) << endl;
return -1;
}
if (connect(iSockFd, (sockaddr *)&oServerAddr, sizeof(oServerAddr)) < 0)
{
cerr << "fail to connect to " << szIp << ":" << usPort << ", err: " << strerror(errno) << endl;
return -1;
}
cout << "connect to " << szIp << ":" << usPort << endl;
ssize_t iSendLen = send(iSockFd, szMsg, uiMsgLen, 0);
if (iSendLen < 0)
{
cerr << "fail to send, err: " << strerror(errno) << endl;
return -1;
}
cout << "send len: " << iSendLen << ", msg: " << szMsg << endl;
char szRecvBuf[1024 * 1024];
char *pRecvBuf = szRecvBuf;
size_t uiBufLen = sizeof(szRecvBuf);
size_t uiRecvTotal = 0;
while (uiRecvTotal < iSendLen)
{
ssize_t iRecvLen = recv(iSockFd, pRecvBuf, uiBufLen, 0);
if (iRecvLen < 0)
{
cerr << "fail to recv, close connection, err: " << strerror(errno) << endl;
close(iSockFd);
return -1;
}
if (iRecvLen == 0)
{
cout << "connection closed by server" << endl;
close(iSockFd);
break;
}
pRecvBuf += iRecvLen;
uiBufLen -= iRecvLen;
uiRecvTotal += iRecvLen;
}
szRecvBuf[uiRecvTotal] = '\0';
cout << "recv len: " << uiRecvTotal << ", msg: " << szRecvBuf << endl;
usleep(1000000000);
close(iSockFd);
return 0;
}