netty搭建web聊天室(1)

以前一直在搞前端的東西,都快忘了本身是個java開發。其實還有好多java方面的東西沒搞過,忽然瞭解到netty,以爲有必要學一學。

介紹

Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。前端

也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 能夠確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty至關於簡化和流線化了網絡應用的編程開發過程,例如:基於TCP和UDP的socket服務開發。java

一些IO概念

  • NIO (non-blocking IO) 非阻塞
  • BIO (blocking IO) 阻塞

以上兩種又可分爲同步和異步,即同步阻塞,同步非阻塞,異步阻塞,異步非阻塞。apache

  • 阻塞:數據沒來,啥都不作,直到數據來了,才進行下一步的處理。
  • 非阻塞:數據沒來,進程就不停的去檢測數據,直到數據來。

至於這塊的詳細概念,你們能夠自行百度學習。總之,netty處理io很高效,不須要你擔憂。編程

netty結構

19172705-3be5145d510746799c53b39528526432.png

能夠看出它支持的網絡傳輸協議,以及容器支持,安全支持,io.bootstrap

19172727-038d2796569a40d5b2c61dc414ed5ab4.png

工做流程:
全部客戶端的鏈接交給住主線程去管理,響應客戶端的消息交給從線程去處理,整個線程池由netty負責。後端

搭建服務

  • 建立maven工程引入最新的依賴
<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啥都想搞
mike啥都想搞

還有其餘後端技術分享在個人公衆號。

相關文章
相關標籤/搜索