并发网络连接数因端口数量受限问题

遇到的问题:端口数量受限

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

单机端口上限为65536

端口为16进制,那么2的16次方值为65536,在Linux系统里面,1024以下端口都是超级管理员用户(如root)才可以使用,普通用户只能使用大于1024的端口值。 
系统提供了默认的端口范围:

cat /proc/sys/net/ipv4/ip_local_port_range 
32768 61000

大概也就是共61000-32768=28232个端口可以使用,单个IP对外只能发送28232个TCP请求。 
以管理员身份,把端口的范围区间增到最大:

echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range

现在有64511个端口可用. 
以上做法只是临时,系统下次重启,会还原。 更为稳妥的做法是修改/etc/sysctl.conf文件,增加一行内容

net.ipv4.ip_local_port_range= 1024 65535

保存,然后使之生效:

sysctl -p

现在可以使用的端口达到64510个(假设系统所有运行的服务器是没有占用大于1024的端口的,较为纯净的centos系统可以做到),要想达到50万请求,还得再想办法。

增加IP地址

一般假设本机网卡名称为 eth0,那么手动再添加几个虚拟的IP:

ifconfig eth0:1 192.168.190.151 
ifconfig eth0:2 192.168.190.152 ......

或者偷懒一些:

for i in `seq 1 9`; do ifconfig eth0:$i 192.168.190.15$i up ; done

这些虚拟的IP地址,一旦重启,或者 service network restart 就会丢失。

为了模拟较为真实环境,在测试端,手动再次添加9个vmware虚拟机网卡,每一个网卡固定一个IP地址,这样省去每次重启都要重新设置的麻烦。

192.168.190.134 
192.168.190.143
192.168.190.144
192.168.190.145
192.168.190.146
192.168.190.147
192.168.190.148
192.168.190.149
192.168.190.150
192.168.190.151

在server服务器端,手动添加桥接网卡和NAT方式网卡

192.168.190.230
192.168.190.240
10.95.20.250

要求测试端和服务器端彼此双方都是可以ping通。

网络四元组/网络五元组

四元组是指的是

{源IP地址,源端口,目的IP地址,目的端口}

五元组指的是(多了协议)

{源IP地址,目的IP地址,协议号,源端口,目的端口}

在《UNIX网络编程卷1:套接字联网API(第3版)》一书中,是这样解释:

一个TCP连接的套接字对(socket pari)是一个定义该连接的两个端点的四元组,即本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。套接字对唯一标识一个网络上的每个TCP连接。

......

标识每个端点的两个值(IP地址和端口号)通常称为一个套接字。

以下以四元组为准。在测试端四元组可以这样认为:

{本机IP地址,本机端口,目的IP地址,目的端口}

请求的IP地址和目的端口基本上是固定的,不会变化,那么只能从本机IP地址和本机端口上考虑,端口的范围一旦指定了,那么增加IP地址,可以增加对外发出的请求数量。假设系统可以使用的端口范围已经如上所设,那么可以使用的大致端口为64000个,系统添加了10个IP地址,那么可以对外发出的数量为 64000 * 10 = 640000,数量很可观。

只有{源IP地址,源端口}确定对外TCP请求数量

经测试,四元组里面,只有{源IP地址,源端口}才能够确定对外发出请求的数量,跟{目的IP地址,目的端口}无关。

测试环境

在server端,并且启动./server两次,分别绑定8000端口和9000端口

./server -p 8000
./server -p 9000

本机IP、端口绑定测试程序

这里写一个简单的测试绑定本机IP地址和指定端口的客户端测试程序。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
          
#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 <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>
#include <errno.h>
#define BUFSIZE 4096
#define SLEEP_MS 10
char buf[BUFSIZE];
int bytes_recvd = 0;
int chunks_recvd = 0;
void chunkcb(struct evhttp_request *req, void *arg) {
int s = evbuffer_remove( req->input_buffer, &buf, BUFSIZE );
bytes_recvd += s;
chunks_recvd++;
printf(">Chunks: %d\tBytes: %d\n", chunks_recvd, bytes_recvd);
}
void reqcb(struct evhttp_request *req, void *arg) {
fprintf(stderr, ">Now closed\n");
exit(-1);
}
void err_cb(int err){
fprintf(stderr, "setup failed(errno = %d): %s", errno, strerror(errno));
}
int main(int argc, char **argv) {
char server_ip[16] = "";
int server_port = 0;
char local_ip[16] = "";
int local_port = 0;
int ch;
while ((ch = getopt(argc, argv, "h:p:c:o:")) != -1) {
switch (ch) {
case 'h':
printf("remote host is %s\n", optarg);
strncpy(server_ip, optarg, 15);
break;
case 'p':
printf("remote port is %s\n", optarg);
server_port = atoi(optarg);
break;
case 'c':
printf("local ip is %s\n", optarg);
strncpy(local_ip, optarg, 15);
break;
case 'o':
printf("local port is %s\n", optarg);
local_port = atoi(optarg);
break;
}
}
event_init();
event_set_fatal_callback(err_cb);
struct evhttp *evhttp_connection;
struct evhttp_request *evhttp_request;
char path[32];
evhttp_connection = evhttp_connection_new(server_ip, server_port);
evhttp_connection_set_local_address(evhttp_connection, local_ip);
evhttp_connection_set_local_port(evhttp_connection, local_port);
evhttp_set_timeout(evhttp_connection, 864000); // 10 day timeout
evhttp_request = evhttp_request_new(reqcb, NULL);
evhttp_request->chunk_cb = chunkcb;
sprintf(&path, "/test/%d", local_port);
evhttp_make_request( evhttp_connection, evhttp_request, EVHTTP_REQ_GET, path );
evhttp_connection_set_timeout(evhttp_request->evcon, 864000);
event_loop( EVLOOP_NONBLOCK );
usleep(SLEEP_MS * 10);
event_dispatch();
return 0;
}
view raw client3.c hosted with ❤ by  GitHub

可以看到libevent-*/include/event2/http.h内置了对绑定本地IP地址的支持:

/** sets the ip address from which http connections are made */
void evhttp_connection_set_local_address(struct evhttp_connection *evcon,
const char *address);

不用担心端口,系统自动自动随机挑选,除非需要特别指定:

/** sets the local port from which http connections are made */
void evhttp_connection_set_local_port(struct evhttp_connection *evcon,
ev_uint16_t port);

编译

gcc -o client3 client3.c -levent

client3运行参数为

  • -h 远程主机IP地址
  • -p 远程主机端口
  • -c 本机指定的IP地址(必须可用)
  • -o 本机指定的端口(必须可用)

测试用例,本机指定同样的IP地址和端口,但远程主机和IP不一样. 
在一个测试端打开一个终端窗口1,切换到 client3对应位置

./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4000

输出为

remote host is 192.168.190.230
remote port is 8000
local ip is 192.168.190.148
local port is 4000
>Chunks: 1 Bytes: 505

再打开一个测试端终端窗口2,执行:

./client3 -h 192.168.190.240 -p 9000 -c 192.168.190.148 -o 4000

窗口2程序,无法执行,自动退出。 
接着在窗口2终端继续输入:

./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4001

注意,和窗口1相比,仅仅改变了端口号为4001。但执行结果,和端口1输出一模一样,在等待接收数据,没有自动退出。

剩下的,无论怎么组合,怎么折腾,只要一对{本机IP,本机端口}被占用,也就意味着对应一个具体的文件句柄,那么其它程序将不能够再次使用。

Java怎么绑定本地IP地址?

Java绑定就很简单,但有些限制,不够灵活,单纯从源码中看不出来,api doc可以告诉我们一些事情。 打开JDKAPI1_6zhCN.CHM,查看InetSocketAddress类的构造函数说明:

public InetSocketAddress(InetAddress addr, int port) 
根据 IP 地址和端口号创建套接字地址。 有效端口值介于 0 和 65535 之间。端口号 zero 允许系统在 bind 操作中挑选暂时的端口。

null 地址将分配通配符 地址。

参数: 
addr - IP 地址 
port - 端口号 
抛出: 
IllegalArgumentException - 如果 port 参数超出有效端口值的指定范围。


public InetSocketAddress(String hostname, int port) 
根据主机名和端口号创建套接字地址。 
尝试将主机名解析为 InetAddress。如果尝试失败,则将地址标记为未解析。

如果存在安全管理器,则将主机名用作参数调用其 checkConnect 方法,以检查解析它的权限。这可能会导致 SecurityException 异常。

有效端口值介于 0 和 65535 之间。端口号 zero 允许系统在 bind 操作中挑选暂时的端口。

参数: hostname - 主机名 
port - 端口号 
抛出: 
IllegalArgumentException - 如果 port 参数超出有效端口值的范围,或者主机名参数为 null。 
SecurityException - 如果存在安全管理器,但拒绝解析主机名的权限。 
另请参见: 
isUnresolved()

InetSocketAddress的两个构造函数都支持,看情况使用。注意int port传递值为0,即可做到系统随机挑选端口。追踪一下源代码,发现最终调用

private native void socketBind(InetAddress address, int port) throws IOException;

如何查看socketBind的原始C代码,我就不清楚了,您若知晓,希望指教一下。 构造一个InetSocketAddress对象:

SocketAddress localSocketAddr = new InetSocketAddress("192.168.190.143", 0);

然后传递给需要位置即可。诸如使用netty连接到某个服务器上,在connect时指定远方地址,以及本机地址

ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) Attempts a new connection with the specified remoteAddress and the specified localAddress.

Netty 客户端连接API见: http://docs.jboss.org/netty/3.2/api/org/jboss/netty/bootstrap/ClientBootstrap.html

Linux支持绑定本机IP、端口原理

说是原理,有些牵强,因为linux C提供了如何绑定函数,框架或者高级语言再怎么封装,在linux平台下面,需要这么调用:

struct sockaddr_in clnt_addr;
....
clnt_addr.sin_family = AF_INET;
clnt_addr.sin_addr.s_addr = INADDR_ANY; //绑定本机IP地址
clnt_addr.sin_port = htons(33333); //绑定本机端口
if (bind(sockfd, (struct sockaddr *) &clnt_addr,
sizeof(clnt_addr)) < 0) error("ERROR on binding");
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting");
.......

构造一个clnt_addr结构体,本地IP或者端口赋值,在connect之前,先bind,就这么简单。

更完整例子,可以参考 http://stackoverflow.com/questions/4852256/need-a-complete-snippet-example-of-binding-tcp-client-socket

有关端口的更详细解释,请参考《UNIX网络编程卷1:套接字联网API(第3版)》2.9节 端口号部分。

高并发网络连接数因端口数量受限问题相关推荐

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

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

  2. 高并发网络编程之epoll详解

    在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序.在大数据.高并发.集群等一些名词唱得火热之年代,select和poll的 ...

  3. 高并发网络编程之epoll(个人遇到最好理解的一篇文章、易懂)

    LT 和 ET本质的区别是: LT模式状态时,主线程正在epoll_wait等待事件时,请求到了,epoll_wait返回后没有去处理请求(recv),那么下次epoll_wait时此请求还是会返回( ...

  4. 高并发网络编程之NIO非阻塞网络编程

    文章目录 NIO非阻塞网络编程 Buffer缓冲区 Buffer工作原理: ByteBuffer内存类型 Channel通道 SocketChannel ServerSocketChannel Sel ...

  5. mongodb线程池_常用高并发网络线程模型设计及MongoDB线程模型优化实践

    服务端通常需要支持高并发业务访问,如何设计优秀的服务端网络IO工作线程/进程模型对业务的高并发访问需求起着至关重要的核心作用. 本文总结了了不同场景下的多种网络IO线程/进程模型,并给出了各种模型的优 ...

  6. 高并发网络架构解决方案分析

    1:html静态化 2:图片服务器分离 3:数据库集群 4:缓存 5:负载均衡 大型高并发高负载网站的系统架构 我在Cernet做过拨号接入平台的搭建,而后在Yahoo3721负载搜索引擎前端平台开发 ...

  7. 高并发网络服务器设计

    小白模式 毕业入职第一家公司的入职作业就是写一个高并发的http服务器.当时的大致思路是这样: 主线程创建监听端口,将监听端口放入epoll监听列表,然后epoll开始循环监听,当到来的读请求是监听端 ...

  8. nodeJS express mysql 高并发时连接数不够用问题 以及如何处理高并发

    首先 描述下问题,前段时间接到了通知,做nodejs高并发代码优化,于是开始整咯,首先用loadrunning模拟高并发,问题就来了,到高并发路由的时候,会出现数据库连接数不够用的情况.查询了代码,都 ...

  9. libevent c++高并发网络编程_高并发编程学习(2)——线程通信详解

    前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...

最新文章

  1. swim 中一行代码解决收回键盘
  2. 是时候装逼了,试试 IDEA 解决 Maven 依赖冲突的高能神器!
  3. 拿下丰厚的年终奖,却未能拯救总薪酬,2021 年度 IT 薪酬调查报告出炉!
  4. python 信号量,Event, 定时器
  5. Jmeter操作mysql数据库测试
  6. centos一键清理磁盘空间_如何清理 Docker 占用的磁盘空间
  7. 【JavaScript】appendChild一个的注意点之会删除原dom树节点
  8. python中在同一个位置输出数据
  9. Linux下使用脚本安装和升级pip
  10. 关于CodeReview
  11. Maven安装和配置详细教程
  12. web压力测试的几个指标
  13. photoshopCS6软件的安装和破解方法
  14. 为什么拉格朗日对偶函数一定是凹函数(逐点下确界)
  15. 交换机级联后网速在底层交换机变慢的问题
  16. intellij idea实现代码实时翻译的插件开发
  17. 网络编程 —— 基础理论知识
  18. 上海迪士尼将推出虎年新春全新体验
  19. AS608指纹模块的上位机检测
  20. android studio实验二 Activity及常用布局和控件的使用

热门文章

  1. matlab 函数,matlab 语法1
  2. 计算机PPT中项目编号怎么弄,电脑技巧收藏家电脑基础设置幻灯片格式:项目符号和编号...
  3. SIP注册信令消息示范及解释
  4. java计算机毕业设计培训机构运营系统源码+程序+lw文档+mysql数据库
  5. 网络协议 -- ARP和RARP协议
  6. [W ParallelNative.cpp:212] Warning: Cannot set number of intraop threads after parallel work h
  7. linux 内核 修改mss,[转载]linux 内核对于TCPMSS的处理
  8. 图解Go语言内存分配 https://juejin.im/post/5c888a79e51d456ed11955a8
  9. 洛谷 P1460 健康的荷斯坦奶牛 Healthy Holsteins
  10. 操作系统-消费者生产者代码C++Windows实现