本示例首选介绍Java原生API实现BIO通信,然后进阶实现NIO通信,最后利用Netty实现NIO通信及Netty主要模块组件介绍。

Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

BIO(Blocking I/O) 方案

BIO通信(一请求一应答)模型图如下

采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,一旦接收到一个连接请求,就可以在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待当前连接的客户端的操作执行完成, 如果要让 BIO 通信模型 能够同时处理多个客户端请求,就必须使用多线程(主要原因是socket.accept()、socket.read()、socket.write() 涉及的三个主要函数都是同步阻塞的)

代码实现

BIO服务端

BIOServer.java

  1. package com.easy.javaBio;
  2. import lombok.SneakyThrows;
  3. import lombok.extern.slf4j.Slf4j;
  4. import java.io.BufferedReader;
  5. import java.io.BufferedWriter;
  6. import java.io.IOException;
  7. import java.io.InputStreamReader;
  8. import java.io.OutputStreamWriter;
  9. import java.net.ServerSocket;
  10. import java.net.Socket;
  11. @Slf4j
  12. public class BIOServer {
  13. public static void main(String[] args) throws IOException {
  14. ServerSocket server = new ServerSocket(10002);
  15. while (true) {
  16. Socket client = server.accept(); //等待客户端的连接,如果没有获取连接 ,在此步一直等待
  17. new Thread(new ServerThread(client)).start(); //为每个客户端连接开启一个线程
  18. }
  19. //server.close();
  20. }
  21. }
  22. @Slf4j
  23. class ServerThread extends Thread {
  24. private Socket client;
  25. public ServerThread(Socket client) {
  26. this.client = client;
  27. }
  28. @SneakyThrows
  29. @Override
  30. public void run() {
  31. log.info("客户端:" + client.getInetAddress().getLocalHost() + "已连接到服务器");
  32. BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
  33. //读取客户端发送来的消息
  34. String mess = br.readLine();
  35. log.info("客户端:" + mess);
  36. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
  37. bw.write(mess + "\n");
  38. bw.flush();
  39. }
  40. }

BIO客户端

BIOClient.java

  1. package com.easy.javaBio;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.io.*;
  4. import java.net.Socket;
  5. @Slf4j
  6. public class BIOClient {
  7. public static void main(String[] args) throws IOException {
  8. Socket s = new Socket("0.0.0.0", 10002);
  9. InputStream input = s.getInputStream();
  10. OutputStream output = s.getOutputStream();
  11. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output));
  12. bw.write("客户端给服务端发消息测试\n"); //向服务器端发送一条消息
  13. bw.flush();
  14. BufferedReader br = new BufferedReader(new InputStreamReader(input)); //读取服务器返回的消息
  15. String mess = br.readLine();
  16. log.info("服务器:" + mess);
  17. }
  18. }

运行示例

运行BIO服务端,然后再运行BIO客户端,观察控制台

BIOServer控制台输出:

  1. Connected to the target VM, address: '127.0.0.1:64346', transport: 'socket'
  2. 17:29:52.519 [Thread-1] INFO com.easy.javaBio.ServerThread - 客户端:YHE6OR5UXQJ6D35/192.168.9.110已连接到服务器
  3. 17:29:52.523 [Thread-1] INFO com.easy.javaBio.ServerThread - 客户端:客户端给服务端发消息测试

BIOClient控制台输出:

  1. Connected to the target VM, address: '127.0.0.1:64355', transport: 'socket'
  2. 17:29:52.527 [main] INFO com.easy.javaBio.BIOClient - 服务器:客户端给服务端发消息测试
  3. Disconnected from the target VM, address: '127.0.0.1:64355', transport: 'socket'

这表示我们实现了一个最简单的BIO通信了

这种方式为每个客户端开启一个线程,高并发时消耗资源较多,容易浪费,甚至导致服务端崩溃,对性能造成负面影响,高并发下不推荐使用。

NIO(New I/O)方案

NIO通信模型图如下

NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。

NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

NIO服务端

NIOServer.java

  1. package com.easy.javaBio;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.io.IOException;
  4. import java.net.InetAddress;
  5. import java.net.InetSocketAddress;
  6. import java.net.Socket;
  7. import java.net.SocketAddress;
  8. import java.nio.ByteBuffer;
  9. import java.nio.channels.SelectionKey;
  10. import java.nio.channels.Selector;
  11. import java.nio.channels.ServerSocketChannel;
  12. import java.nio.channels.SocketChannel;
  13. import java.util.*;
  14. @Slf4j
  15. public class NIOServer {
  16. private InetAddress addr;
  17. private int port;
  18. private Selector selector;
  19. private static int BUFF_SIZE = 1024;
  20. public NIOServer(InetAddress addr, int port) throws IOException {
  21. this.addr = addr;
  22. this.port = port;
  23. startServer();
  24. }
  25. private void startServer() throws IOException {
  26. // 获得selector及通道(socketChannel)
  27. this.selector = Selector.open();
  28. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  29. serverChannel.configureBlocking(false);
  30. // 绑定地址及端口
  31. InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
  32. serverChannel.socket().bind(listenAddr);
  33. serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
  34. log.info("NIOServer运行中...按下Ctrl-C停止服务");
  35. while (true) {
  36. log.info("服务器等待新的连接和selector选择…");
  37. this.selector.select();
  38. // 选择key工作
  39. Iterator keys = this.selector.selectedKeys().iterator();
  40. while (keys.hasNext()) {
  41. SelectionKey key = (SelectionKey) keys.next();
  42. // 防止出现重复的key,处理完需及时移除
  43. keys.remove();
  44. //无效直接跳过
  45. if (!key.isValid()) {
  46. continue;
  47. }
  48. if (key.isAcceptable()) {
  49. this.accept(key);
  50. } else if (key.isReadable()) {
  51. this.read(key);
  52. } else if (key.isWritable()) {
  53. this.write(key);
  54. } else if (key.isConnectable()) {
  55. this.connect(key);
  56. }
  57. }
  58. }
  59. }
  60. private void connect(SelectionKey key) throws IOException {
  61. SocketChannel channel = (SocketChannel) key.channel();
  62. if (channel.finishConnect()) {
  63. // 成功
  64. log.info("成功连接了");
  65. } else {
  66. // 失败
  67. log.info("失败连接");
  68. }
  69. }
  70. private void accept(SelectionKey key) throws IOException {
  71. ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
  72. SocketChannel channel = serverChannel.accept();
  73. channel.configureBlocking(false);
  74. channel.register(this.selector, SelectionKey.OP_READ);
  75. Socket socket = channel.socket();
  76. SocketAddress remoteAddr = socket.getRemoteSocketAddress();
  77. log.info("连接到: " + remoteAddr);
  78. }
  79. private void read(SelectionKey key) throws IOException {
  80. SocketChannel channel = (SocketChannel) key.channel();
  81. ByteBuffer buffer = ByteBuffer.allocate(BUFF_SIZE);
  82. int numRead = channel.read(buffer);
  83. if (numRead == -1) {
  84. log.info("关闭客户端连接: " + channel.socket().getRemoteSocketAddress());
  85. channel.close();
  86. return;
  87. }
  88. String msg = new String(buffer.array()).trim();
  89. log.info("得到了: " + msg);
  90. // 回复客户端
  91. String reMsg = msg + " 你好,这是BIOServer给你的回复消息:" + System.currentTimeMillis();
  92. channel.write(ByteBuffer.wrap(reMsg.getBytes()));
  93. }
  94. private void write(SelectionKey key) throws IOException {
  95. ByteBuffer byteBuffer = ByteBuffer.allocate(BUFF_SIZE);
  96. byteBuffer.flip();
  97. SocketChannel clientChannel = (SocketChannel) key.channel();
  98. while (byteBuffer.hasRemaining()) {
  99. clientChannel.write(byteBuffer);
  100. }
  101. byteBuffer.compact();
  102. }
  103. public static void main(String[] args) throws IOException {
  104. new NIOServer(null, 10002);
  105. }
  106. }

使用NIO, 可以用Selector最终决定哪一组注册的socket准备执行I/O

NIO客户端

NIOClient.java

  1. package com.easy.javaBio;
  2. import lombok.extern.slf4j.Slf4j;
  3. import java.io.IOException;
  4. import java.net.InetSocketAddress;
  5. import java.nio.ByteBuffer;
  6. import java.nio.channels.SocketChannel;
  7. import java.util.ArrayList;
  8. @Slf4j
  9. public class NIOClient {
  10. private static int BUFF_SIZE = 1024;
  11. public static void main(String[] args) throws IOException, InterruptedException {
  12. InetSocketAddress socketAddress = new InetSocketAddress("0.0.0.0", 10002);
  13. SocketChannel socketChannel = SocketChannel.open(socketAddress);
  14. log.info("连接 BIOServer 服务,端口:10002...");
  15. ArrayList<String> companyDetails = new ArrayList<>();
  16. // 创建消息列表
  17. companyDetails.add("腾讯");
  18. companyDetails.add("阿里巴巴");
  19. companyDetails.add("京东");
  20. companyDetails.add("百度");
  21. companyDetails.add("google");
  22. for (String companyName : companyDetails) {
  23. socketChannel.write(ByteBuffer.wrap(companyName.getBytes()));
  24. log.info("发送: " + companyName);
  25. ByteBuffer buffer = ByteBuffer.allocate(BUFF_SIZE);
  26. buffer.clear();
  27. socketChannel.read(buffer);
  28. String result = new String(buffer.array()).trim();
  29. log.info("收到NIOServer回复的消息:" + result);
  30. // 等待2秒钟再发送下一条消息
  31. Thread.sleep(2000);
  32. }
  33. socketChannel.close();
  34. }
  35. }

运行示例

首先运行我们的NIOServer,然后再运行NIOClient,观察控制台输出

NIOServer控制台输出

  1. 17:35:40.921 [main] INFO com.easy.javaBio.NIOServer - NIOServer运行中...按下Ctrl-C停止服务
  2. 17:35:40.924 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  3. 17:36:29.188 [main] INFO com.easy.javaBio.NIOServer - 连接到: /192.168.9.110:64443
  4. 17:36:29.188 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  5. 17:36:29.194 [main] INFO com.easy.javaBio.NIOServer - 得到了: 腾讯
  6. 17:36:29.194 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  7. 17:36:31.194 [main] INFO com.easy.javaBio.NIOServer - 得到了: 阿里巴巴
  8. 17:36:31.195 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  9. 17:36:33.195 [main] INFO com.easy.javaBio.NIOServer - 得到了: 京东
  10. 17:36:33.195 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  11. 17:36:35.196 [main] INFO com.easy.javaBio.NIOServer - 得到了: 百度
  12. 17:36:35.197 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  13. 17:36:37.197 [main] INFO com.easy.javaBio.NIOServer - 得到了: google
  14. 17:36:37.198 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…
  15. 17:36:39.198 [main] INFO com.easy.javaBio.NIOServer - 关闭客户端连接: /192.168.9.110:64443
  16. 17:36:39.198 [main] INFO com.easy.javaBio.NIOServer - 服务器等待新的连接和selector选择…

NIOClient控制台输出

  1. 17:36:29.189 [main] INFO com.easy.javaBio.NIOClient - 连接 BIOServer 服务,端口:10002...
  2. 17:36:29.194 [main] INFO com.easy.javaBio.NIOClient - 发送: 腾讯
  3. 17:36:29.194 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:腾讯 你好,这是BIOServer给你的回复消息:1576229789194
  4. 17:36:31.194 [main] INFO com.easy.javaBio.NIOClient - 发送: 阿里巴巴
  5. 17:36:31.195 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:阿里巴巴 你好,这是BIOServer给你的回复消息:1576229791194
  6. 17:36:33.195 [main] INFO com.easy.javaBio.NIOClient - 发送: 京东
  7. 17:36:33.196 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:京东 你好,这是BIOServer给你的回复消息:1576229793195
  8. 17:36:35.196 [main] INFO com.easy.javaBio.NIOClient - 发送: 百度
  9. 17:36:35.197 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:百度 你好,这是BIOServer给你的回复消息:1576229795197
  10. 17:36:37.197 [main] INFO com.easy.javaBio.NIOClient - 发送: google
  11. 17:36:37.198 [main] INFO com.easy.javaBio.NIOClient - 收到NIOServer回复的消息:google 你好,这是BIOServer给你的回复消息:1576229797198

NIO服务端每隔两秒会收到客户端的请求,并对客户端的消息做出回复。

直接使用Java NIO API构建应用程序是可以的,但要做到正确和安全并不容易。特别是在高负载下,可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务。可以选中Netty, Apache Mina等高性能网络编程框架。

Netty 构建 NIO 通信服务 方案

使用JDK原生网络应用程序API,会存在的问题

  • NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等

  • 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序

  • 可靠性能力补齐,开发工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大

Netty对JDK自带的NIO的API进行封装,解决上述问题,主要特点有

  • 高并发

Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高 。

  • 传输快

Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。

  • 封装好

Netty封装了NIO操作的很多细节,提供易于使用的API。

Netty框架的优势

  • API使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
  • 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。

代码实现

pom.xml依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.9.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.easy</groupId>
  12. <artifactId>netty</artifactId>
  13. <version>0.0.1</version>
  14. <name>netty</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. <encoding>UTF-8</encoding>
  19. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  20. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  21. </properties>
  22. <dependencies>
  23. <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
  24. <dependency>
  25. <groupId>io.netty</groupId>
  26. <artifactId>netty-all</artifactId>
  27. <version>4.1.43.Final</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <scope>compile</scope>
  38. </dependency>
  39. </dependencies>
  40. <modules>
  41. <module>java-tcp</module>
  42. <module>netty-server</module>
  43. <module>netty-client</module>
  44. </modules>
  45. <build>
  46. <plugins>
  47. <plugin>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-maven-plugin</artifactId>
  50. </plugin>
  51. </plugins>
  52. </build>
  53. </project>

搭建 Netty 服务端

NettyServer.java

  1. package com.easy.nettyServer;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelOption;
  5. import io.netty.channel.EventLoopGroup;
  6. import io.netty.channel.nio.NioEventLoopGroup;
  7. import io.netty.channel.socket.nio.NioServerSocketChannel;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.beans.factory.annotation.Value;
  10. import org.springframework.stereotype.Component;
  11. import javax.annotation.PostConstruct;
  12. import javax.annotation.PreDestroy;
  13. import java.net.InetSocketAddress;
  14. @Component
  15. @Slf4j
  16. public class NettyServer {
  17. /**
  18. * boss 线程组用于处理连接工作
  19. */
  20. private EventLoopGroup boss = new NioEventLoopGroup();
  21. /**
  22. * work 线程组用于数据处理
  23. */
  24. private EventLoopGroup work = new NioEventLoopGroup();
  25. @Value("${netty.port}")
  26. private Integer port;
  27. /**
  28. * 启动Netty Server
  29. *
  30. * @throws InterruptedException
  31. */
  32. @PostConstruct
  33. public void start() throws InterruptedException {
  34. ServerBootstrap bootstrap = new ServerBootstrap();
  35. bootstrap.group(boss, work)
  36. // 指定Channel
  37. .channel(NioServerSocketChannel.class)
  38. //使用指定的端口设置套接字地址
  39. .localAddress(new InetSocketAddress(port))
  40. //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
  41. .option(ChannelOption.SO_BACKLOG, 1024)
  42. //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
  43. .childOption(ChannelOption.SO_KEEPALIVE, true)
  44. //将小的数据包包装成更大的帧进行传送,提高网络的负载
  45. .childOption(ChannelOption.TCP_NODELAY, true)
  46. .childHandler(new ServerChannelInitializer());
  47. ChannelFuture future = bootstrap.bind().sync();
  48. if (future.isSuccess()) {
  49. log.info("启动 Netty Server");
  50. }
  51. }
  52. @PreDestroy
  53. public void destory() throws InterruptedException {
  54. boss.shutdownGracefully().sync();
  55. work.shutdownGracefully().sync();
  56. log.info("关闭Netty");
  57. }
  58. }

NettyServerHandler.java

  1. package com.easy.nettyServer;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import lombok.extern.slf4j.Slf4j;
  5. @Slf4j
  6. public class NettyServerHandler extends ChannelInboundHandlerAdapter {
  7. /**
  8. * 客户端连接会触发
  9. */
  10. @Override
  11. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  12. log.info("Channel active......");
  13. }
  14. /**
  15. * 客户端发消息会触发
  16. */
  17. @Override
  18. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  19. log.info("服务器收到消息: {}", msg.toString());
  20. ctx.write("我是服务端,我收到你的消息了!");
  21. ctx.flush();
  22. }
  23. /**
  24. * 发生异常触发
  25. */
  26. @Override
  27. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  28. cause.printStackTrace();
  29. ctx.close();
  30. }
  31. }

ServerChannelInitializer.java

  1. package com.easy.nettyServer;
  2. import io.netty.channel.ChannelInitializer;
  3. import io.netty.channel.socket.SocketChannel;
  4. import io.netty.handler.codec.string.StringDecoder;
  5. import io.netty.handler.codec.string.StringEncoder;
  6. import io.netty.util.CharsetUtil;
  7. public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
  8. @Override
  9. protected void initChannel(SocketChannel socketChannel) throws Exception {
  10. //添加编解码
  11. socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
  12. socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
  13. socketChannel.pipeline().addLast(new NettyServerHandler());
  14. }
  15. }

创建 Netty 客户端

NettyClient.java

  1. package com.easy.nettyClient;
  2. import io.netty.bootstrap.Bootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelFutureListener;
  5. import io.netty.channel.ChannelOption;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioSocketChannel;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.springframework.beans.factory.annotation.Value;
  12. import org.springframework.stereotype.Component;
  13. import javax.annotation.PostConstruct;
  14. import java.util.concurrent.TimeUnit;
  15. @Component
  16. @Slf4j
  17. public class NettyClient {
  18. private EventLoopGroup group = new NioEventLoopGroup();
  19. @Value("${netty.port}")
  20. private Integer port;
  21. @Value("${netty.host}")
  22. private String host;
  23. private SocketChannel socketChannel;
  24. /**
  25. * 发送消息
  26. */
  27. public void sendMsg(String msg) {
  28. socketChannel.writeAndFlush(msg);
  29. }
  30. @PostConstruct
  31. public void start() {
  32. Bootstrap bootstrap = new Bootstrap();
  33. bootstrap.group(group)
  34. .channel(NioSocketChannel.class)
  35. .remoteAddress(host, port)
  36. .option(ChannelOption.SO_KEEPALIVE, true)
  37. .option(ChannelOption.TCP_NODELAY, true)
  38. .handler(new NettyClientInitializer());
  39. ChannelFuture future = bootstrap.connect();
  40. //客户端断线重连逻辑
  41. future.addListener((ChannelFutureListener) future1 -> {
  42. if (future1.isSuccess()) {
  43. log.info("连接Netty服务端成功");
  44. } else {
  45. log.info("连接失败,进行断线重连");
  46. future1.channel().eventLoop().schedule(() -> start(), 20, TimeUnit.SECONDS);
  47. }
  48. });
  49. socketChannel = (SocketChannel) future.channel();
  50. }
  51. }

NettyClientHandler.java

  1. package com.easy.nettyClient;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import lombok.extern.slf4j.Slf4j;
  5. @Slf4j
  6. public class NettyClientHandler extends ChannelInboundHandlerAdapter {
  7. @Override
  8. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  9. log.info("客户端Active .....");
  10. }
  11. @Override
  12. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  13. log.info("客户端收到消息: {}", msg.toString());
  14. }
  15. @Override
  16. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  17. cause.printStackTrace();
  18. ctx.close();
  19. }
  20. }

NettyClientInitializer.java

  1. package com.easy.nettyClient;
  2. import io.netty.channel.ChannelInitializer;
  3. import io.netty.channel.socket.SocketChannel;
  4. import io.netty.handler.codec.string.StringDecoder;
  5. import io.netty.handler.codec.string.StringEncoder;
  6. public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
  7. @Override
  8. protected void initChannel(SocketChannel socketChannel) throws Exception {
  9. socketChannel.pipeline().addLast("decoder", new StringDecoder());
  10. socketChannel.pipeline().addLast("encoder", new StringEncoder());
  11. socketChannel.pipeline().addLast(new NettyClientHandler());
  12. }
  13. }

运行示例

打开浏览器,地址栏输入:http://localhost:8091/send?msg=你好,观察服务端和客户端控制台

服务端控制台输出

  1. 2019-12-13 18:01:37.901 INFO 11288 --- [ main] com.easy.nettyServer.NettyServer : 启动 Netty Server
  2. 2019-12-13 18:01:45.834 INFO 11288 --- [ntLoopGroup-3-1] com.easy.nettyServer.NettyServerHandler : Channel active......
  3. 2019-12-13 18:02:07.858 INFO 11288 --- [ntLoopGroup-3-1] com.easy.nettyServer.NettyServerHandler : 服务器收到消息: 你好

客户端控制台输出

  1. 2019-12-13 18:01:45.822 INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClient : 连接Netty服务端成功
  2. 2019-12-13 18:01:45.822 INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClientHandler : 客户端Active .....
  3. 2019-12-13 18:02:08.005 INFO 11908 --- [ntLoopGroup-2-1] com.easy.nettyClient.NettyClientHandler : 客户端收到消息: 我是服务端,我收到你的消息了!

表示使用Netty实现了我们的NIO通信了

Netty 模块组件

Bootstrap、ServerBootstrap

一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类。

Future、ChannelFuture

在Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理,但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFuture,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

Channel

Netty网络通信组件,能够用于执行网络I/O操作。Channel为用户提供:

  • 当前网络连接的通道的状态(例如是否打开?是否已连接?)
  • 网络连接的配置参数 (例如接收缓冲区大小)
  • 提供异步的网络I/O操作(如建立连接,读写,绑定端口),异步调用意味着任何I/O调用都将立即返回,并且不保证在调用结束时所请求的I/O操作已完成。调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以I/O操作成功、失败或取消时回调通知调用方。
  • 支持关联I/O操作与对应的处理程序

不同协议、不同阻塞类型的连接都有不同的 Channel 类型与之对应,下面是一些常用的 Channel 类型

  • NioSocketChannel,异步的客户端 TCP Socket 连接
  • NioServerSocketChannel,异步的服务器端 TCP Socket 连接
  • NioDatagramChannel,异步的 UDP 连接
  • NioSctpChannel,异步的客户端 Sctp 连接
  • NioSctpServerChannel,异步的 Sctp 服务器端连接

Selector

Netty基于Selector对象实现I/O多路复用,通过 Selector, 一个线程可以监听多个连接的Channel事件, 当向一个Selector中注册Channel 后,Selector 内部的机制就可以自动不断地查询(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel

NioEventLoop

NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:

  • I/O任务 即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。
  • 非IO任务 添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。

两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。

NioEventLoopGroup

NioEventLoopGroup,主要管理eventLoop的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个Channel上的事件,而一个Channel只对应于一个线程。

ChannelHandler

ChannelHandler是一个接口,处理I/O事件或拦截I/O操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序。

ChannelHandlerContext

保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象

ChannelPipline

保存ChannelHandler的List,用于处理或拦截Channel的入站事件和出站操作。 ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及Channel中各个的ChannelHandler如何相互交互。

资料

Spring Boot、Cloud 学习项目

Spring Boot 搭建TCP Server的更多相关文章

  1. Set up HTTP/2 server with Spring Boot 【基于Spring boot搭建http2.0服务器】

    1. Server side With spring boot, we can set up a http server easily. Restcontroller make it easier t ...

  2. maven 聚合工程 用spring boot 搭建 spring cloud 微服务 模块式开发项目

    项目的简单介绍: 项目采用maven聚合工程 用spring boot 搭建 spring cloud的微服务 模块式开发 项目的截图: 搭建开始: 能上图 我少打字 1.首先搭建maven的聚合工程 ...

  3. 自我救赎 → 利用 IDEA 和 Spring Boot 搭建 SSM

    前言 开心一刻 儿子读高中放学回来了,一向不管他学习的我突然来了兴趣,想看看他的学习他的状况,抄起他的数学习题看了起来,当看到 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x ...

  4. Spring Boot搭建Web项目常用功能

    搭建WEB项目过程中,哪些点需要注意: 1.技术选型: 前端:freemarker.vue 后端:spring boot.spring mvc 2.如何包装返回统一结构结果数据? 首先要弄清楚为什么要 ...

  5. 如何基于Spring Boot搭建一个完整的项目

    前言 使用Spring Boot做后台项目开发也快半年了,由于之前有过基于Spring开发的项目经验,相比之下觉得Spring Boot就是天堂,开箱即用来形容是绝不为过的.在没有接触Spring B ...

  6. (子文章)Spring Boot搭建两个微服务模块

    目录 1. 创建工程和user-service模块 1.1 创建空工程 1.2 在空工程里新建Module 2. 配置文件 2.1 pom.xml 2.2 application.yml 3. 代码 ...

  7. 记录一次Spring boot 搭建框架连接Mysql数据库注解事务不回滚的故障

    搭建了一个新框架,使用了spring boot 替换以简化原来繁杂的spring配置,使用Spring注解管理事务,持久层使用mybatis. 连接mysql数据库完成项目的过程中发现不支持事务,因为 ...

  8. 使用Spring Boot搭建应用开发框架(一) —— 基础架构

    Spring的简史 第一阶段:XML配置,在Spring1.x时代,使用Spring开发满眼都是xml配置的Bean,随着项目的扩大,我们需要把xml配置文件分放到不同的配置文件里,那时候需要频繁的在 ...

  9. spring boot(二): spring boot+jdbctemplate+sql server

    前言 小项目或者做demo时可以使用jdbc+sql server解决即可,这篇就基于spring boot环境使用jdbc连接sql server数据库,和spring mvc系列保持一致. 在sp ...

随机推荐

  1. Medium高赞系列,如何正确的在Stack Overflow提问

    在我们写程序的时候,经常会遇到各色各样的问题,在国内,小伙伴们经常去知乎.CSDN.博客园.思否.安卓巴士等地方提问并获得答案. 这些地方汇集了很多优秀的.爱分享的国内资源.小编比较自豪的一件事情就是 ...

  2. PHP关于access_token失效问题

    PHP关于access_token失效问题 有时候PHP设置了缓存 明明就是没有过期 但却提示失效这情况一般就是1 多个appid和secrete 生成的access_token互相覆盖了 所以 这种 ...

  3. PHP压缩文件夹的方法

    PHP压缩文件夹的方法<pre> public function addFileToZip($path, $zip) { $handler = opendir($path); //打开当前 ...

  4. haproxy+keepalived练习

    小的网站结构 说明:如果部署在云上,比如阿里云上,不需要自己部署keepalived,直接买阿里云的slb即可,slb然后分发流量到两台haproxy机器 一.先部署两个web服务器 编译安装ngin ...

  5. PHP 在 Laravel 中动态隐藏 API 字段

    我最近在 Laravel Brasil 社区看到一个问题,结果比看起来更有趣.想象一下你有一个 UsersResource 用下面的实现: <?php namespace App\Http\Re ...

  6. 在VMware15.5中安装CentOS7_7_64bit

    一.创建虚拟机 在我的另一个随笔里有. 地址为:https://www.cnblogs.com/qi-yuan/p/11692092.html 只是在虚拟机安装操作系统时候选择 Linux 而不是 W ...

  7. nyoj 69-数的长度 (log10(),计算数的位数)

    69-数的长度 内存限制:64MB 时间限制:3000ms 特判: No 通过数:10 提交数:13 难度:1 题目描述: N!阶乘是一个非常大的数,大家都知道计算公式是N!=N*(N-1)····· ...

  8. C语言1博客作业06

    这个作业属于哪个课程 C语言程序设计II 这个作业的要求在哪里 https://www.cnblogs.com/sanying/p/11771502.html 我在这个课程的目标是 端正态度,认真对待 ...

  9. 十、GAP

    1.1     背景 GAP(Generic Access Profile)位于主机协议栈的最顶层,用来定义BLE设备在待机或者连接状态中的行为,该Profile保证不同的Bluetooth产品可以互 ...

  10. HTML_本地存储

    在HTML5当中,新增了很多的存储方式,这里我先介绍两种,方便我们的使用和操作,具体新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问 ...