Netty構建游戲服務器(二)--Hello World


 

一,准備工作

1,netty-all-4.1.5.Final.jar(官網下載)

2,eclipse

二,步驟概要

1,服務器開發

(1),創建Server類

該類是程序的主入口,有main方法,服務器開啟也是在此執行。

該類主要是提供了channel鏈接,綁定了端口。

該類需要new一個Initalizer類來完成服務器開啟。

(2),創建Initalizer類

該類是初始化類,主要是創建了傳輸通道ChannelPipeline,然后在通道中加入了一些列的Handler,其中解碼和編碼Handler是Netty自帶的輔助類,在最后假如了自定義業務控制器類。

(3),創建Handler類

該類是自定義的業務控制器,需要的邏輯都在這里實現,例如收到信息如何處理,斷開連接如何發送消息等。

2,客戶端開發

和服務器端開發類似,不同在於:

(1),client類(對應服務器的server類)只需要一個EventLoopGroup,而服務器類需要兩個。

(2),編碼解碼器要和服務器一致。

三,具體實現

1,項目樹形圖

服務器端和客戶端分別有三個類,HelloClient是客戶端主入口,HelloServer是服務器端主入口。

2,服務器代碼

(1),HelloServer

package org.example.hello;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class HelloServer {
    
    /**
     * 服務端監聽的端口地址
     */
    private static final int portNumber = 7878;
    
    public static void main(String[] args) throws InterruptedException {
        //開啟兩個事件循環組,事件循環組會自動構建EventLoop,服務器一般開啟兩個,提高效率
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
           //Netty的引導類,用於簡化開發
            ServerBootstrap b = new ServerBootstrap();
           //把事件循環組加入引導程序
            b.group(bossGroup, workerGroup);
           //開啟socket
            b.channel(NioServerSocketChannel.class);
           //加入業務控制器,這里是加入一個初始化類,其中包含了很多業務控制器
            b.childHandler(new HelloServerInitializer());

            // 服務器綁定端口監聽
            ChannelFuture f = b.bind(portNumber).sync();
            // 監聽服務器關閉監聽
            f.channel().closeFuture().sync();

            // 可以簡寫為
            /* b.bind(portNumber).sync().channel().closeFuture().sync(); */
        } finally {
           //Netty優雅退出
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

(1),HelloServerInitializer

初始化傳輸通道

package org.example.hello;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

//繼承Netty提供的初始化類,只要復寫其中的方法就可以了
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
       //開啟傳輸通道,這個通道的作用就是管理控制器,形成一個責任鏈式管理
        ChannelPipeline pipeline = ch.pipeline();

        // 以("\n")為結尾分割的 解碼器
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));

        // 字符串解碼 和 編碼
        pipeline.addLast("decoder", new StringDecoder()); 
        pipeline.addLast("encoder", new StringEncoder());

        // 加入自定義的Handler
        pipeline.addLast("handler", new HelloServerHandler());
        //初始化類一般都是先加入編碼解碼器來解讀傳輸來的消息,然后加入自定義類來處理業務邏輯
    }
}

(1),HelloServerHandler

定義業務邏輯

package org.example.hello;

import java.net.InetAddress;

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.util.concurrent.GlobalEventExecutor;

//繼承Netty提供的通道傳入處理器類,只要復寫方法就可以了,簡化開發
public class HelloServerHandler extends SimpleChannelInboundHandler<String> {
    
    //獲取現有通道,一個通道channel就是一個socket鏈接在這里
    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //有新鏈接加入,對外發布消息
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  // (2)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n");
        }
        channels.add(ctx.channel());
    } 
    
    //有鏈接斷開,對外發布消息
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  // (3)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n");
        }
        channels.remove(ctx.channel());
    }
    
    //消息讀取有兩個方法,channelRead和channelRead0,其中channelRead0可以讀取泛型,常用  
    //收到消息打印出來,並返還客戶端消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 收到消息直接打印輸出
        System.out.println(ctx.channel().remoteAddress() + " Say : " + msg);
        
        // 返回客戶端消息 - 我已經接收到了你的消息
        ctx.writeAndFlush("Received your message !\n");
    }
    
    /*
     * 
     * 覆蓋 channelActive 方法 在channel被啟用的時候觸發 (在建立連接的時候)
     * 
     * channelActive 和 channelInActive 在后面的內容中講述,這里先不做詳細的描述
     * */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        
        System.out.println("RamoteAddress : " + ctx.channel().remoteAddress() + " active !");
        
        ctx.writeAndFlush( "Welcome to " + InetAddress.getLocalHost().getHostName() + " service!\n");
        
        super.channelActive(ctx);
    }
}

3,客戶端代碼

(1),HelloClient

package org.example.hello;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class HelloClient {
    
    public static String host = "127.0.0.1";
    public static int port = 7878;

    /**
     * @param args
     * @throws InterruptedException 
     * @throws IOException 
     */
    public static void main(String[] args) throws InterruptedException, IOException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
            .channel(NioSocketChannel.class)
            .handler(new HelloClientInitializer());

            // 連接服務端
            Channel ch = b.connect(host, port).sync().channel();
            
            // 控制台輸入
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            //也可以用while循環
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    continue;
                }
                /*
                 * 向服務端發送在控制台輸入的文本 並用"\r\n"結尾
                 * 之所以用\r\n結尾 是因為我們在handler中添加了 DelimiterBasedFrameDecoder 幀解碼。
                 * 這個解碼器是一個根據\n符號位分隔符的解碼器。所以每條消息的最后必須加上\n否則無法識別和解碼
                 * */
                ch.writeAndFlush(line + "\r\n");
            }
        } finally {
            // The connection is closed automatically on shutdown.
            group.shutdownGracefully();
        }
    }
}

(2),HelloClientInitializer

package org.example.hello;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class HelloClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        /*
         * 這個地方的 必須和服務端對應上。否則無法正常解碼和編碼
         * 
         * 解碼和編碼 我將會在下一張為大家詳細的講解。再次暫時不做詳細的描述
         * 
         * */
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        
        // 客戶端的邏輯
        pipeline.addLast("handler", new HelloClientHandler());
    }
}

(3),HellClientHandler

package org.example.hello;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class HelloClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        
        System.out.println("Server say : " + msg);
    }
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client active ");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client close ");
        super.channelInactive(ctx);
    }
}

四,運行測試

服務器右鍵運行程序

客戶端右鍵運行程序

服務器端console:

客戶端console:

切換console:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM