《从零开始搭建游戏服务器》Netty导入创建一个Socket服务器

http://blog.csdn.net/linshuhe1/article/details/53671820 

对于普通Web应用后端的开发,常常会借助一些成熟的框架,例如SpringMVC,游戏的服务器区别于此的特点是:游戏服务器需要有比较大的吞吐量,而且通常为Socket长连接,所以需要一个分布式架构。当前比较流行的网络通信框架都为NIO(非阻塞网络通信)框架,比较有代表性的有:NettyMina

      

一、优势对比:

        Netty是由JBoss提供的Java开发框架,适用于开发高性能、高稳定性的客户端或者服务端网络通信框架,相较于Mina有如下几点优点:

1.支持多种编解码方式,当然也包括当前游戏开发中应用最广的Google提供的Protobuff编解码方式;

2.典型的NIO架构,非阻塞异步的事件驱动网络通信架构,吞吐量高,支持高并发;


二、资源下载:

1.netty-all-4.0.42.Final.jar:在Netty官网下载所需版本的jar包,不同版本的Netty差别比较大,我选择的是Netty4.0.42 Final版本。

2.Log4j或common-logging:这是用于打印和记录日志的工具,非常实用,详细的使用方法可以参考:最详细的Log4j使用教程,当然也有更简单方便的一个工具:common-logging,使用方法参考: Apache Commons-logging使用实例


三、Socket服务:

1.新建一个Java工程,步骤:File —— new —— Java Project;

2.资源导入:

        解压上面下载的Netty4.0.42压缩包,将netty-4.0.42.Final\jar\all-in-one目录下面的netty-all-4.0.42.Final.jar和commons-logging-1.2中的commons-logging-1.2.jar一起复制到eclipse中项目的libs目录下面(没有这个目录的话可以自行创建,专门用于存放外部导入的jar包):


3.编写服务代码:

        在src目录下新建一个包名com.tw.login.server,然后新建两个类:

LoginSocketServer.java:用于管理Socket,例如:ip端口绑定,Socket启动和关闭等;

SocketServerHandler.java:Socket数据接收监听和解析中心;

        大致过程就是我们在LoginSocketServer中启动一个ServerBootsrap服务器,并且绑定线程组,并且绑定用来监听数据的继承自SocketChannel的自定义监听器类,在其中设置网络传输数据的编码方式,然后设置连接到此服务器需要访问的IP和端口。

        关于传输数据协议,这里我们先以最简单的字符串为例,以("\n")为结尾分割的解码器作为数据协议格式,关于更复杂的数据类型协议在后面的篇章会继续设计。


LoginSocketServer源码:

  1. public class LoginSocketServer {  
  2.     private static final Log logger = LogFactory.getLog(LoginSocketServer.class);  
  3.     private static final String IP = "127.0.0.1";  
  4.     private static final int PORT = 8088;  
  5.       
  6.     //分配用于处理业务的线程组数量  
  7.     protected static final int BisGroupSize = Runtime.getRuntime().availableProcessors()*2;  
  8.     //每个线程组中线程的数量  
  9.     protected static final int worGroupSize = 4;  
  10.       
  11.     private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BisGroupSize);  
  12.     private static final EventLoopGroup workerGroup = new NioEventLoopGroup(worGroupSize);  
  13.       
  14.     protected static void  run() throws Exception {  
  15.         ServerBootstrap bootstrap = new ServerBootstrap();  
  16.         bootstrap.group(bossGroup, workerGroup);  
  17.         bootstrap.channel(NioServerSocketChannel.class);  
  18.         bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  19.             @Override  
  20.             protected void initChannel(SocketChannel ch) throws Exception {  
  21.                 ChannelPipeline pipeline = ch.pipeline();  
  22.                 // 以("\n")为结尾分割的 解码器  
  23.                 pipeline.addLast("framer"new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));  
  24.                 pipeline.addLast("decoder"new StringDecoder());  
  25.                 pipeline.addLast("encoder"new StringEncoder());  
  26.                 pipeline.addLast(new SocketServerHandler());  
  27.             }  
  28.         });  
  29.         bootstrap.bind(IP,PORT).sync();  
  30.         logger.info("Socket服务器已启动完成");  
  31.     }  
  32.       
  33.     protected static void shutdown() {  
  34.         bossGroup.shutdownGracefully();  
  35.         workerGroup.shutdownGracefully();  
  36.     }  
  37.       
  38.     public static void  main(String[] args) throws Exception {  
  39.         logger.info("开始启动Socket服务器...");  
  40.         run();  
  41.     }  
  42.       
  43. }  
public class LoginSocketServer {
	private static final Log logger = LogFactory.getLog(LoginSocketServer.class);
	private static final String IP = "127.0.0.1";
	private static final int PORT = 8088;
	
	//分配用于处理业务的线程组数量
	protected static final int BisGroupSize = Runtime.getRuntime().availableProcessors()*2;
	//每个线程组中线程的数量
	protected static final int worGroupSize = 4;
	
	private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BisGroupSize);
	private static final EventLoopGroup workerGroup = new NioEventLoopGroup(worGroupSize);
	
	protected static void  run() throws Exception {
		ServerBootstrap bootstrap = new ServerBootstrap();
		bootstrap.group(bossGroup, workerGroup);
		bootstrap.channel(NioServerSocketChannel.class);
		bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel ch) throws Exception {
				ChannelPipeline pipeline = ch.pipeline();
				// 以("\n")为结尾分割的 解码器
				pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
		        pipeline.addLast("decoder", new StringDecoder());
		        pipeline.addLast("encoder", new StringEncoder());
				pipeline.addLast(new SocketServerHandler());
			}
		});
		bootstrap.bind(IP,PORT).sync();
		logger.info("Socket服务器已启动完成");
	}
	
	protected static void shutdown() {
		bossGroup.shutdownGracefully();
		workerGroup.shutdownGracefully();
	}
	
	public static void  main(String[] args) throws Exception {
		logger.info("开始启动Socket服务器...");
		run();
	}
	
}
SocketServerHandler源码:

  1. public class SocketServerHandler extends SimpleChannelInboundHandler<String> {  
  2.     private static final Log logger = LogFactory.getLog(LoginSocketServer.class);  
  3.   
  4.     @Override  
  5.     public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) throws Exception {  
  6.         // TODO Auto-generated method stub  
  7.           
  8.     }  
  9.   
  10.     @Override  
  11.     public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {  
  12.         // TODO Auto-generated method stub  
  13.         logger.info("数据内容:data="+data);  
  14.     }  
  15. }  
public class SocketServerHandler extends SimpleChannelInboundHandler<String> {
	private static final Log logger = LogFactory.getLog(LoginSocketServer.class);

	@Override
	public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) throws Exception {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {
		// TODO Auto-generated method stub
        logger.info("数据内容:data="+data);
	}
}

为了测试还需要创建一个客户端,其实创建过程很简单:直接复制一份上述服务器的代码,稍作修改即可:
LoginSocketClient源码:

  1. private static final Log logger = LogFactory.getLog(LoginSocketClient.class);  
  2.     private static final String IP = "127.0.0.1";  
  3.     private static final int PORT = 8088;  
  4.       
  5.     private static final EventLoopGroup group = new NioEventLoopGroup();  
  6.       
  7.     @SuppressWarnings("rawtypes")  
  8.     protected static void  run() throws Exception {  
  9.         Bootstrap bootstrap = new Bootstrap();  
  10.         bootstrap.group(group);  
  11.         bootstrap.channel(NioSocketChannel.class);  
  12.         bootstrap.handler(new ChannelInitializer() {  
  13.             @Override  
  14.             protected void initChannel(Channel ch) throws Exception {  
  15.                 // TODO Auto-generated method stub  
  16.                 ChannelPipeline pipeline = ch.pipeline();  
  17.                 /* 
  18.                  * 这个地方的 必须和服务端对应上。否则无法正常解码和编码 
  19.                  *  
  20.                  * 解码和编码 我将会在下一张为大家详细的讲解。再次暂时不做详细的描述 
  21.                  *  
  22.                  * */  
  23.                 pipeline.addLast("framer"new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));  
  24.                 pipeline.addLast("decoder"new StringDecoder());  
  25.                 pipeline.addLast("encoder"new StringEncoder());  
  26.                 pipeline.addLast(new SocketClientHandler());  
  27.             }  
  28.         });  
  29.           
  30.         // 连接服务端  
  31.         Channel ch = bootstrap.connect(IP,PORT).sync().channel();  
  32.           
  33.           
  34.         ch.writeAndFlush("客户端数据"+"\r\n");  
  35.           
  36.         logger.info("向Socket服务器发送数据:"+"客户端数据"+"\r\n");  
  37.     }  
  38.       
  39.     public static void  main(String[] args){  
  40.         logger.info("开始连接Socket服务器...");  
  41.         try {  
  42.             run();  
  43.         } catch (Exception e) {  
  44.             // TODO Auto-generated catch block  
  45.             e.printStackTrace();  
  46.         }finally{  
  47.             group.shutdownGracefully();  
  48.         }  
  49.     }  
private static final Log logger = LogFactory.getLog(LoginSocketClient.class);
	private static final String IP = "127.0.0.1";
	private static final int PORT = 8088;
	
	private static final EventLoopGroup group = new NioEventLoopGroup();
	
	@SuppressWarnings("rawtypes")
	protected static void  run() throws Exception {
		Bootstrap bootstrap = new Bootstrap();
		bootstrap.group(group);
		bootstrap.channel(NioSocketChannel.class);
		bootstrap.handler(new ChannelInitializer() {
			@Override
			protected void initChannel(Channel ch) throws Exception {
				// TODO Auto-generated method stub
				ChannelPipeline pipeline = ch.pipeline();
				/*
		         * 这个地方的 必须和服务端对应上。否则无法正常解码和编码
		         * 
		         * 解码和编码 我将会在下一张为大家详细的讲解。再次暂时不做详细的描述
		         * 
		         * */
		        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
		        pipeline.addLast("decoder", new StringDecoder());
		        pipeline.addLast("encoder", new StringEncoder());
				pipeline.addLast(new SocketClientHandler());
			}
		});
		
		// 连接服务端
        Channel ch = bootstrap.connect(IP,PORT).sync().channel();
        
        
        ch.writeAndFlush("客户端数据"+"\r\n");
		
		logger.info("向Socket服务器发送数据:"+"客户端数据"+"\r\n");
	}
	
	public static void  main(String[] args){
		logger.info("开始连接Socket服务器...");
		try {
			run();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			group.shutdownGracefully();
		}
	}
SocketClientHandler源码:

  1. public class SocketClientHandler extends SimpleChannelInboundHandler<String> {  
  2.     private static final Log logger = LogFactory.getLog(LoginSocketClient.class);  
  3.   
  4.     @Override  
  5.     public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) throws Exception {  
  6.         // TODO Auto-generated method stub  
  7.     }  
  8.   
  9.     @Override  
  10.     public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {  
  11.         // TODO Auto-generated method stub  
  12.         String data = msg.toString();  
  13.         logger.info("数据内容:data="+data);  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void channelRead0(ChannelHandlerContext arg0, String data) throws Exception {  
  18.         // TODO Auto-generated method stub  
  19.         logger.info("数据内容:data="+data);  
  20.     }  
  21. }  
public class SocketClientHandler extends SimpleChannelInboundHandler<String> {
	private static final Log logger = LogFactory.getLog(LoginSocketClient.class);

	@Override
	public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) throws Exception {
		// TODO Auto-generated method stub
	}

	@Override
	public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {
		// TODO Auto-generated method stub
		String data = msg.toString();
		logger.info("数据内容:data="+data);
	}

	@Override
	protected void channelRead0(ChannelHandlerContext arg0, String data) throws Exception {
		// TODO Auto-generated method stub
		logger.info("数据内容:data="+data);
	}
}

4.运行:

        运行方式其实很简单,直接在Eclipse中选中项目根目录,右键——Run As——Java Application,注意要先运行服务端工程再运行客户端工程,运行结果如下:


服务端Console结果:



客户端Console结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值