SO_REUSEPORT學習筆記補遺
前言
因為能力有限,還是有很多東西(SO_REUSEADDR和SO_REUSEPORT的區別等)沒有能夠在一篇文字中表達清楚,作為補遺,也方便以后自己回過頭來復習。
SO_REUSADDR VS SO_REUSEPORT
兩者不是一碼事,沒有可比性。有時也會被其搞暈,自己總結的不好,推薦StackOverflow的Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ?資料,總結的很全面。
簡單來說:
- 設置了SO_REUSADDR的應用可以避免TCP 的 TIME_WAIT 狀態 時間過長無法復用端口,尤其表現在應用程序關閉-重啟交替的瞬間
- SO_REUSEPORT更強大,隸屬于同一個用戶(防止端口劫持)的多個進程/線程共享一個端口,同時在內核層面替上層應用做數據包進程/線程的處理均衡
若有困惑,推薦兩者都設置,不會有沖突。
Netty多線程使用SO_REUSEPORT
上一篇講到SO_REUSEPORT,多個程綁定同一個端口,可以根據需要控制進程的數量。這里講講基于Netty 4.0.25+Epoll navtie transport
在單個進程內多個線程綁定同一個端口的情況,也是比較實用的。
TCP服務器,同一個進程多線程綁定同一個端口
這是一個PING-PONG示范應用:
public void run() throws Exception {
final EventLoopGroup bossGroup = new EpollEventLoopGroup();
final EventLoopGroup workerGroup = new EpollEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(EpollServerSocketChannel. class)
.childHandler( new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new StringDecoder(CharsetUtil.UTF_8 ),
new StringEncoder(CharsetUtil.UTF_8 ),
new PingPongServerHandler());
}
}).option(ChannelOption. SO_REUSEADDR, true)
.option(EpollChannelOption. SO_REUSEPORT, true)
.childOption(ChannelOption. SO_KEEPALIVE, true);
int workerThreads = Runtime.getRuntime().availableProcessors();
ChannelFuture future;
for ( int i = 0; i < workerThreads; ++i) {
future = b.bind( port).await();
if (!future.isSuccess())
throw new Exception(String. format("fail to bind on port = %d.",
port), future.cause());
}
Runtime. getRuntime().addShutdownHook (new Thread(){
@Override
public void run(){
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
});
}
打成jar包,在CentOS 7下面運行,檢查同一個端口所打開的文件句柄。
# lsof -i:8000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3515 root 42u IPv6 29040 0t0 TCP *:irdmi (LISTEN)
java 3515 root 43u IPv6 29087 0t0 TCP *:irdmi (LISTEN)
java 3515 root 44u IPv6 29088 0t0 TCP *:irdmi (LISTEN)
java 3515 root 45u IPv6 29089 0t0 TCP *:irdmi (LISTEN)
同一進程,但打開的文件句柄是不一樣的。
UDP服務器,多個線程綁同一個端口
/**
* UDP諺語服務器,單進程多線程綁定同一端口示范
*/
public final class QuoteOfTheMomentServer {
private static final int PORT = Integer.parseInt(System. getProperty("port" ,
"9000" ));
public static void main(String[] args) throws Exception {
final EventLoopGroup group = new EpollEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group).channel(EpollDatagramChannel. class)
.option(EpollChannelOption. SO_REUSEPORT, true )
.handler( new QuoteOfTheMomentServerHandler());
int workerThreads = Runtime.getRuntime().availableProcessors();
for (int i = 0; i < workerThreads; ++i) {
ChannelFuture future = b.bind( PORT).await();
if (!future.isSuccess())
throw new Exception(String.format ("Fail to bind on port = %d.",
PORT), future.cause());
}
Runtime. getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
group.shutdownGracefully();
}
});
}
}
}
@Sharable
class QuoteOfTheMomentServerHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
private static final String[] quotes = {
"Where there is love there is life." ,
"First they ignore you, then they laugh at you, then they fight you, then you win.",
"Be the change you want to see in the world." ,
"The weak can never forgive. Forgiveness is the attribute of the strong.", };
private static String nextQuote() {
int quoteId = ThreadLocalRandom.current().nextInt( quotes .length );
return quotes [quoteId];
}
@Override
public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {
if ("QOTM?" .equals(packet.content().toString(CharsetUtil. UTF_8))) {
ctx.write( new DatagramPacket(Unpooled.copiedBuffer( "QOTM: "
+ nextQuote(), CharsetUtil. UTF_8), packet.sender()));
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
}
}
同樣也要檢測一下端口文件句柄打開情況:
# lsof -i:9000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3181 root 26u IPv6 27188 0t0 UDP *:cslistener
java 3181 root 27u IPv6 27217 0t0 UDP *:cslistener
java 3181 root 28u IPv6 27218 0t0 UDP *:cslistener
java 3181 root 29u IPv6 27219 0t0 UDP *:cslistener
小結
以上為Netty+SO_REUSEPORT多線程綁定同一端口的一些情況,是為記載。
posted on 2015-02-25 22:23 nieyong 閱讀(6701) 評論(1) 編輯 收藏 所屬分類: Socket