在做性能测试的时候,百万并发的长连接是否会耗尽反向代理的端口号呢?答案:不会。
解决方法如下:
多个后端配成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;
}