前言
在游戏开发中,网络通信是不可避免的,为了更好地掌控网络通信,通常需要自定义网络协议。在这篇文章中,我们将会介绍如何使用Netty实现自定义协议,并将其应用到Unity的网络通信中。
准备工作
在开始之前,需要先准备好Java和C#的开发环境。同时,需要安装好Netty库和Unity库。
实现
自定义协议
我们将自定义一种协议,该协议用于Unity客户端与Java服务器之间的通信。 协议格式如下:
+——+————+——————+——————+
| 协议头 | 消息长度 | 消息体 |
+——+————+——————+——————+
|2 | 4 | n(n<=0) |
+——+————+——————+——————+
协议头固定为2个字节,消息长度固定为4个字节,用于存储消息体的长度。消息体长度没有固定,可以根据具体数据而变化。
服务器端Java代码
我们使用Netty库来实现服务器端代码。
public class Server {
private int port;
public Server(int port) {
this.port = port;
}
public void run() {
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 {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MessageDecoder());
pipeline.addLast(new MessageEncoder());
pipeline.addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
System.out.println("Server started on port " + port);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
ew Server(8888).run();
}
}
在代码中,我们首先定义了一个Server类,用于启动Netty服务器。在该类中,我们使用EventLoopGroup来创建boss和worker线程组。然后创建ServerBootstrap实例,并设置服务器的一些参数。我们需要设置端口号、Channel类型、ChannelHandler以及一些ChannelOption。最后,通过bind方法来启动服务器。如果绑定成功,则输出相应的信息。
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message message = (Message) msg;
System.out.println("Server received message: " + message.getBody()); // 处理接收到的消息
Message response = new Message();
response.setBody("Hello from server!");
ctx.writeAndFlush(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace(); ctx.close();
}
}
在上述代码中,我们使用了ChannelHandler来处理接收到的消息。在服务器收到消息后,我们会将消息转化为Message类,并输出到控制台上。然后处理相应业务逻辑,并将处理结果作为回传信息进行返回。
public class MessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 6) {
return;
}
in.markReaderIndex();
short magic = in.readShort();
if (magic != 0x7EF2) {
throw new CorruptedFrameException("Invalid magic number: " + magic);
}
int length = in.readInt();
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
byte[] body = new byte[length];
in.readBytes(body);
Message message = new Message();
message.setBody(new String(body, StandardCharsets.UTF_8));
out.add(message);
}
}
在上述代码中,我们使用了MessageDecoder来接收并解码消息。首先我们读取2个字节的协议头,判断是否为0x7EF2。然后读取4个字节的消息体长度,再读取相应长度的数据,并将其转化为Message对象。
public class MessageEncoder extends MessageToByteEncoder<Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
byte[] body = msg.getBody().getBytes(StandardCharsets.UTF_8);
out.writeShort(0x7EF2);
out.writeInt(body.length);
out.writeBytes(body);
}
}
在上述代码中,我们使用了MessageEncoder来编码和发送消息。首先我们写入协议头和消息体长度,然后写入相应长度的数据。
客户端C#代码
我们使用Unity库来实现客户端代码。
public class Client : MonoBehaviour {
private TcpClient client;
private NetworkStream stream;
public void Connect(string ip, int port) {
client = new TcpClient(ip, port);
stream = client.GetStream();
}
public void Send(string message) {
byte[] body = Encoding.UTF8.GetBytes(message);
int length = body.Length;
byte[] buffer = new byte[6 + length];
buffer[0] = 0x7E;
buffer[1] = 0xF2;
buffer[2] = (byte)((length << 24) & 0xFF);
buffer[3] = (byte)((length << 16) & 0xFF);
buffer[4] = (byte)((length << 8) & 0xFF);
buffer[5] = (byte)(length & 0xFF);
Array.Copy(body, 0, buffer, 6, length);
stream.Write(buffer, 0, buffer.Length);
byte[] recvBuffer = new byte[1024];
int recvLength = stream.Read(recvBuffer, 0, recvBuffer.Length);
string response = Encoding.UTF8.GetString(recvBuffer, 0, recvLength);
Debug.Log("Client received message: " + response);
}
public void Disconnect() {
stream.Close();
client.Close();
}
}
在客户端代码中,我们首先定义了一个Client类,用于处理客户端与服务器的通信。其中Connect方法用于连接服务器,Send方法用于发送消息,Disconnect方法用于断开连接。 在Send方法中,我们首先将消息体转化为byte数组,并获取其长度。然后根据协议格式,计算出整个消息的长度,并将其写