Commit b564375d authored by wuyuhang's avatar wuyuhang

Initial commit

parents
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 依赖于环境的 Maven 主目录路径
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="tcp-receiver" />
</profile>
</annotationProcessing>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="esv-snapshots" />
<option name="name" value="esv-snapshots" />
<option name="url" value="http://36.129.47.17:18084/repository/esv-snapshots/" />
</remote-repository>
<remote-repository>
<option name="id" value="esv-releases" />
<option name="name" value="esv-releases" />
<option name="url" value="http://36.129.47.17:18084/repository/maven-releases/" />
</remote-repository>
<remote-repository>
<option name="id" value="cloudera.repo" />
<option name="name" value="cloudera.repo" />
<option name="url" value="https://repository.cloudera.com/artifactory/cloudera-repos/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://maven.aliyun.com/repository/central/" />
</remote-repository>
<remote-repository>
<option name="id" value="esv-repository" />
<option name="name" value="esv-repository" />
<option name="url" value="http://36.129.47.17:18084/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="aliyun-public" />
<option name="name" value="aliyun-public" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="blade-proxy" />
<option name="name" value="blade-proxy" />
<option name="url" value="http://36.129.47.17:18084/repository/blade-proxy/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>tcp-receiver</artifactId>
<version>1.0.0</version>
<build>
<finalName>tcp-receiver</finalName>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>com.example.tcpreceiver.TcpServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<snakeyaml.version>1.33</snakeyaml.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<netty.version>4.1.110.Final</netty.version>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>tcp-receiver</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netty.version>4.1.110.Final</netty.version>
<snakeyaml.version>1.33</snakeyaml.version>
<!-- 实际部署到Linux服务器时建议显式指定classifier,比如 linux-x86_64,
以确保拉到epoll native包,见下方 netty-transport-native-epoll 依赖说明 -->
</properties>
<dependencies>
<!-- Netty 核心 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- Linux epoll 原生传输,比NIO Selector性能更好,吞吐量大场景必备 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<classifier>linux-x86_64</classifier>
</dependency>
<!-- YAML配置解析 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- 简单日志实现,方便看控制台输出,生产环境可换成logback/log4j2 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
<build>
<finalName>tcp-receiver</finalName>
<plugins>
<!-- 打包成可直接运行的fat jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.tcpreceiver.TcpServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
import com.example.tcpsend.NettyTcpPushServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* TCP压测发送端。
*
* 用法:
* javac TcpSender.java
* java TcpSender <host> <port> <连接数> <持续秒数> [单次写入buffer大小KB,默认64]
*
* 示例:
* java TcpSender 192.168.1.100 9000 8 60 256
* (8个并发连接,往192.168.1.100:9000发60秒,每次write 256KB)
*
* 设计说明:
* - 发送内容随便填充(全0即可),测试目标是吞吐量不是内容本身
* - 每个连接一个线程,死循环write,不等待也不读取响应(纯压测接收端的读取能力)
* - 用一个共享的AtomicLong累加所有线程发出的字节数,主线程每秒打印一次瞬时速率和累计速率
* - 多连接是为了模拟"多个客户端同时发数据"的场景,单连接很难打满万兆网卡(受限于单TCP流和单核处理能力)
*/
public class TcpSender {
private static final AtomicLong totalBytesSent = new AtomicLong(0);
private static final AtomicBoolean running = new AtomicBoolean(true);
public static void main(String[] args) throws InterruptedException {
if (args.length < 4) {
System.out.println("用法: java TcpSender <host> <port> <连接数> <持续秒数> [单次写入buffer大小KB,默认64]");
System.out.println("示例: java TcpSender 192.168.1.100 9000 8 60 256");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
int connections = Integer.parseInt(args[2]);
int durationSeconds = Integer.parseInt(args[3]);
int bufferSizeKB = args.length >= 5 ? Integer.parseInt(args[4]) : 64;
System.out.println("[配置] host=" + host + ", port=" + port
+ ", 连接数=" + connections + ", 持续秒数=" + durationSeconds
+ ", 单次写入大小=" + bufferSizeKB + "KB");
Thread[] senderThreads = new Thread[connections];
for (int i = 0; i < connections; i++) {
int connIndex = i;
senderThreads[i] = new Thread(() -> senderWorker(host, port, bufferSizeKB, connIndex), "sender-" + i);
senderThreads[i].start();
}
// 统计打印线程:每秒打印一次瞬时速率和累计速率
Thread reporter = new Thread(() -> {
long lastBytes = 0;
long startTime = System.nanoTime();
while (running.get()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
long currentBytes = totalBytesSent.get();
long deltaBytes = currentBytes - lastBytes;
lastBytes = currentBytes;
double elapsedSeconds = (System.nanoTime() - startTime) / 1_000_000_000.0;
double instantMBps = deltaBytes / 1024.0 / 1024.0;
double instantGbps = (deltaBytes * 8) / 1_000_000_000.0;
double avgMBps = (currentBytes / 1024.0 / 1024.0) / elapsedSeconds;
System.out.println(String.format(
"[t=%.0fs] 瞬时速率=%.2f MB/s (%.2f Gbps), 累计=%.2f MB, 平均速率=%.2f MB/s",
elapsedSeconds, instantMBps, instantGbps,
currentBytes / 1024.0 / 1024.0, avgMBps
));
}
}, "reporter");
reporter.setDaemon(true);
reporter.start();
// 主线程负责控制总时长,时间到了把running置false,通知所有发送线程退出
Thread.sleep(durationSeconds * 1000L);
running.set(false);
for (Thread t : senderThreads) {
t.interrupt();
t.join(2000);
}
long totalBytes = totalBytesSent.get();
System.out.println(String.format(
"[测试结束] 总耗时=%ds, 总发送字节数=%d (%.2f MB / %.2f GB), 平均速率=%.2f MB/s (%.2f Gbps)",
durationSeconds, totalBytes,
totalBytes / 1024.0 / 1024.0, totalBytes / 1024.0 / 1024.0 / 1024.0,
(totalBytes / 1024.0 / 1024.0) / durationSeconds,
(totalBytes * 8.0 / 1_000_000_000.0) / durationSeconds
));
}
/**
* 单个连接的发送逻辑:建立连接后死循环write固定内容的buffer,直到收到停止信号。
*/
private static void senderWorker(String host, int port, int bufferSizeKB, int connIndex) {
byte[] buffer = new byte[bufferSizeKB * 1024];
// 内容随便填,这里填个固定字节模式,方便和"全0"区分,便于抓包时肉眼确认
for (int i = 0; i < buffer.length; i++) {
buffer[i] = (byte) (i % 256);
}
try (Socket socket = new Socket()) {
// 适当调大发送缓冲区,配合接收端的SO_RCVBUF,减少写阻塞
socket.setSendBufferSize(4 * 1024 * 1024);
socket.setTcpNoDelay(true);
socket.connect(new InetSocketAddress(host, port), 5000);
System.out.println("[连接成功] sender-" + connIndex + " -> " + host + ":" + port);
OutputStream out = socket.getOutputStream();
while (running.get() && !Thread.currentThread().isInterrupted()) {
out.write(buffer);
totalBytesSent.addAndGet(buffer.length);
}
} catch (Exception e) {
System.out.println("[连接异常] sender-" + connIndex + ": " + e.getMessage());
}
}
}
\ No newline at end of file
package com.example.tcpreceiver;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.net.InetSocketAddress;
/**
* 核心连接处理器。
*
* 设计要点:
* - 继承 SimpleChannelInboundHandler<ByteBuf>,框架会在每次channelRead0返回后自动release(msg),
* 不需要手动管理引用计数,避免内存泄漏。
* - 只做计数,不拷贝、不持有、不写文件,意味着这里的开销极小,瓶颈应该落在网络IO本身。
* - 每个连接独享一个Handler实例(Netty默认每条连接new一个pipeline),所以这里的字段不需要加锁。
*/
public class ConnectionHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final GlobalStats globalStats;
private long bytesReceived = 0L;
private long connectionStartNanos;
private String remoteIp;
public ConnectionHandler(GlobalStats globalStats) {
// 关闭自动release,因为SimpleChannelInboundHandler默认就会release,这里显式调用父类无参构造即可
super();
this.globalStats = globalStats;
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
connectionStartNanos = System.nanoTime();
InetSocketAddress remote = (InetSocketAddress) ctx.channel().remoteAddress();
remoteIp = remote.getAddress().getHostAddress();
System.out.println("[连接建立] " + remoteIp + ":" + remote.getPort());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
// 只接收不存储不处理:仅累加字节数,内容直接丢弃。
// msg会在本方法返回后由父类自动release,不需要也不应该手动retain/缓存。
bytesReceived += msg.readableBytes();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
long durationNanos = System.nanoTime() - connectionStartNanos;
double durationSeconds = durationNanos / 1_000_000_000.0;
double mbReceived = bytesReceived / 1024.0 / 1024.0;
double throughputMBps = durationSeconds > 0 ? mbReceived / durationSeconds : 0;
System.out.println(String.format(
"[连接结束] %s, 耗时=%.3fs, 接收字节数=%d (%.2f MB), 平均速率=%.2f MB/s",
remoteIp, durationSeconds, bytesReceived, mbReceived, throughputMBps
));
globalStats.recordConnectionFinished(remoteIp, bytesReceived);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.println("[连接异常] " + remoteIp + ": " + cause.getMessage());
ctx.close();
}
}
package com.example.tcpreceiver;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 全局统计:
* 1. 按客户端IP维度累加历史总字节数、总连接数(跨多次连接持续累加)
* 2. 维护全局总字节数/总连接数,供定期打印汇总使用
*/
public class GlobalStats {
public static class IpStat {
public final AtomicLong totalBytes = new AtomicLong(0);
public final AtomicLong connectionCount = new AtomicLong(0);
}
private final Map<String, IpStat> ipStatsMap = new ConcurrentHashMap<>();
private final AtomicLong globalTotalBytes = new AtomicLong(0);
private final AtomicLong globalConnectionCount = new AtomicLong(0);
/**
* 一次连接结束时调用,累加该IP的统计和全局统计。
*/
public void recordConnectionFinished(String ip, long bytesReceived) {
IpStat stat = ipStatsMap.computeIfAbsent(ip, k -> new IpStat());
stat.totalBytes.addAndGet(bytesReceived);
stat.connectionCount.incrementAndGet();
globalTotalBytes.addAndGet(bytesReceived);
globalConnectionCount.incrementAndGet();
}
public long getGlobalTotalBytes() {
return globalTotalBytes.get();
}
public long getGlobalConnectionCount() {
return globalConnectionCount.get();
}
public Map<String, IpStat> getIpStatsMap() {
return ipStatsMap;
}
}
package com.example.tcpreceiver;
import org.yaml.snakeyaml.Yaml;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
/**
* 配置加载类。
* 优先从外部文件路径加载(部署时放在jar包同目录或任意指定路径),
* 找不到外部文件时,回退到classpath内置的默认配置(resources/config.yaml),
* 保证服务在没有外部配置时也能用默认参数跑起来。
*/
public class ServerConfig {
public int port = 24584;
public boolean useEpoll = true;
public int bossThreads = 1;
public int workerThreads = 0;
public int soBacklog = 1024;
public boolean tcpNoDelay = true;
public boolean soKeepAlive = true;
public int soRcvbuf = 4 * 1024 * 1024;
public int soSndbuf = 64 * 1024;
public boolean usePooledAllocator = true;
public int recvBufferAllocatorMax = 1024 * 1024;
public int recvBufferAllocatorMin = 64 * 1024;
public int recvBufferAllocatorInitial = 256 * 1024;
public boolean enablePerIpStats = true;
public int globalStatsIntervalSeconds = 30;
public int rate;
public int dataLength;
public int dataTime;
/**
* 加载配置。
*
* @param externalPath 外部配置文件路径,可为null。
* 为null或文件不存在时,使用classpath下的config.yaml作为默认值。
*/
@SuppressWarnings("unchecked")
public static ServerConfig load(String externalPath) {
ServerConfig cfg = new ServerConfig();
Map<String, Object> root = null;
try {
if (externalPath != null && Files.exists(Paths.get(externalPath))) {
try (InputStream in = new FileInputStream(externalPath)) {
root = new Yaml().load(in);
System.out.println("[Config] 使用外部配置文件: " + externalPath);
}
} else {
InputStream in = ServerConfig.class.getClassLoader().getResourceAsStream("config.yaml");
if (in != null) {
root = new Yaml().load(in);
in.close();
System.out.println("[Config] 外部配置未指定或不存在,使用内置默认 config.yaml");
} else {
System.out.println("[Config] 未找到任何配置文件,使用代码内硬编码默认值");
}
}
} catch (IOException e) {
System.out.println("[Config] 读取配置文件失败,使用默认值: " + e.getMessage());
}
if (root == null) {
return cfg;
}
Map<String, Object> server = (Map<String, Object>) root.get("server");
if (server != null) {
cfg.port = getInt(server, "port", cfg.port);
cfg.useEpoll = getBool(server, "useEpoll", cfg.useEpoll);
cfg.bossThreads = getInt(server, "bossThreads", cfg.bossThreads);
cfg.workerThreads = getInt(server, "workerThreads", cfg.workerThreads);
cfg.soBacklog = getInt(server, "soBacklog", cfg.soBacklog);
cfg.tcpNoDelay = getBool(server, "tcpNoDelay", cfg.tcpNoDelay);
cfg.soKeepAlive = getBool(server, "soKeepAlive", cfg.soKeepAlive);
cfg.soRcvbuf = getInt(server, "soRcvbuf", cfg.soRcvbuf);
cfg.soSndbuf = getInt(server, "soSndbuf", cfg.soSndbuf);
cfg.usePooledAllocator = getBool(server, "usePooledAllocator", cfg.usePooledAllocator);
cfg.recvBufferAllocatorMax = getInt(server, "recvBufferAllocatorMax", cfg.recvBufferAllocatorMax);
cfg.recvBufferAllocatorMin = getInt(server, "recvBufferAllocatorMin", cfg.recvBufferAllocatorMin);
cfg.recvBufferAllocatorInitial = getInt(server, "recvBufferAllocatorInitial", cfg.recvBufferAllocatorInitial);
}
Map<String, Object> sendServer = (Map<String, Object>) root.get("sendServer");
if (sendServer != null) {
cfg.rate = getInt(sendServer, "rate", cfg.rate);
cfg.dataLength = getInt(sendServer, "dataLength", cfg.dataLength);
cfg.dataTime = getInt(sendServer, "dataTime", cfg.dataTime);
}
Map<String, Object> stats = (Map<String, Object>) root.get("stats");
if (stats != null) {
cfg.enablePerIpStats = getBool(stats, "enablePerIpStats", cfg.enablePerIpStats);
cfg.globalStatsIntervalSeconds = getInt(stats, "globalStatsIntervalSeconds", cfg.globalStatsIntervalSeconds);
}
return cfg;
}
private static int getInt(Map<String, Object> map, String key, int def) {
Object v = map.get(key);
if (v == null) return def;
return ((Number) v).intValue();
}
private static boolean getBool(Map<String, Object> map, String key, boolean def) {
Object v = map.get(key);
if (v == null) return def;
return (Boolean) v;
}
@Override
public String toString() {
return "ServerConfig{" +
"port=" + port +
", useEpoll=" + useEpoll +
", bossThreads=" + bossThreads +
", workerThreads=" + workerThreads +
", soBacklog=" + soBacklog +
", tcpNoDelay=" + tcpNoDelay +
", soKeepAlive=" + soKeepAlive +
", soRcvbuf=" + soRcvbuf +
", soSndbuf=" + soSndbuf +
", usePooledAllocator=" + usePooledAllocator +
", recvBufferAllocatorMax=" + recvBufferAllocatorMax +
", recvBufferAllocatorMin=" + recvBufferAllocatorMin +
", recvBufferAllocatorInitial=" + recvBufferAllocatorInitial +
", enablePerIpStats=" + enablePerIpStats +
", globalStatsIntervalSeconds=" + globalStatsIntervalSeconds +
'}';
}
}
package com.example.tcpreceiver;
import com.example.tcpsend.NettyTcpPushServer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* TCP接收服务启动类。
*
* 启动方式:
* java -jar tcp-receiver.jar [外部配置文件路径]
*
* 不传参数时,使用jar内置的默认 config.yaml。
* 生产部署时建议把config.yaml拷贝到jar同目录,按服务器配置调整后通过参数传入路径,
* 这样换服务器或调参数都不需要重新打包。
*/
public class TcpServer {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
NettyTcpPushServer.main(args);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
String externalConfigPath = args.length > 0 ? args[0] : null;
ServerConfig cfg = ServerConfig.load(externalConfigPath);
System.out.println("[启动] 加载到的配置: " + cfg);
boolean useEpoll = cfg.useEpoll && Epoll.isAvailable();
if (cfg.useEpoll && !Epoll.isAvailable()) {
System.out.println("[启动] 配置要求使用epoll,但当前环境不支持,自动降级为NIO");
}
EventLoopGroup bossGroup;
EventLoopGroup workerGroup;
Class<? extends ServerChannel> channelClass;
if (useEpoll) {
bossGroup = new EpollEventLoopGroup(cfg.bossThreads);
workerGroup = cfg.workerThreads > 0
? new EpollEventLoopGroup(cfg.workerThreads)
: new EpollEventLoopGroup();
channelClass = EpollServerSocketChannel.class;
System.out.println("[启动] 使用 Epoll 传输");
} else {
bossGroup = new NioEventLoopGroup(cfg.bossThreads);
workerGroup = cfg.workerThreads > 0
? new NioEventLoopGroup(cfg.workerThreads)
: new NioEventLoopGroup();
channelClass = NioServerSocketChannel.class;
System.out.println("[启动] 使用 NIO 传输");
}
GlobalStats globalStats = new GlobalStats();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(channelClass)
.option(ChannelOption.SO_BACKLOG, cfg.soBacklog)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
// 每条连接独立的Handler实例,内部计数字段互不影响
ch.pipeline().addLast(new ConnectionHandler(globalStats));
}
});
// ===== 子连接(实际数据通道)参数 =====
bootstrap.childOption(ChannelOption.TCP_NODELAY, cfg.tcpNoDelay);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, cfg.soKeepAlive);
if (cfg.soRcvbuf > 0) {
bootstrap.childOption(ChannelOption.SO_RCVBUF, cfg.soRcvbuf);
}
if (cfg.soSndbuf > 0) {
bootstrap.childOption(ChannelOption.SO_SNDBUF, cfg.soSndbuf);
}
// 内存分配器:池化直接内存可以显著降低高吞吐场景下的GC压力和内存拷贝开销
if (cfg.usePooledAllocator) {
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
} else {
bootstrap.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
}
// 自适应接收缓冲区:根据实际数据量在min~max之间动态调整单次读取大小,
// 大流量场景适当调大可以减少系统调用次数
bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,
new AdaptiveRecvByteBufAllocator(
cfg.recvBufferAllocatorMin,
cfg.recvBufferAllocatorInitial,
cfg.recvBufferAllocatorMax
));
Channel serverChannel = bootstrap.bind(cfg.port).sync().channel();
System.out.println("[启动] 服务已启动,监听端口: " + cfg.port);
serverChannel.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
This diff is collapsed.
# ====================================
# TCP接收服务 配置文件
# 部署到不同配置的服务器时,主要调整这里
# ====================================
server:
# 监听端口
port: 24584
sendRate: 800
# 是否优先使用Linux epoll原生传输(强烈建议Linux服务器开启,性能优于NIO)
# 如果不是Linux环境或没有对应native包,自动降级为NIO
useEpoll: true
# Boss线程数:负责接收新连接,一般1-2个足够,不需要很多
bossThreads: 1
# Worker线程数:负责实际IO读写,决定了并发处理能力
# 建议设置为 CPU核心数 或 CPU核心数*2,根据实际压测调整
# 0表示使用Netty默认值(CPU核数*2)
workerThreads: 0
# ===== TCP相关参数 =====
# 服务端accept队列大小,高并发连接场景适当调大,避免握手被丢弃
soBacklog: 1024
# 是否禁用Nagle算法。只接收数据场景通常建议true,降低延迟
tcpNoDelay: true
# 是否开启TCP keepalive,检测死连接
soKeepAlive: true
# 接收缓冲区大小(字节)。万兆网大流量场景建议调大,比如 1MB ~ 4MB
# 0表示不显式设置,使用系统默认值
soRcvbuf: 4194304
# 发送缓冲区大小(字节),本服务只接收基本不发送,可以设小一点或者不设
soSndbuf: 65536
# ===== Netty内部缓冲区/内存相关 =====
# 是否使用池化的直接内存分配器(推荐true,减少GC压力和内存拷贝)
usePooledAllocator: true
# 每次读取的最大缓冲区大小提示(字节),配合自适应ByteBuf分配器
# 大流量场景可以适当调大,减少读取系统调用次数
recvBufferAllocatorMax: 1048576
recvBufferAllocatorMin: 65536
recvBufferAllocatorInitial: 262144
sendServer:
rate: 30
dataLength: 120000
dataTime: 30
# 统计相关
stats:
# 是否按客户端IP维度汇总统计(多次连接累加)
enablePerIpStats: true
# 是否定期打印全局汇总统计(所有IP总和),单位:秒。0表示关闭定期打印
globalStatsIntervalSeconds: 30
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment