java游戏服务器开发从0开始-Netty

1,为什么用Netty?

在这里我要吹一下netty了,如果你从0开始制作socket通讯,长连接,自定义协议,高并发,Netty就是绝配

如果你是小白,如果你对socket不太了解,想搭建一个稳定的java游戏服务器,你就选Netty

 

2,使用Netty搭建socket通讯框架

Netty的具体介绍和基础代码,请查找《Netty 权威指南》,如果你要用Netty,强烈建议你去读一下,你一定不会后悔

 

3,socket底层数据传输采用protobuf

为什么用protobuf?数据压缩,跨平台,客户端h5,u3d,cocos 都能开发


下面进入正式的服务器开发,参考 Netty 权威指南之Google protobuf 编解码 

新建服务器BaseServer.java

package com.xs.game.netty;

import org.apache.log4j.Logger;

import com.xs.game.protobuf.GameProtobuf;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LoggingHandler;

public abstract class BaseServer {
	protected static Logger log = Logger.getLogger(BaseServer.class);
	
	public BaseServer() {
		super();
	}
	private EventLoopGroup bossGroup;
	private EventLoopGroup workerGroup;
	private ServerBootstrap serverBootstrap;
	
	public void start(){
		this.bossGroup = new NioEventLoopGroup();
		this.workerGroup = new NioEventLoopGroup(); 
		try{
			this.serverBootstrap = new ServerBootstrap();
			this.serverBootstrap.group(bossGroup, workerGroup);
			this.serverBootstrap.channel(NioServerSocketChannel.class);
			//重复使用本地地址和端口
			this.serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
			//等待客户连接的超时时间
			this.serverBootstrap.option(ChannelOption.SO_TIMEOUT, 10);
			//启用Nagle算法
			this.serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
			//保持连接
			this.serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
			
			this.serverBootstrap.handler(new LoggingHandler());
			this.serverBootstrap.childHandler(new ChildChannelHandler());
			//绑定端口,同步等待成功
			ChannelFuture f = this.serverBootstrap.bind(getPort()).sync();
			log.info(this.getClass().getSimpleName()+" start success at "+getPort()+"..........");
			//等待服务器监听端口关闭
			f.channel().closeFuture().addListener( new ChannelFutureListener() {
				public void operationComplete(ChannelFuture future) throws Exception {
					future.channel().close();
					shutdown();
				}
		    });
		} catch (InterruptedException e) {
			log.error(this.getClass().getSimpleName()+" start failed at "+getPort()+"..........", e);
		}
	}
	
	protected class ChildChannelHandler extends ChannelInitializer{
		@Override
		protected void initChannel(SocketChannel channel) throws Exception {
			channel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
			channel.pipeline().addLast(new ProtobufDecoder(GameProtobuf.ServerRequest.getDefaultInstance()));
			channel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
			channel.pipeline().addLast(new ProtobufEncoder());
			channel.pipeline().addLast(new ProtobufHander());
		}
		
	}
	
	private void shutdown(){
		this.bossGroup.shutdownGracefully();
		this.workerGroup.shutdownGracefully();
	}
	
	public abstract int getPort();
}

把端口抽象出来,便于以后项目扩展

protobuf.proto

option java_package = "com.xs.game.protobuf";
option java_outer_classname = "GameProtobuf";

//客户端发送请求
message ServerRequest{
	required int32 msgId = 1;
	required bytes data = 2;
}

//服务器返回数据
message ServerResponse{
	required int32 msgId = 1;
	required bytes data = 2;
}

具体的实现在Netty权威指南中都有实现,不在细聊

重点聊一下msgId和data的作用

在游戏开发中,所有的客户端逻辑操作都对应了一个msgId,根绝msgId服务器找到相应的处理方法

msgId的作用

msgId在以往的开发中有两种形式

1,类名+方法名

通过类名得到服务器的bean,再根据反射得到处理方法,此类型调试很方便,通过消息数据能快速找到消息处理单元,调试消息很方便,但是用到了反射,对于我这种性格的人是不能忍的

2,int的msgId

服务器做一个维护表记录着msgId和处理单元的关系

以上两种第二种更加符合我的开发习惯


data的作用

byte[] data的处理也有两种形式

1,json方式处理

byte[] -> string -> json  

public void doPlayerLogin(JsonObject msg){
		//做登录处理
}

我们可以在分发上层把byte[]转换成JsonObject

优点:此方式扩展性很强,发挥json的特性 

缺点:消息数据体大,人数多后单服务器带宽有影响

2,protobuf方式处理

byte[]->protobuf

此方式需要根据具体的处理逻辑找到相应的protobuf

举例如下,当服务器收到一条登录消息,根据MsgId找到登录处理单元

public void doPlayerLogin(byte[] data) throws Exception {
		LoginMsg msg = LoginMsg.parseFrom(data);
		//做登录处理
}

//不能做到
public void doPlayerLogin(LoginMsg msg){
		//做登录处理
}

LoginMsg的转换只能放到处理单元里,因为我们不能在分发上层决定转换成哪个protobuf

优点:消息数据体小,转换性能好,框架结构确定死,参数名都有prtobuf文件确定好,前后端对接简单

缺点:随着项目消息增多,书写protobuf文件很头疼,服务器返回客户端的数据很复杂,要把所有返回数据都用protobuf书写出来,工作量还是很大。

 

 

4,测试

自己写一个netty客户端,模拟发送一个ServerRequest

测试完成

你可能感兴趣的:(java游戏开发)