搭建netty服务器

版权声明:本文为博主原创文章,转载请附上原文链接,谢谢! https://blog.csdn.net/qq_21454973/article/details/81096514

首先,jar包。

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha2</version>
</dependency>

简单点,粗暴点,直接all。

然后Server端代码:

public class TPRSTCPServer implements Runnable{
	//间隔时间
	protected static int readerIdleTime = 10;
	
	private static Logger logger = Logger.getLogger(TPRSTCPServer.class);

	private static final int PORT = ;
	
	private TPRSTCPServer(){
		
	}
	private static TPRSTCPServer tcpServer = new TPRSTCPServer();
	public static TPRSTCPServer getInstance(){
		return tcpServer;
	}
	
	@Override
	public void run() {
		int port = PORT;
		try {
			new NettyServer(port).run();
			System.out.println("server:run()");
		} catch (Exception e) {
			logger.error(e);
		}
		
	}
	
}
class NettyServer {
	private int port;

	public NettyServer(int port) {
		super();
		this.port = port;
	}

	public void run() throws Exception {

		/***
		 * NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
		 * Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。 在这个例子中我们实现了一个服务端的应用,
		 * 因此会有2个NioEventLoopGroup会被使用。 第一个经常被叫做‘boss’,用来接收进来的连接。
		 * 第二个经常被叫做‘worker’,用来处理已经被接收的连接, 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
		 * 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
		 * 并且可以通过构造函数来配置他们的关系。
		 */
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		System.out.println("运行端口:" + port);
		try {
			/**
			 * ServerBootstrap 是一个启动NIO服务的辅助启动类 你可以在这个服务中直接使用Channel
			 */
			ServerBootstrap b = new ServerBootstrap();
			/**
			 * 这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException: group not
			 * set异常
			 */
			b = b.group(bossGroup, workerGroup);
			/***
			 * ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
			 * 这里告诉Channel如何获取新的连接.
			 */
			b = b.channel(NioServerSocketChannel.class);
			/***
			 * 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。 ChannelInitializer是一个特殊的处理类,
			 * 他的目的是帮助使用者配置一个新的Channel。
			 * 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
			 * 或者其对应的ChannelPipeline来实现你的网络程序。 当你的程序变的复杂时,可能你会增加更多的处理类到pipline上,
			 * 然后提取这些匿名类到最顶层的类上。
			 */
			b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
				@Override
				public void initChannel(SocketChannel ch) throws Exception {
					
					InetSocketAddress insocket = ch.remoteAddress();
					String ip = insocket.getAddress().getHostAddress();
					System.out.println(ip+" is connect");
					
					
					TPRSGatewayService.addGatewayChannel(ip, ch.pipeline());
					//ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
					ch.pipeline().addLast(new IdleStateHandler(TPRSTCPServer.readerIdleTime, 0, 0, TimeUnit.SECONDS));
					//ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
					ch.pipeline().addLast(new TPRSDiscardServerHandler());// demo1.discard
					
					// ch.pipeline().addLast(new
					// ResponseServerHandler());//demo2.echo
					// ch.pipeline().addLast(new
					// TimeServerHandler());//demo3.time
				}
			});
			/***
			 * 你可以设置这里指定的通道实现的配置参数。 我们正在写一个TCP/IP的服务端,
			 * 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
			 * 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
			 */
			b = b.option(ChannelOption.SO_BACKLOG, 128);
			/***
			 * option()是提供给NioServerSocketChannel用来接收进来的连接。
			 * childOption()是提供给由父管道ServerChannel接收到的连接,
			 * 在这个例子中也是NioServerSocketChannel。
			 */
			b = b.childOption(ChannelOption.SO_KEEPALIVE, true);
			/***
			 * 绑定端口并启动去接收进来的连接
			 */
			ChannelFuture f = b.bind(port).sync();
			/**
			 * 这里会一直等待,直到socket被关闭
			 */
			f.channel().closeFuture().sync();
		} finally {
			/***
			 * 关闭
			 */
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

	
}

readerIdleTime理解为心跳间隔时间。端口自行设置,其他已有注解。

handler类,消息处理:

public class TPRSDiscardServerHandler extends ChannelHandlerAdapter {
	
	static Logger logger = Logger.getLogger(TPRSDiscardServerHandler.class);
	private static List<String> program1List = new ArrayList<String>();
    private static List<String> program2List = new ArrayList<String>();
	
	private int lossConnectCount = 0;
	private int outTimes = 10;
	/**
	 * 心跳,时间设置在IdleStateHandler中
	 * 总的时间 lossConnectCount*time 超时则关闭通道
	 */
	@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = insocket.getAddress().getHostAddress();
        if (evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent)evt;
            if (event.state()== IdleState.READER_IDLE){
                lossConnectCount++;
                if (lossConnectCount>outTimes){
                	logger.info("关闭"+ip+"不活跃通道!");
                    close(ctx, null);
                    
                }
            }
        }else {
            super.userEventTriggered(ctx,evt);
        }
    }
	
	/**
	 * 这里我们覆盖了chanelRead()事件处理方法。 每当从客户端收到新的数据时, 这个方法会在收到消息时被调用,
	 * 这个例子中,收到的消息的类型是ByteBuf
	 * 
	 * @param ctx
	 *            通道处理的上下文信息
	 * @param msg
	 *            接收的消息
	 * @throws UnsupportedEncodingException 
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
		//心跳复位,你也可以放入parseHelper中复位,这里认为一旦收到消息就算一次心跳
		lossConnectCount = 0;
		
		try {
			ByteBuf buf = (ByteBuf)msg;  
			
            InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
			String ip = insocket.getAddress().getHostAddress();
			//新连接存入map
			ChannelPipeline sc = TPRSGatewayService.getGatewayChannel(ip);
			if(sc == null){
				TPRSGatewayService.addGatewayChannel(ip, ctx.pipeline());
			}
			
			try {
				byte[] result1 = new byte[buf.readableBytes()];
				buf.readBytes(result1);
				 ByteArrayInputStream is = new ByteArrayInputStream(result1);
		         BufferedReader br = new BufferedReader(new InputStreamReader(is, "GBK"));
		         String aline = "";
		         while ((aline = br.readLine()) != null) {
		        	 parseHelper(aline, ip);
		         }
		         
			} catch (Exception e) {
				logger.error(e);
			}
			
		} finally {
			/**
			 * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
			 * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
			 */
			// 抛弃收到的数据
			ReferenceCountUtil.release(msg);
		}

	}

	/***
	 * 这个方法会在发生异常时触发
	 * 
	 * @param ctx
	 * @param cause
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		/**
		 * exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO
		 * 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来 并且把关联的 channel
		 * 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不 同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
		 */
		// 出现异常就关闭
		InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = insocket.getAddress().getHostAddress();
		logger.info(ip+" 出现异常!");
		cause.printStackTrace();
		close(ctx, null);
	}
	
	@Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    //channel失效处理,客户端下线或者强制退出等任何情况都触发这个方法
		InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = insocket.getAddress().getHostAddress();
		logger.info(ip+" 出现异常!");
        super.channelInactive(ctx);
        close(ctx, null);
    }
	@Override
	public void close(ChannelHandlerContext ctx, ChannelPromise promise)
	throws Exception {
		InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = insocket.getAddress().getHostAddress();
		logger.info(ip+" close.....");
		TPRSGatewayService.removeGatewayChannel(ip);
		ctx.close();
	}
	
	private void parseHelper(String msg,String ip) throws Exception {
		System.out.println("接收的消息:"+msg);
        //根据规则处理消息。。。。
	}
	
}

然后是保存channel的辅助类:

public class TPRSGatewayService {
	private static Logger logger = Logger.getLogger(TPRSGatewayService.class);
	private static Map<String, ChannelPipeline> map = new ConcurrentHashMap<>();
	
	
	
	
	public static void addGatewayChannel(String ip, ChannelPipeline channelPipeline) {
		map.put(ip, channelPipeline);
	}
	
	

	public static Map<String, ChannelPipeline> getChannels() {
		return map;
	}
	
	

	public static ChannelPipeline getGatewayChannel(String ip) {
		try {
			return map.get(ip);
		} catch (Exception e) {
			logger.error(e);
			return null;
		}
	}
	
	

	public static void removeGatewayChannel(String ip) {
		try {
			System.out.println(ip+"移除");
			map.remove(ip);
			
		} catch (Exception e) {
			logger.error(e);
		}
	}
	
	
	
	public static boolean sendToClient(String msg,String ip){
		ChannelPipeline ch = map.get(ip);
		if(ch!=null){
			
			try {
				logger.info(ip+" : "+msg);
				byte[] bytes = msg.getBytes("gbk");
				ch.writeAndFlush(Unpooled.copiedBuffer(bytes));
				return true;
			} catch (Exception e) {
				map.remove(ip);
				return false;
			}
		}else{
			System.out.println(" 该 链 接 不 存 在 !");

		}
		return false;
	}
	
	
}

byte[] bytes = msg.getBytes("gbk");这里根据终端能接收的字符集类型设定,本文中原项目类型是utf8,但是终端是只能识别gbk,所以要getBytes("gbk")。

然后就可以测试了:

// 将规则跑起来
		public static void main(String[] args) {
			TPRSTCPServer server = TPRSTCPServer.getInstance();
			Thread t = new Thread(server);// 创建客户端处理线程
			t.start();// 启动线程
			Scanner scan = new Scanner(System.in);
			while (true) {
				System.out.println("输入操作:");
				String op = scan.nextLine();
				if ("1".equals(op)) {
					for (String key : TPRSGatewayService.getChannels().keySet()) {
						System.out.println("key = " + key);
					}
				} else{
					System.out.println("输入地址");
					String ClientIp = scan.nextLine();
					System.out.println("输入地址是:" + ClientIp);
//				    String ClientIp = "192.168.1.135";
					System.out.println("输入消息");
					String message = scan.nextLine();
					System.out.println("输入消息:" + message);

					boolean s = TPRSGatewayService.sendToClient(message, ClientIp);
					System.out.println("消息发送状态:" + s);
				}

			
			}
			
		}

累了困了写篇博客,提神醒脑。

猜你喜欢

转载自blog.csdn.net/qq_21454973/article/details/81096514
今日推荐