【Netty4.X】Unity客户端与Netty服务器的网络通信(一)

   最近在读李林峰写的<<Netty权威指南(第2版)>>,所以本系列有部分内容是参考书籍来写的,感谢作者提供的学习资料。


----------------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------


我的开发环境

IDE:eclipse-jee-indigo-SR2-win32-x86_64

JDK:JDK1.7


下载Netty的软件包

官网地址:http://netty.io/,从【Downloads】选择下载netty-all-4.1.1.Final-sources。如图:



搭建Netty应用工程

创建一个java工程,将netty-all-4.1.1.Final.jar包导入到项目中

【Netty4.X】Unity客户端与Netty服务器的网络通信(一)_第1张图片

Netty服务器端开发

package com.lll.game;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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

public class HttpServer {
    private static Log log = LogFactory.getLog(HttpServer.class);
    
    public static void main(String[] args) throws Exception {
        HttpServer server = new HttpServer();
        log.info("服务已启动...");
        server.start(8844);
    }
    
    public void start(int port) throws Exception {
    	//配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel ch) throws Exception {
                                	ch.pipeline().addLast(new ServerHandler());
                                }
                            }).option(ChannelOption.SO_BACKLOG, 128) //最大客户端连接数为128
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
        	//优雅退出,释放线程池资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

5.x代码

package com.lll.game;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class ServerHandler extends ChannelHandlerAdapter{
	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		super.handlerAdded(ctx);
		System.out.println(ctx.channel().id()+"进来了");
	}
	
	@Override
	public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
		super.handlerRemoved(ctx);
		System.out.println(ctx.channel().id()+"离开了");
	}
	
}

ServerHandler继承于ChannelHandlerAdapter,而ChannelHandlerAdapter实现了ChannelHandler接口,在ChannelHandler里申明了2个生命周期的监听方法你可以完成任意初始化任务只要他不会被阻塞很长的时间。代码如下:

/*
 * Copyright 2012 The Netty Project
 *
 /
public interface ChannelHandler {

    /**
     * Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
     */
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
     * anymore.
     */
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if a {@link Throwable} was thrown.
     *
     * @deprecated is part of {@link ChannelInboundHandler}
     */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    /**
     * Indicates that the same instance of the annotated {@link ChannelHandler}
     * can be added to one or more {@link ChannelPipeline}s multiple times
     * without a race condition.
     * <p>
     * If this annotation is not specified, you have to create a new handler
     * instance every time you add it to a pipeline because it has unshared
     * state such as member variables.
     * <p>
     * This annotation is provided for documentation purpose, just like
     * <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
     */
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}


提示:本书作者使用的是netty是用的5.0,而我在官方下载的时候只找到4.x包(据说5.0好像已经被官方废弃了)。2个版本相比较,不同版本ChannelHandler申明的方法不一样!!

Netty5.x与4.x代码上的差异

在4.x中,ChannelHandlerAdapter里的channelRead方法被放到ChannelInboundHandlerAdapter类里面了,所以上面的代码需要修改下。代码如下:

package com.game.lll.net;

import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerHandler extends ChannelInboundHandlerAdapter{
	 private static Log log = LogFactory.getLog(ServerHandler.class);
	@Override
	public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		super.handlerAdded(ctx);
		System.out.println(ctx.channel().id()+"进来了");
	}
	
	@Override
	public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
		super.handlerRemoved(ctx);
		System.out.println(ctx.channel().id()+"离开了");
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		ByteBuf buf = (ByteBuf)msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		String body = new String(req,"UTF-8");
		log.info(req);
		String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?
				new Date(System.currentTimeMillis()).toString():"BAD ORDER";
		ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
		ctx.write(resp);
	}
	
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		// TODO Auto-generated method stub
		ctx.close();
	}
}


ServerHandler继承自ChannelInboundHandlerAdapter,它用于对网络事件进行读写操作,通常我们只需要关注channelRead和exceptionCaught方法。下面对这两个方法进行简单说明。

channelRead方法

  第30行做类型转换,将msg转换成Netty的ByteBuf对象。通过ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,根据可读的字节数创建byte数组,通过ByteBuf的readBytes方法将缓冲区中的字节数组复制到新建的byte数组中,最后通过new String构造函数获取请求信息。这是对请求消息进行判断,如果是“QUERY TIME ORDER”则创建应答信息,通过ChannelHandlerContext的write方法异步发送应答消息给客户端。

exceptionCaught方法

第43行,当发生异常时,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源。

Unity客户端开发

using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class HttpClient : MonoBehaviour
{
	private const string IP = "127.0.0.1";
	private const int PORT = 8844;

	public UILabel userName;
	public UILabel password;

	private Socket client;
	private string msg,ip;

	public void start()
	{
		try {
			client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			client.Connect(IP, PORT);
			Debug.Log ("连接服务器成功\r\n");

			Thread threadReceive = new Thread(ReceiveMsg);
			threadReceive.IsBackground = true;
			threadReceive.Start();
		}catch
		{
			Debug.Log ("连接服务器失败\r\n");
		}
	}

	public void Send()
	{
		if(client == null)
		{
			start ();
		}

		byte[] buffer = Encoding.UTF8.GetBytes("userName:"+userName.text+" password"+password.text);
		client.Send(buffer);

	}

	private void ReceiveMsg()
	{
		byte[] buffer = new byte[1024 * 1024];
		int len = 0;
		while (true)
		{
			len = client.Receive(buffer);
			//区分是客户端来了,还是消息来了
			if (buffer[0] == 1)//客户端
			{
				ip=Encoding.UTF8.GetString(buffer, 1, len - 1);
			}else//文本消息
			{
				msg = Encoding.UTF8.GetString(buffer, 1, len-1);
			}
		}
		print (msg);
	}

	void Update()
	{
		if (!string.IsNullOrEmpty(msg))
		{
			Debug.Log ("服务器说:" + msg + "\r\n");
			msg = "";
		}
		if (!string.IsNullOrEmpty(ip))
		{

			ip = "";
		}
	}

	void OnApplicationQuit()
	{
		client.Shutdown(SocketShutdown.Both);
		client.Close();
	}
}


运行和调试

启动服务器端


启动客户端


调试结果

服务器控制台

客户端控制台

【Netty4.X】Unity客户端与Netty服务器的网络通信(一)_第2张图片

你可能感兴趣的:(netty)