Netty框架实现简单的组播

        在游戏中会经常遇到这一种情况,世界/阵营频道聊天,或者某个玩家获得了特别NB的物品,或者某个活动已经开始,都需要通知某个组或者所有的客户端,这些就需要网络编程中的组播/多播。其实就是类似与设计模式中的发布-订阅模式。Netty框架中的ChannelGroup接口提供了一些方法来帮我们实现需求。老样子,我们直接使用简单的代码例子说明。为了简便,我们实现最简单世界聊天,原理就是一个玩家将聊天信息发送给服务器,服务器将消息转发给所有链接的客户端。


       消息的传输继续使用protobuf协议,Netty框架使用4.0版本。关于Netty和protobuf的使用前面的文章有详细介绍。

      首先定义pb文件,ChatRequest.proto:

package message;
option java_package = "message.request";
option java_outer_classname = "ChatRequest";


message Command {
	
	optional int32 chatType = 1;
	
	optional string content = 2;
}

      ChatResponse.proto和ChatRequest.proto的定义是一样的。ChatResponse.proto:

package message;
option java_package = "message.response";
option java_outer_classname = "ChatResponse";


message ChatMessage {
	
	optional int32 chatType = 1;
	
	optional string content = 2;
}

        将java文件生成到项目TestNettyTcpMulticast中的src目中下。

server端:NettyTcpMulticastServer.java

package server;

import message.request.ChatRequest;
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;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;


public class NettyTcpMulticastServer {

	public static void main(String[] args) throws Exception {
	
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);
		
		try {
			ServerBootstrap bs = new ServerBootstrap();
			bs.group(bossGroup, workGroup)
				.channel(NioServerSocketChannel.class)
				.childHandler(new ChannelInitializer() {
					
					@Override
					public void initChannel(SocketChannel ch) throws Exception {
						
						ProtobufDecoder protobufDecoder = new ProtobufDecoder(ChatRequest.Command.getDefaultInstance());
						
						//Decoder
						ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(2 * 1024 * 1024, 0, 4, 0, 4));
						ch.pipeline().addLast("protobufDecoder", protobufDecoder);
						
						//Encoder
						ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
						ch.pipeline().addLast("protobufEncoder", new ProtobufEncoder());
						
					
						//handler
						ch.pipeline().addLast(new SocketHandler());
					}
				})
				
				.option(ChannelOption.SO_BACKLOG, 128)
				.childOption(ChannelOption.SO_KEEPALIVE, true);
			
			//Bind and start to accept incoming connections
			ChannelFuture cf = bs.bind(8080).sync();
			
			cf.channel().closeFuture().sync();
		} finally {
			workGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}	
}

server逻辑处理,SockerHandler.java

package server;

import message.request.ChatRequest;
import message.response.ChatResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

public class SocketHandler extends ChannelInboundHandlerAdapter {

	//channels保存所有链接进来的channel
	private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
	
	
	//将链接的channel加到channels中
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		channels.add(ctx.channel());
		super.channelActive(ctx);
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		
		ChatRequest.Command command = (ChatRequest.Command)msg;
		
		ChatResponse.ChatMessage.Builder build = ChatResponse.ChatMessage.newBuilder();
		build.setChatType(command.getChatType())
			 .setContent(command.getContent() + ",已经经过服务器转发");
		
		ChatResponse.ChatMessage chatMessage = build.build();
		
		//将消息发送给所有链接的客户端
		channels.writeAndFlush(chatMessage);
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}


       Netty中的每一个socket链接就是一个channel,只需要将所有链接的客户端channel加到ChannelGroup对象channels中去,只需在逻辑处理中调用channels对象的writeAndFlush方法将消息发送出去就行了。关于ChannelGroup的具体用法查看Netty官方文档即可。

        这里我们弄两个客户端,一个发聊天内容,经过服务器后将内容转给所有连接的客户端。这是NettyTcpMulitcastSendReceiveClient.java

package client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import message.request.ChatRequest;
import message.response.ChatResponse;


public class NettyTcpMulitcastSendReceiveClient {

	public static void main(String[] args) throws IOException {

		Socket socket = null;  
		DataOutputStream dataOut = null;
		DataInputStream dataIn = null;
		
        try {  
  
        	socket = new Socket("localhost", 8080);  
        	
        	dataOut = new DataOutputStream(socket.getOutputStream());
        	dataIn = new DataInputStream(socket.getInputStream());
             
            ChatRequest.Command.Builder requestBuiler = ChatRequest.Command.newBuilder();
            requestBuiler.setChatType(0)
            			 .setContent("我是发送方");
            ChatRequest.Command Command = requestBuiler.build();
            
            byte[] dataOutBytes = Command.toByteArray();  
            
            //先发送header
            dataOut.writeInt(dataOutBytes.length);
            
            //再发送body
            dataOut.write(dataOutBytes);
            dataOut.flush();
            
           
            int bodyLength = dataIn.readInt();
            byte[] bodyBytes = new byte[bodyLength];
            dataIn.read(bodyBytes);
            
            //将字节流转成ChatResponse.ChatMessage类型
            ChatResponse.ChatMessage chatResponse = ChatResponse.ChatMessage.parseFrom(bodyBytes);
            
            System.out.println("chatType: " + chatResponse.getChatType());
            System.out.println("chatContent: " + chatResponse.getContent());
 
        } finally {  
            // 关闭连接 
        	dataIn.close();
        	dataOut.close();  
            socket.close();  
        }  
	}
}

     这是另一个客户端只是单纯的接收聊天信息。NettyTcpMulitcastReceiveClient.java
package client;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

import message.response.ChatResponse;


public class NettyTcpMulitcastReceiveClient {
	
	public static void main(String[] args) throws IOException {
		
		Socket socket = null;  
		DataInputStream dataIn = null;
		
        try {  
  
        	socket = new Socket("localhost", 8080);    
        	dataIn = new DataInputStream(socket.getInputStream());
             
            int bodyLength = dataIn.readInt();
            byte[] bodyBytes = new byte[bodyLength];
            dataIn.read(bodyBytes);
            
            //将字节流转成ChatResponse.ChatMessage类型
            ChatResponse.ChatMessage chatResponse = ChatResponse.ChatMessage.parseFrom(bodyBytes);
            
            int chatType = chatResponse.getChatType();
            String chatContent = chatResponse.getContent();
            
            System.out.println("chatType: " + chatType);
            System.out.println("chatContent: " + chatContent);
 
        } finally {  
            // 关闭连接 
        	dataIn.close();
            socket.close();  
        }
	}

}
        例子是相当简单的。可以测试一下程序,首先启动服务端程序,然后先启动NettyTcpMulitcastReceiveClient.java他会阻塞在socket的read处,再启动NettyTcpMulitcastSendReceiveClient发送聊天内容。就可以看到两个客户程序都接收到了聊天信息。

你可能感兴趣的:(Java)