天天看點

JAVA Socket程式設計學習9--Netty入門之Hello World!

内容來自(做了微小的改動):

http://www.cnblogs.com/zou90512/p/3492878.html

http://www.cnblogs.com/zou90512/p/3507729.html

在中國程式界。我們都是學着Hello World !慢慢成長起來的。逐漸從一無所知到熟悉精通的。

首先建立一個Java項目。引入一個Netty 架構的包。這個步驟我在本系列教程的後面就不在重複了。先上一張我示例的項目工程圖給大家看一下:

JAVA Socket程式設計學習9--Netty入門之Hello World!

1.下載下傳并為項目添加Netty架構

Netty的包大家可以從Netty官網:http://netty.io/downloads.html下載下傳

JAVA Socket程式設計學習9--Netty入門之Hello World!

    如圖所示(原作者寫作時間是2013-12-26,我這裡的截圖時間是2017-12-05): Netty提供了三個主要版本的架構包給大家下載下傳。

    3.10.6版本Final說明這個版本是3.x版本中最新的版本。final意味着功能不再繼續添加更新。僅為修改bug等提供繼續的更新。

    5.x版本由于是開始。不能排除是否穩定運作等問題。加上5.x在4.x的版本上略微修改的。在5.x穩定之前。不推薦大家學習使用。(我截圖的時候根本就沒有5.x版本,難道是不穩定删掉了?)

    本教程是基于Netty4.x(我這裡下載下傳的是穩定版的netty-4.0.53)版本的。

解壓netty-4.0.53.Final.tar.bz2後,大家可以看到這樣一個目錄結構,非常的清晰。

JAVA Socket程式設計學習9--Netty入門之Hello World!

    第一個檔案夾jar是jar包的檔案夾。第二個javadoc是API文檔。第三個license檔案夾是開源的授權檔案(可以直接無視)。

    javadoc檔案夾下面是一個jar包。可以直接解壓縮出來。解壓縮之後的檔案夾就是api文檔(以網頁的形勢展現)。

    jar檔案夾裡面有很多的jar包和一個all-in-one檔案夾。都是Netty架構的組成部分。all-in-one裡面有兩個檔案一個是jar包,另一個是對應的source源代碼包。這樣做的目的是為了給程式員有選擇的添加自己所需要的包。

    假如讀者是初學者的話。推薦直接套用all-in-one裡面的jar包。假如你熟悉Netty的話可以根據自己的項目需求添加不同的jar包。

2.建立Server服務端

    Netty建立全部都是實作自AbstractBootstrap(http://netty.io/5.0/api/io/netty/bootstrap/AbstractBootstrap.html)。用戶端的是Bootstrap,服務端的則是ServerBootstrap。

2.1建立一個HelloServer

package NettyTest;

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 {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            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 {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
           

    EventLoopGroup 是在4.x版本中提出來的一個新概念。用于channel的管理。服務端需要兩個。和3.x版本一樣,一個是boss線程一個是worker線程。

    b.childHandler(new HelloServerInitializer());    //用于添加相關的Handler

    服務端簡單的代碼,真的沒有辦法在精簡了感覺。就是一個綁定端口操作。

2.2建立和實作HelloServerInitializer

    在HelloServer中的HelloServerInitializer在這裡實作。

    首先我們需要明确我們到底是要做什麼的。很簡單。Hello World!。我們希望實作一個能夠像服務端發送文字的功能。服務端假如可以最好還能傳回點消息給用戶端,然用戶端去顯示。

    需求簡單。那我們下面就準備開始實作。

    DelimiterBasedFrameDecoder Netty在官方網站上提供的示例顯示 有這麼一個解碼器可以簡單的消息分割。

    其次 在decoder裡面我們找到了String解碼編碼器。着都是官網提供給我們的。

package NettyTest;

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 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());
	}
}
           

    上面的三個解碼和編碼都是Netty自帶的。

    另外我們自己的Handler怎麼辦呢。在最後我們添加一個自己的Handler用于寫自己的處理邏輯。

2.3 增加自己的邏輯HelloServerHandler

     自己的Handler我們這裡先去繼承extends官網推薦的SimpleChannelInboundHandler<C>。在這裡C,由于我們需求裡面發送的是字元串,這裡的C改寫為String。

package NettyTest;

import java.net.InetAddress;

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

public class HelloServerHandler extends SimpleChannelInboundHandler<String> {

	@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);
	}
}
           

    在channelHandlerContent自帶一個writeAndFlush方法。方法的作用是寫入Buffer并刷入。

注意:在3.x版本中此處有很大差別。在3.x版本中write()方法是自動flush的。在4.x版本的前面幾個版本也是一樣的。但是在4.0.9之後修改為WriteAndFlush。普通的write方法将不會發送消息。需要手動在write之後flush()一次

    這裡channeActive的意思是當連接配接活躍(建立)的時候觸發.輸出消息源的遠端位址。并傳回歡迎消息。

    channelRead0 在這裡的作用是類似于3.x版本的messageReceived()。可以當做是每一次收到消息是觸發。

    我們在這裡的代碼是傳回用戶端一個字元串"Received your message !".

注意:字元串最後面的"\n"是必須的。因為我們在前面的解碼器DelimiterBasedFrameDecoder是一個根據字元串結尾為“\n”來結尾的。假如沒有這個字元的話。解碼會出現問題。

3.Client用戶端

     類似于服務端的代碼。我們不做特别詳細的解釋。

     直接上示例代碼:

package NettyTest;

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));
			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();
		}
	}
}
           

下面的是HelloClientInitializer代碼貌似是和服務端的完全一樣。我沒注意看。其實編碼和解碼是相對的。服務端和用戶端都是解碼和編碼,才能通信。

package NettyTest;

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());
	}
}
           

HellClientHandler:

package NettyTest;

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);
	}
}
           

下面上幾張成果圖:

用戶端在連接配接建立是輸出了Client active 資訊,并收到服務端傳回的Welcome消息。

輸入Hello World!回車發送消息。服務端響應傳回消息已接受。

JAVA Socket程式設計學習9--Netty入門之Hello World!

用戶端控制台截圖

JAVA Socket程式設計學習9--Netty入門之Hello World!

服務端控制台截圖

4.Hello World代碼詳解

4.1HelloServer詳解

    HelloServer首先定義了一個靜态終态的變量---服務端綁定端口7878。至于為什麼是這個7878端口,純粹是筆者個人喜好。大家可以按照自己的習慣選擇端口。當然了。常用的幾個端口(例如:80,8080,843(Flash及Silverlight政策檔案請求端口等等),3306(Mysql資料庫占用端口))最好就不要占用了,避免一些奇怪的問題。

    HelloServer類裡面的代碼并不多。隻有一個main函數,加上内部短短的幾行代碼。

    Main函數開始的位置定義了兩個工作線程,一個命名為WorkerGroup,另一個命名為BossGroup。都是執行個體化NioEventLoopGroup。這一點和3.x版本中基本思路是一緻的。Worker線程用于管理線程為Boss線程服務。

    講到這裡需要解釋一下EventLoopGroup,它是4.x版本提出來的一個新概念。類似于3.x版本中的線程。用于管理Channel連接配接的。在main函數的結尾就用到了EventLoopGroup提供的便捷的方法,shutdownGraceFully(),翻譯為中文就是優雅的全部關閉。感覺是不是很有意思。作者居然會如此可愛的命名了這樣一個函數。檢視相應的源代碼。我們可以在DefaultEventExecutorGroup的父類MultithreadEventExecutorGroup中看到它的實作代碼。關閉了全部EventExecutor數組child裡面子元素。相比于3.x版本這是一個比較重大的改動。開發者可以很輕松的全部關閉,而不需要擔心出現記憶體洩露。

    在try裡面執行個體化一個ServerBootstrap b。設定group。設定channel為NioServerSocketChannel。

設定childHandler,在這裡使用執行個體化一個HelloServerInitializer類來實作,繼承ChannelInitializer<SocketChannel>。内部的代碼我們可以在前文的注視中大緻了解一下,主要作用是設定相關的位元組解碼編碼器,和代碼處理邏輯。Handler是Netty包裡面占很大一個比例。可見其的作用和用途。Handler涉及很多領域。HTTP,UDP,Socket,WebSocket等等。詳細的部分在本章的第三節解釋。

    設定好Handler綁定端口7878,并調用函數sync(),監聽端口(等待用戶端連接配接和發送消息)。并監聽端口關閉(為了防止線程停止)。

    最後finally我們要優雅的全部關閉服務端。^_^

4.2HelloClient詳解

    相比于服務端的代碼。用戶端要精簡一些。

    用戶端僅僅隻需要一個worker的EventLoopGroup。其次是類似于ServerBootstrap的HandlerInitializer。

    唯一不同的可能就是用戶端的connect方法。服務端的綁定并監聽端口,用戶端是連接配接指定的位址。Sync().channel()是為了傳回這個連接配接服務端的channel,并用于後面代碼的調用。

    BufferedReader這個是用于控制台輸入的。不做詳細的解釋了就。大家都懂的。

    當使用者輸入一行内容并回車之後。循環的讀取每一行内容。然後使用writeAndFlush向服務端發送消息。

4.3HandlerInitializer詳解

    Handler在Netty中是一個比較重要的概念。有着相當重要的作用。相比于Netty的底層。我們接觸更多的應該是他的Handler。在這裡我将它剝離出來單獨解釋。

    ServerHandlerInitializer繼承與ChannelInitializer<SocketChannel>需要我們實作一個initChannel()方法。我們定義的handler就是寫在這裡面。

    在最開始的地方定義了一個DelimiterBasedFrameDecoder。按直接翻譯就是基于分隔符的幀解碼器。再一次感覺架構的作者的命名,好直接好簡單。詳細的内容我們在後面的文章中在為大家詳細的解釋。目前大家知道他是以分隔符為分割标準的解碼器就好了。

    也許有人會問分隔符是什麼?我隻能 !*_* :“納尼 !!”。分隔符其實就是“\n”我們在學習C語言的時候最常用的的也許就是這個分隔符了吧。

    下面的則是StringDecoder和StringEncoder。字元串解碼器和字元串編碼器。

    最後面則是我們自己的邏輯。服務/用戶端邏輯是在消息解碼之後處理的。然後服務/用戶端傳回相關消息則是需要對消息進行相對應的編碼。最終才是以二進制資料流的形勢發送給服務/用戶端的。

Netty的UDP實作(http://www.cnblogs.com/wade-luffy/p/6180770.html):

服務端開發

由于UDP通信雙方不需要建立鍊路,是以,代碼相對于TCP更加簡單一些,代碼如下:啟動類ChineseProverbServer類

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;

//由于我們用的是UDP協定,是以要用NioDatagramChannel來建立
public class ChineseProverbServer {
	public void run(int port) throws Exception{
		EventLoopGroup group = new NioEventLoopGroup();
		Bootstrap b = new Bootstrap();
		//由于我們用的是UDP協定,是以要用NioDatagramChannel來建立
		b.group(group).channel(NioDatagramChannel.class)
		.option(ChannelOption.SO_BROADCAST, true)//支援廣播
		.handler(new ChineseProverbServerHandler());//ChineseProverbServerHandler是業務處理類
		b.bind(port).sync().channel().closeFuture().await();
	}
	public static void main(String [] args) throws Exception{
		int port = 8080;
		new ChineseProverbServer().run(port);
	}
}
           

    由于使用UDP通信,在建立Channel的時候通過NioDatagramChannel來建立,随後設定Socket參數支援廣播,最後設定處理handler ChineseProverbServerHandler. 

    相比于TCP通信,UDP不存在用戶端和伺服器端的實際連接配接,是以不需要為連接配接(channelPipeline)設定handler,對于服務端,隻需設定啟動輔助類的handler即可。

下面看ChineseProverbServerHandler的實作

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.ThreadLocalRandom;

/**
 * @author 作者 YYD
 * @version 建立時間:2016年11月18日 下午8:43:10
 * @function 未添加
 */
public class ChineseProverbServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
	//諺語清單
	private static final String[] DICTIONARY = { "隻要功夫深,鐵棒磨成針。",
		"舊時王謝堂前燕,飛入尋常百姓家。", "洛陽親友如相問,一片冰心在玉壺。", "一寸光陰一寸金,寸金難買寸光陰。",
		"老骥伏枥,志在千裡,烈士暮年,壯心不已" };
	private String nextQuote(){
		//傳回0-DICTIONARY.length中的一個整數。
		int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
		return DICTIONARY[quoteId];//将諺語清單中對應的諺語傳回
	}
	/**
	 * 在這個方法中,形參packet用戶端發過來的DatagramPacket對象
	 * DatagramPacket 類解釋
	 * 1.官網是這麼說的:
	 * The message container that is used for {@link DatagramChannel} to communicate with the remote peer.
	 * 翻譯:DatagramPacket 是消息容器,這個消息容器被 DatagramChannel使用,作用是用來和遠端裝置交流
	 * 2.看它的源碼我們發現DatagramPacket是final類不能被繼承,隻能被使用。我們還發現DatagramChannel最終實作了AddressedEnvelope接口,接下來我們看一下AddressedEnvelope接口。
	 * AddressedEnvelope接口官網解釋如下:
	 * A message that wraps another message with a sender address and a recipient address.
	 * 翻譯:這是一個消息,這個消息包含發送者和接受者消息
	 * 3.那我們知道了DatagramPacket它包含了發送者和接受者的消息,
	 * 通過content()來擷取消息内容
	 * 通過sender();來擷取發送者的消息
	 * 通過recipient();來擷取接收者的消息。
	 * 
	 * 4.public DatagramPacket(ByteBuf data, InetSocketAddress recipient) {}
	 * 這個DatagramPacket其中的一個構造方法,data 是發送内容;是發送都資訊。
	 */
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
		String req = packet.content().toString(CharsetUtil.UTF_8);//上面說了,通過content()來擷取消息内容
		System.out.println(req);
		if("諺語字典查詢?".equals(req)){//如果消息是“諺語字典查詢?”,就随機擷取一條消息發送出去。
			/**
			 * 重新 new 一個DatagramPacket對象,我們通過packet.sender()來擷取發送者的消息。
			 * 重新發達出去!
			 */
			ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("諺語查詢結果:"+nextQuote(),CharsetUtil.UTF_8), packet.sender()));
		}
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
		cause.printStackTrace();
	}
}
           

解釋:請看channelRead0方法,netty對UDP進行了封裝,接收到的是DatagramPacket對象,然後通過packet.content().toString(CharsetUtil.UTF_8)擷取packet的内容。如果是“諺語字典查詢?”字元串則随機取一個諺語傳回。DatagramPacket有二個參數,第一個是發送的内容,另一個是接收者的相關資訊,這個可以通過packet.sender()擷取。

用戶端開發

     UDP程式的用戶端和伺服器端代碼非常相似,唯一不同是UDP用戶端會主動構造請求消息,向本網段内的所有主機請求消息,對于服務端而言接收到廣播消息之後向廣播消息的發起方進行定點發送。

啟動類ChineseProverbClient類

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;

/**
 * @author 作者 YYD
 * @version 建立時間:2016年11月18日 下午9:00:11
 * @function 未添加
 */
public class ChineseProverbClient {
	public void run(int port) throws Exception{
		EventLoopGroup group  = new NioEventLoopGroup();
		try {
			Bootstrap b = new Bootstrap();
			b.group(group).channel(NioDatagramChannel.class)
			.option(ChannelOption.SO_BROADCAST,true)//允許廣播
			.handler(new ChineseProverbClientHandler());//設定消息處理器
			Channel ch = b.bind(0).sync().channel();
			//向網段内的所有機器廣播UDP消息。
			ch.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("諺語字典查詢?",CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255",port))).sync();
			if(!ch.closeFuture().await(15000)){
				System.out.println("查詢逾時!");
			}
		} catch (Exception e) {
			group.shutdownGracefully();
		}
	}
	public static void main(String [] args) throws Exception{
		int port = 8080;
		new ChineseProverbClient().run(port);
	}
}
           

解釋:建立UDPChannel和設定支援廣播屬性等與服務端完全一緻,由于不需要和服務端建立鍊路,UDP Channel 建立完成之後,用戶端就要主動發送廣播消息。而TCP用戶端是在用戶端和服務端鍊路建立成功之後由用戶端的業務handler發送消息,這就是二者最大差別。

ChineseProverClientHandler類

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;

/**
 * @author 作者 YYD
 * @version 建立時間:2016年11月18日 下午9:09:18
 * @function 未添加
 */
public class ChineseProverbClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {
	/**
	 * DatagramPacket的詳細介紹,看伺服器的代碼注釋,這裡不重複了。
	 */
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
		String response = msg.content().toString(CharsetUtil.UTF_8);
		if (response.startsWith("諺語查詢結果:")) {
			System.out.println(response);
			ctx.close();
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}
           

    代碼非常簡單,接收到服務端的消息之後将其轉成字元串然後判斷是否以“諺語查詢結果:”開頭,如果沒有發生丢包等問題,資料是完整的,就列印查詢結果,然後釋放資源。

運作結果:

先啟動UDP服務端,然後啟動用戶端(運作二次),運作結果如下:

伺服器運作結果:

JAVA Socket程式設計學習9--Netty入門之Hello World!

用戶端1運作結果:

JAVA Socket程式設計學習9--Netty入門之Hello World!

用戶端2運作結果:

JAVA Socket程式設計學習9--Netty入門之Hello World!

通過上圖我們可以看出,用戶端每次運作結果都不一樣,說明UDP伺服器的查詢功能正确,并且用戶端成功的接受到了伺服器的應答,說明整個過程沒有丢包和亂序問題。