【游戏开发】Unity客户端与Netty4.X服务器的网络通信(一)
netty,unity与netty的网络通信2016-07-20
最近在读李林峰写的<<Netty权威指南(第2版)>,所以本系列有部分内容是参考书籍来写的,感谢作者提供的学习资料。
----------------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------
1.创建服务启动类HttpServer,既然作为服务器它肯定和tomcat服务器一样有个开关来开启/关闭服务器,可以在类中看到有个main函数。对的,它的启动方式就是右键->Run As->Java Application
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(); } } }
2.创建ServerHandler类来负责对网络事件进行读写操作
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()+"离开了"); } }
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方法。下面对这两个方法进行简单说明。
第30行做类型转换,将msg转换成Netty的ByteBuf对象。通过ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,根据可读的字节数创建byte数组,通过ByteBuf的readBytes方法将缓冲区中的字节数组复制到新建的byte数组中,最后通过new String构造函数获取请求信息。这是对请求消息进行判断,如果是“QUERY TIME ORDER”则创建应答信息,通过ChannelHandlerContext的write方法异步发送应答消息给客户端。
第43行,当发生异常时,关闭ChannelHandlerContext,释放和ChannelHandlerContext相关联的句柄等资源。
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(); } }
服务器控制台
客户端控制台