这里我们用Netty实现消息的接受及分发,服务器端的 handler 添加流程如图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.netty.dispatch; 2 3 import com.netty.model.SocketModel; 4 import com.netty.protocol.LoginProtocol; 5 import io.netty.channel.ChannelHandlerContext; 6 7 public class BattleDispatch { 8 private static BattleDispatch instance = new BattleDispatch(); 9 10 public static BattleDispatch getInstance() { 11 return instance; 12 } 13 14 public void dispatch(ChannelHandlerContext ctx, SocketModel message) { 15 switch (message.getArea()) { 16 case LoginProtocol.Area_LoginRequest: 17 // 处理登录的事务 18 break; 19 default: 20 break; 21 } 22 } 23 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.netty.dispatch; 2 3 /* 4 * 处理登陆数据并把结果分发回客户端 5 */ 6 7 import java.util.List; 8 import com.netty.model.SocketModel; 9 import com.netty.protocol.LoginProtocol; 10 import com.netty.protocol.TypeProtocol; 11 import com.netty.util.VerifyOperate; 12 13 import io.netty.channel.ChannelHandlerContext; 14 15 public class LoginDispatch { 16 17 private static LoginDispatch instance = new LoginDispatch(); 18 19 public static LoginDispatch getInstance() { 20 return instance; 21 } 22 23 public void dispatch(ChannelHandlerContext ctx, SocketModel message) { 24 switch (message.getArea()) { 25 case LoginProtocol.Area_LoginRequest: 26 LoginResponse(ctx, message); 27 28 break; 29 default: 30 break; 31 } 32 } 33 /* 34 *检测用户登录是否密码错误,用户名不存在等,返回int对应的不同类型 35 */ 36 public void LoginResponse(ChannelHandlerContext ctx, SocketModel request) { 37 SocketModel response = new SocketModel(); 38 int command = LoginCheck(ctx, request); //success 10 39 response.setType(TypeProtocol.TYPE_LOGIN); //0 40 response.setArea(LoginProtocol.Area_LoginResponse); //6 41 response.setCommand(command); 42 response.setMessage(request.getMessage()); 43 ctx.writeAndFlush(response); // 44 45 if (command == LoginProtocol.Login_Succeed) { 46 LoginUser(ctx, request);// 如果成功就登陆用户,开始新手向导 47 } 48 } 49 50 public int LoginCheck(ChannelHandlerContext ctx, SocketModel request) { 51 List<String> message = request.getMessage(); 52 String username = message.get(0); 53 String password = message.get(1); 54 55 System.out.println(username); 56 System.out.println(password); 57 58 if (message.isEmpty()) { 59 return LoginProtocol.Login_InvalidMessage; 60 } else { 61 if (VerifyOperate.getInstance().checkPlayer("tb_verify", username, 62 password) > 1000) { 63 return LoginProtocol.Login_Succeed; 64 } else { 65 return LoginProtocol.Login_InvalidPassword; 66 } 67 } 68 } 69 70 public void LoginUser(ChannelHandlerContext ctx, SocketModel socketModel) { 71 // user = 72 // UserMySQL.getInstance().initUser(User.getUserByChannel(ctx.channel())); 73 // user.setWizard(WizardMySQL.getInstance().initWizard(user.getUserID())); 74 // SocketModel message = new SocketModel(); 75 // message.setType(TypeProtocol.TYPE_WIZARD); 76 // message.setArea(WizardProtocol.Wizard_Create_Request); 77 // message.setCommand(user.getWizard().getStepIndex()); 78 // message.setMessage(null); 79 // ctx.writeAndFlush(message); 80 } 81 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.netty.server; 2 3 import com.netty.dispatch.BattleDispatch; 4 import com.netty.dispatch.LoginDispatch; 5 import com.netty.model.SocketModel; 6 import com.netty.protocol.TypeProtocol; 7 import io.netty.channel.ChannelHandlerAdapter; 8 import io.netty.channel.ChannelHandlerContext; 9 10 public class ServerHandler extends ChannelHandlerAdapter { 11 12 public void channelActive(ChannelHandlerContext ctx) throws Exception // 客户端连上服务器的时候会触发 13 { 14 System.out.println("client:" + ctx.channel().id() + " join server"); 15 } 16 17 public void channelInactive(ChannelHandlerContext ctx) throws Exception // 当客户端断开连接的时候触发 18 { 19 System.out.println("client:" + ctx.channel().id() + " leave server"); 20 } 21 22 public void channelRead(ChannelHandlerContext ctx, Object msg) 23 throws Exception // 当客户端发送数据到服务器会触发 24 { 25 SocketModel message = (SocketModel) msg; 26 27 switch (message.getType()) { 28 case TypeProtocol.TYPE_LOGIN: 29 LoginDispatch.getInstance().dispatch(ctx, message); 30 break; 31 32 case TypeProtocol.TYPE_USER: 33 //UserDispatch.getInstance().dispatch(ctx, message); 34 break; 35 36 case TypeProtocol.TYPE_WIZARD: 37 //WizardDispatch.getInstance().dispatch(ctx, message); 38 break; 39 40 case TypeProtocol.TYPE_BATTLE: 41 BattleDispatch.getInstance().dispatch(ctx, message); 42 break; 43 44 default: 45 break; 46 } 47 48 } 49 50 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 51 throws Exception { 52 53 } 54 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.netty.server; 2 3 import com.netty.decoder.LengthDecoder; 4 import com.netty.decoder.MessageDecoder; 5 import com.netty.encoder.MessageEncoder; 6 7 import io.netty.bootstrap.ServerBootstrap; 8 import io.netty.channel.ChannelFuture; 9 import io.netty.channel.ChannelInitializer; 10 import io.netty.channel.ChannelOption; 11 import io.netty.channel.EventLoopGroup; 12 import io.netty.channel.nio.NioEventLoopGroup; 13 import io.netty.channel.socket.SocketChannel; 14 import io.netty.channel.socket.nio.NioServerSocketChannel; 15 16 public class GameServer { 17 18 public void bind(int port) throws Exception 19 { 20 //Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的, 21 //注册Channel,管理他们的生命周期 22 //服务器端需要指定两个 EventLoopGroup, 一个是 bossGroup, 用于处理客户端的连接请求; 23 //另一个是 workerGroup, 用于处理与各个客户端连接的 IO 操作. 24 EventLoopGroup bossGroup = new NioEventLoopGroup(); 25 EventLoopGroup workGroup = new NioEventLoopGroup(); 26 try 27 { 28 ServerBootstrap b = new ServerBootstrap(); //server 引导程序 29 30 b.group(bossGroup, workGroup) 31 .channel(NioServerSocketChannel.class) //指定 Channel类型. 因为是服务器端使用NioServerSocketChannel. 32 .option(ChannelOption.SO_BACKLOG, 1024) //客户端连接数为1024 33 .childHandler(new ChannelInitializer<SocketChannel>() 34 { 35 protected void initChannel(SocketChannel ch) throws Exception 36 { 37 //注册业务handler 38 ch.pipeline().addLast(new LengthDecoder(1024, 0, 4, 0, 4)); 39 ch.pipeline().addLast(new MessageDecoder()); 40 ch.pipeline().addLast(new MessageEncoder()); 41 ch.pipeline().addLast(new ServerHandler()); 42 } 43 }); 44 45 ChannelFuture f = b.bind(port).sync(); 46 47 if(f.isSuccess()) 48 { 49 System.out.println("Server starts success at port: " + port); 50 } 51 52 f.channel().closeFuture().sync(); 53 } 54 catch(Exception e) 55 { 56 e.printStackTrace(); 57 } 58 finally 59 { 60 bossGroup.shutdownGracefully(); 61 workGroup.shutdownGracefully(); 62 } 63 64 } 65 66 public static void main(String args[]) throws Exception 67 { 68 int port = 8080; 69 new GameServer().bind(port); 70 } 71 }
我们添加客户端登陆逻辑
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using UnityEngine; 2 using System.Collections.Generic; 3 using UnityEngine.UI; 4 5 public class LoginUI : MonoBehaviour { 6 7 private InputField UserNameInput; 8 private InputField PassWordInput; 9 10 void Start() 11 { 12 UserNameInput = transform.Find("UserName").GetComponent<InputField>(); 13 PassWordInput = transform.Find("PassWord").GetComponent<InputField>(); 14 15 if (PlayerPrefs.GetString("Username") != null && PlayerPrefs.GetString("Username") != "") 16 { 17 UserNameInput.text = PlayerPrefs.GetString("Username"); //数据持久化 18 } 19 } 20 21 public void ButtonClick(GameObject click) 22 { 23 if(click.name.Equals("BtnLogin")) 24 { 25 if (UserNameInput.text == "") 26 { 27 return; 28 } 29 else if (PassWordInput.text == "") 30 { 31 return; 32 } 33 else 34 { 35 SocketModel LoginRequest = new SocketModel(); 36 LoginRequest.SetType(TypeProtocol.TYPE_LOGIN); 37 LoginRequest.SetArea(LoginProtocol.Area_LoginRequest); 38 LoginRequest.SetCommand(0); 39 List<string> message = new List<string>(); 40 message.Add(UserNameInput.text); 41 message.Add(PassWordInput.text); 42 LoginRequest.SetMessage(message); 43 MainClient.instance.SendMsg(LoginRequest); 44 } 45 } 46 if (click.name.Equals("BtnRegister")) 47 { 48 //用户注册 49 } 50 } 51 }
最后新建一个gameobject,将MainClient赋给它,将之做成prefab
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using UnityEngine; 2 using System.Net.Sockets; 3 using System; 4 using System.IO; 5 using ProtoBuf; 6 7 public class MainClient : MonoBehaviour { 8 9 private const string HOST = "192.168.5.141"; //服务器IP 10 private const int PORT = 8080; 11 public static MainClient instance; 12 public static TcpClient client; 13 14 private byte[] receiveData; 15 private int len; //消息长度 16 private bool isHead; 17 private SocketModel message; 18 19 void Awake() 20 { 21 if (instance == null) 22 { 23 instance = this; 24 DontDestroyOnLoad(this.gameObject); 25 } 26 } 27 28 // Use this for initialization 29 void Start () 30 { 31 if (client == null) 32 { 33 Connect(); 34 } 35 36 isHead = true; 37 receiveData = new byte[800]; 38 ///在start里面开始异步接收消息 39 client.GetStream().BeginRead(receiveData, 0, 800, ReceiveMsg, client.GetStream()); 40 } 41 42 public void SendMsg(SocketModel socketModel) 43 { 44 byte[] msg = Serial(socketModel); 45 //消息体结构:消息体长度+消息体 46 byte[] data = new byte[4 + msg.Length]; 47 IntToBytes(msg.Length).CopyTo(data, 0); 48 msg.CopyTo(data, 4); 49 client.GetStream().Write(data, 0, data.Length); 50 } 51 52 public void ReceiveMsg(IAsyncResult ar) //异步接收消息 53 { 54 NetworkStream stream = (NetworkStream)ar.AsyncState; 55 stream.EndRead(ar); 56 57 if(isHead) //读取消息体的长度 58 { 59 byte[] lenByte = new byte[4]; 60 System.Array.Copy(receiveData, lenByte, 4); 61 len = ByteToInt(lenByte, 0); 62 isHead = false; 63 } 64 if(!isHead) //读取消息体内容 65 { 66 byte[] msgByte = new byte[len]; 67 //ConstrainedCopy: sourceArray,int sourceIndex,Array destinationArray, 68 //int destinationIndex,int length 69 System.Array.ConstrainedCopy(receiveData, 4, msgByte, 0, len); 70 isHead = true; 71 len = 0; 72 message = DeSerial(msgByte); 73 } 74 75 stream.BeginRead(receiveData, 0, 800, ReceiveMsg, stream); 76 77 switch (message.GetCommand()) 78 { 79 case LoginProtocol.Login_Succeed: 80 Debug.Log("Login_Succeed"); 81 break; 82 83 case LoginProtocol.Login_InvalidPassword: 84 Debug.Log("InvalidPassword"); 85 break; 86 87 default: 88 Debug.Log("TMT"); 89 break; 90 } 91 } 92 93 private byte[] Serial(SocketModel socketModel) //将SocketModel转化成字节数组 94 { 95 using (MemoryStream ms = new MemoryStream()) 96 { 97 Serializer.Serialize<SocketModel>(ms, socketModel); 98 byte[] data = new byte[ms.Length]; 99 ms.Position = 0; 100 ms.Read(data, 0, data.Length); 101 return data; 102 } 103 } 104 105 private SocketModel DeSerial(byte[] msg) //将字节数组转化成我们的消息类型SocketModel 106 { 107 using (MemoryStream ms = new MemoryStream()) 108 { 109 ms.Write(msg, 0, msg.Length); 110 ms.Position = 0; 111 SocketModel socketModel = Serializer.Deserialize<SocketModel>(ms); 112 return socketModel; 113 } 114 } 115 116 public static int ByteToInt(byte[] data, int offset) 117 { 118 int num = 0; 119 for(int i = offset; i < offset + 4; i++) 120 { 121 num <<= 8; 122 num |= (data[i] & 0xff); 123 } 124 return num; 125 } 126 127 public static byte[] IntToBytes(int num) 128 { 129 byte[] bytes = new byte[4]; 130 for (int i = 0; i < 4; i++) 131 { 132 bytes[i] = (byte)(num >> (24 - i * 8)); 133 } 134 return bytes; 135 } 136 137 void OnApplicatonQuit() 138 { 139 client.Close(); 140 } 141 142 public void Connect() 143 { 144 client = new TcpClient(); 145 try 146 { 147 client.Connect(HOST, PORT); 148 } 149 catch(Exception e) 150 { 151 Debug.LogException(e); 152 client.Close(); 153 } 154 } 155 156 }