踏莎行·术 - NIO系列2:TCP监听绑定

本文介绍使用Java NIO实现TCP监听绑定的过程。通过异步事件模型(Reactor模式),文章详细阐述了如何绑定多个本地地址(端口),并探讨了同步与异步注册的区别及其带来的线程间通信问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:本文适合对象需对java NIO API的使用及异步事件模型(Reactor模式)有一定程度的了解,主要讲述使用java原生NIO实现一个TCP监听绑定的过程及细节设计。


我们一开始设计了一个TCP接入服务类,这个类提供了一个API方法提供对本地一系列地址(端口)的监听绑定,类初始化后完成Selector的open操作如下:

selector = Selector.open();

提供的绑定API,其方法签名如下:

 /**
	 * Binds to the specified local addresses and start to accept incoming connections. If any address binding failed then
	 * rollback the already binding addresses. Bind is fail fast, if encounter the first bind exception then throw it immediately.
	 * 
	 * @param firstLocalAddress
	 * @param otherLocalAddresses
	 * @throws throw if bind failed.
	 */
	synchronized public void bind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) throws IOException;

为何需要同步?因为我们不希望多个线程同时调用该方法,导致地址绑定异常。

参数中可以传递多个本地地址(端口)同时进行监听绑定。

在NIO的绑定过程中需进行事件注册(对OP_ACCEPT感兴趣),如下:

  ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		ServerSocket ss = ssc.socket();
		ss.setReuseAddress(config.isReuseAddress());
		ss.bind(address, config.getBacklog());
		ssc.register(selector, SelectionKey.OP_ACCEPT);


由于注册过程中除了涉及锁竞争还可能产生死锁,所以一般的做法都是将绑定地址放在队列中进行异步注册由reactor线程进行处理,例如:

  bindAddresses.addAll(localAddresses);
		if (!bindAddresses.isEmpty()) {
			synchronized (lock) {
				// wake up for unblocking the select() to process binding addresses
				selector.wakeup();

				// wait for bind result
				wait0();
			}
		}

从同步注册变为异步注册后就存在一个问题,实际注册绑定时可能存在端口已绑定的异常,在异步情况下就需要线程间通信来通知异常消息,并向调用方反馈。

如上面代码片段中的wait0()方法就是等待绑定结果,若出现绑定异常则抛出

  private void wait0() throws IOException {
		while (!this.endFlag) {
			try {
				lock.wait();
			} catch (InterruptedException e) {
				throw new IOException(e);
			}
		}

		// reset end flag
		this.endFlag = false;

		if (this.exception != null) {
			IOException e = exception;
			this.exception = null;
			throw e;
		}
	}

以上代码也说明了,NIO异步模型转化为同步API导致的模型阻抗付出了额外的代价和开销 --- 线程间通信。

至此,完成了TCP服务监听过程,下文将进一步讲述服务接入和数据传输相关设计细节。



查看zookeeper的日志,发现[main:DatadirCleanupHanagere⁷9]-autopurge.purgeInterual set to B2825-83-2722:45:86,856 [myid:21-INFO [main:DatadirCleanupranagerP181]-Purge task is not scheduled.2825-83-2722:45:86,864 [myid:2]-INFO [main:QuorumPeerMaine127]-Starting quorum peer2825-83-2722:45:86,871 [mjid:21-INFO [main:NIOServerCnxnFactorye⁹4]-binding to port B.8.8.8/88.0.0:21812825-83-2722:45:86,872 [myid:21-ERROR [main:QuorumPerHaine891-Unexpected exception,exiting abnormallyjava.net.BindException:Address already in useat sun.nio.ch.Net.binde(Native Method)at sun.nio.ch.Net.bind(Met.java:463)at sun.nio.ch.Net.bind(Net.java:455)at sun.nio.ch.ServerSocketChanmelImpl.bind(ServerSocketChammelImpl.java:223)at sun.nio.ch.SeruerSocketAdaptor.bind(SeruerSocketAdaptor.jaua:74)at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:67)at org.apache.zookeeper.seruer.NIOServerCnxnFactory.conf igure(NIOServerCnxnFactory.java:95)at org.apache.zookeeper.server.quorum.QuorumPeerHain.runFromConf ig(QuorumPeerMain.jaua:138)at org.apache.zookeeper.seruer.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.jaua:111)at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:78)2825-83-2722:45:22,312 [myid:]-INFO [main:FourLetterwordMainD43]-conmecting to localhost 21812825-83-2722:45:22,334 [myid:21-INFO [NIOServerCxn.Factory:8.8.8.8/8.8.8.8:2181:MIOServerCnxnFactoryD197]-Accepted socket connection from/127.8.8.1:441842825-83-2722:45:22,335 [myid:2]-INFO [NIOServerCxn.Factory:0.8.8.8/8.8.8.0:2181:NIOServerCnxnPB27]-Processing srur command from/127.8.8.1:441842025-83-2722:45:22,336 [myid:2]-INFO [Thread-7:NIOServerCnxne1887]-Closed socket comnection forclient /127.8.8.1:44184(no session established for client)
03-28
java.net.ConnectException: Failed to connect to localhost/[0:0:0:0:0:0:0:1]:4317 at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:297) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:207) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) ~[okhttp-4.12.0.jar:na] at io.opentelemetry.exporter.sender.okhttp.internal.RetryInterceptor.intercept(RetryInterceptor.java:96) ~[opentelemetry-exporter-sender-okhttp-1.51.0.jar:1.51.0] at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:517) ~[okhttp-4.12.0.jar:na] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na] at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na] Suppressed: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:4317 ... 21 common frames omitted Caused by: java.net.ConnectException: Connection refused: no further information at java.base/sun.nio.ch.Net.pollConnect(Native Method) at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672) at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:547) at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602) at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) at java.base/java.net.Socket.connect(Socket.java:633) at okhttp3.internal.platform.Platform.connectSocket(Platform.kt:128) at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:295) ... 20 common frames omitted Caused by: java.net.ConnectException: Connection refused: no further information at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na] at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672) ~[na:na] at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:547) ~[na:na] at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:602) ~[na:na] at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[na:na] at java.base/java.net.Socket.connect(Socket.java:633) ~[na:na] at okhttp3.internal.platform.Platform.connectSocket(Platform.kt:128) ~[okhttp-4.12.0.jar:na] at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:295) ~[okhttp-4.12.0.jar:na] ... 20 common frames omitted 2025-07-06T23:37:09.395+08:00 ERROR 12372 --- [otel-demo] [alhost:4317/...] i.o.exporter.internal.grpc.GrpcExporter : Failed to export logs. The request could not be executed. Error message: Failed to connect to localhost/[0:0:0:0:0:0:0:1]:4317
最新发布
07-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值