以前一直在搞前端的東西,都快忘了本身是個java開發。其實還有好多java方面的東西沒搞過,忽然瞭解到netty,以爲有必要學一學。
Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。前端
也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 能夠確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty至關於簡化和流線化了網絡應用的編程開發過程,例如:基於TCP和UDP的socket服務開發。java
以上兩種又可分爲同步和異步,即同步阻塞,同步非阻塞,異步阻塞,異步非阻塞。apache
至於這塊的詳細概念,你們能夠自行百度學習。總之,netty處理io很高效,不須要你擔憂。編程
能夠看出它支持的網絡傳輸協議,以及容器支持,安全支持,io.bootstrap
工做流程:
全部客戶端的鏈接交給住主線程去管理,響應客戶端的消息交給從線程去處理,整個線程池由netty負責。後端
<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.mike</groupId> <artifactId>netty</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> </dependencies> </project>
package netty; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http2.Http2Headers; import io.netty.util.concurrent.GlobalEventExecutor; /** * */ public class ChatHandler extends SimpleChannelInboundHandler{ public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); /** * 每當從服務端收到新的客戶端鏈接時,客戶端的 Channel 存入ChannelGroup列表中,並通知列表中的其餘客戶端 Channel */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); } channels.add(ctx.channel()); } /** * 每當從服務端收到客戶端斷開時,客戶端的 Channel 移除 ChannelGroup 列表中,並通知列表中的其餘客戶端 Channel */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n"); } channels.remove(ctx.channel()); } /** * 會話創建時 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"在線"); } /** * 會話結束時 */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"掉線"); } /** * 出現異常 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7) Channel incoming = ctx.channel(); System.out.println("ChatClient:"+incoming.remoteAddress()+"異常"); // 當出現異常就關閉鏈接 cause.printStackTrace(); ctx.close(); } /** * 讀取客戶端發送的消息,並將信息轉發給其餘客戶端的 Channel。 */ @Override protected void channelRead0(ChannelHandlerContext ctx, Object request) throws Exception { FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1,HttpResponseStatus.OK , Unpooled.wrappedBuffer("Hello netty" .getBytes())); response.headers().set("Content-Type", "text/plain"); response.headers().set("Content-Length", response.content().readableBytes()); response.headers().set("connection", HttpHeaderValues.KEEP_ALIVE); ctx.writeAndFlush(response); } }
這裏面其實只須要重寫channelRead0 方法就能夠了,其餘是它的生命週期的方法,能夠用來作日至記錄。咱們在讀取消息後,往channel裏寫入了一個http的response。瀏覽器
package netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; /** * 用來增長多個的處理類到 ChannelPipeline 上,包括編碼、解碼、SimpleChatServerHandler 等。 */ public class ChatServerInitializer extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("HttpResponseEncoder",new HttpResponseEncoder()); pipeline.addLast("HttpRequestDecoder",new HttpRequestDecoder()); pipeline.addLast("chathandler", new ChatHandler()); System.out.println("ChatClient:"+ch.remoteAddress() +"鏈接上"); } }
這個pipeline能夠理解爲netty的攔截器,每一個消息進來,通過各個攔截器的處理。咱們須要響應http消息,因此加入了響應編碼以及請求解碼,最後加上了咱們的自定義處理器。這裏面有不少處理器,netty以及幫你定義好的。安全
package netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * The class ChatServer */ public class ChatServer { private int port; public ChatServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChatServerInitializer()) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); System.out.println("ChatServer 啓動了"); // 綁定端口,開始接收進來的鏈接 ChannelFuture f = b.bind(port).sync(); // (7) // 等待服務器 socket 關閉 。 // 在這個例子中,這不會發生,但你能夠優雅地關閉你的服務器。 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); System.out.println("ChatServer 關閉了"); } } public static void main(String[] args) throws Exception { new ChatServer(8090).run(); } }
這個啓動類就是按照上面那個結構圖來的,添加兩個線程組,設置channel,添加消息處理器,配置一些選項option。服務器
啓動程序,瀏覽器訪問 http://localhost:8090網絡
能夠在瀏覽器看到咱們返回的消息,可是控制檯卻顯示鏈接了多個客戶端,實際上是由於瀏覽器發送了無關的請求道服務端,因爲咱們沒有作路由,因此全部請求都是200。
能夠看到,發送了兩次請求。如今咱們換postman測試。
此次只有一個客戶端鏈接,當咱們關閉postman:
客戶端顯示掉線,整個會話過程結束。
咱們完成了服務端的簡單搭建,模擬了聊天會話場景。後面再接着完善。
別忘了關注我 mike啥都想搞
還有其餘後端技術分享在個人公衆號。