netty websocket

1:ChannelInitializer设置支持websocket  (/ws)

     

public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel>{

	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
		pipeline.addLast("idleState", new IdleStateHandler(10, 0, 0, TimeUnit.MINUTES));

		pipeline.addLast(new HttpServerCodec());
		pipeline.addLast(new HttpObjectAggregator(64 * 1024));

		pipeline.addLast(new WebSocketHandler());
		pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
	}

}

   2:WebSocketServer设置childHandler

  

public class WebSocketServer {

	public static AttributeKey<Integer> CLIENT_KEY = AttributeKey.valueOf("CLIENT_KEY");// // 玩家唯一标示
	
	private int serverId;
	private int port;
	private ChannelFuture channelFuture;
	
	public WebSocketServer(int serverId, int port) {
		this.serverId = serverId;
		this.port = port;
	}
	
	public void start() throws Exception {
		boolean isEpoll = Epoll.isAvailable();
		int cpuNum = Runtime.getRuntime().availableProcessors();

		EventLoopGroup bossGroup = null;
		EventLoopGroup workerGroup = null;
		if (isEpoll) {
			bossGroup = new EpollEventLoopGroup(cpuNum);
			workerGroup = new EpollEventLoopGroup(cpuNum * 2 + 1);
		} else {
			bossGroup = new NioEventLoopGroup(cpuNum);
			workerGroup = new NioEventLoopGroup(cpuNum * 2 + 1);
		}

		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup);
			b.channel(isEpoll ? EpollServerSocketChannel.class : NioServerSocketChannel.class);
			b.childHandler(new WebSocketServerInitializer());

			// TIME_WAIT时可重用端口,服务器关闭后可立即重启,此时任何非期
			// 望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能
			b.option(ChannelOption.SO_REUSEADDR, true);
			// 设置了ServerSocket类的SO_RCVBUF选项,就相当于设置了Socket对象的接收缓冲区大小,4KB
			b.option(ChannelOption.SO_RCVBUF, 1024 * 64);
			// 请求连接的最大队列长度,如果backlog参数的值大于操作系统限定的队列的最大长度,那么backlog参数无效
			b.option(ChannelOption.SO_BACKLOG, 1024);
			// 使用内存池的缓冲区重用机制
			b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

			// 当客户端发生断网或断电等非正常断开的现象,如果服务器没有设置SO_KEEPALIVE选项,则会一直不关闭SOCKET。具体的时间由OS配置
			b.childOption(ChannelOption.SO_KEEPALIVE, true);
			// 在调用close方法后,将阻塞n秒,让未完成发送的数据尽量发出,netty中这部分操作调用方法异步进行。我们的游戏业务没有这种需要,所以设置为0
			b.childOption(ChannelOption.SO_LINGER, 0);
			// 数据包不缓冲,立即发出
			b.childOption(ChannelOption.TCP_NODELAY, true);
			// 发送缓冲大小,默认8192
			b.childOption(ChannelOption.SO_SNDBUF, 1024 * 64);
			// 使用内存池的缓冲区重用机制
			b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

			b.childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 1024 * 128);
			b.childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 1024 * 64);

			System.out.println("GatewayServer" + serverId + " 启动了 port = " + port + ", isEpoll = " + isEpoll);

			// 绑定端口,开始接收进来的连接
			channelFuture = b.bind(port).sync();

			// 等待服务器 socket 关闭
			channelFuture.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();

			System.out.println("GameServer 关闭了 port = " + port);
		}
	}

	public void stop() {
		try {
			channelFuture.channel().close().sync();
		} catch (Exception e) {
			System.out.println("Websocket close error");
		}
	}
	
	public static void main(String[] args)
	{
		try {
			new WebSocketServer(1,9400).start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

   3:WebSocketHandler

    第一次为http请求,之后升级为websocket协议

    

public class WebSocketHandler extends SimpleChannelInboundHandler<Object>{

	private WebSocketServerHandshaker handshaker;

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
		if (msg instanceof FullHttpRequest) {
			processHttpRequest(ctx, (FullHttpRequest) msg);
		} else if (msg instanceof BinaryWebSocketFrame) {
			processWebSocketRequest(ctx, (BinaryWebSocketFrame) msg);
		} else if (msg instanceof CloseWebSocketFrame) {
			processCloseWebSocketRequest(ctx, (CloseWebSocketFrame) msg);
		}
	}
	
	protected void processWebSocketRequest(ChannelHandlerContext ctx, BinaryWebSocketFrame frame) {
		PBMessage packet = new PBMessage();
		packet.read(frame.content());
		
		PlayerDeadMsg req ;
		try {
			req = PlayerDeadMsg.parseFrom(packet.toByteArray());
			
			System.out.println("getDeadUserId=="+req.getDeadUserId());
			System.out.println("KillUserId=="+req.getKillUserId());
			
		} catch (InvalidProtocolBufferException e) {
			e.printStackTrace();
		}
	}
	
	protected void processCloseWebSocketRequest(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
		Integer userId = (Integer) ctx.channel().attr(WebSocketServer.CLIENT_KEY).get();
		System.out.println("WebSocket close webSocket, userId = " + userId);
	}
	
	protected void processHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
		if (!HttpMethod.GET.equals(request.getMethod()) || !"websocket".equalsIgnoreCase(request.headers().get("Upgrade"))) {
			DefaultHttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
			ctx.channel().write(resp);
			ctx.channel().close();
			return;
		}

		WebSocketServerHandshakerFactory wsShakerFactory = new WebSocketServerHandshakerFactory("ws://" + request.headers().get(HttpHeaders.Names.HOST), null, false);
		handshaker = wsShakerFactory.newHandshaker(request);
		if (handshaker == null) {
			WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
		} else {
			handshaker.handshake(ctx.channel(), request);
		}
	}

}

Client测试代码

public class Client implements Runnable {

	private int userId;
	private Channel client;
	private int count;

	public Client(int userId) throws Exception {
		this.userId = userId;
	}

	public void init() throws Exception {
		URI uri = new URI(WebSocketClient.URL);
		String scheme = uri.getScheme() == null ? "ws" : uri.getScheme();
		final String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
		final int port;
		if (uri.getPort() == -1) {
			if ("ws".equalsIgnoreCase(scheme)) {
				port = 80;
			} else if ("wss".equalsIgnoreCase(scheme)) {
				port = 443;
			} else {
				port = -1;
			}
		} else {
			port = uri.getPort();
		}

		if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
			System.err.println("Only WS(S) is supported.");
			return;
		}

		final boolean ssl = "wss".equalsIgnoreCase(scheme);
		final SslContext sslCtx;
		if (ssl) {
			sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
		} else {
			sslCtx = null;
		}

		EventLoopGroup group = new NioEventLoopGroup();
		try {
			// Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
			// If you change it to V00, ping is not supported and remember to change
			// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
			final WebSocketClientHandler handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()));

			Bootstrap b = new Bootstrap();
			b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel ch) {
					ChannelPipeline p = ch.pipeline();
					if (sslCtx != null) {
						p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
					}
					p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), handler);
				}
			});

			Channel ch = b.connect(uri.getHost(), port).sync().channel();
			handler.handshakeFuture().sync();

			client = ch;

			while (true) {
				if (count > 3000) {
					ch.writeAndFlush(new CloseWebSocketFrame());
					ch.close().sync();
					break;
				}

				if (client == null || !client.isActive()) {
					ch.writeAndFlush(new CloseWebSocketFrame());
					ch.close().sync();
					break;
				}

				loginGame();

				count++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			group.shutdownGracefully();
		}
	}

	@Override
	public void run() {
		while (true) {
			if (client == null) {
				try {
					init();
				} catch (Exception e) {
					System.out.println(e);
				}
			} else if (!client.isActive()) {
				break;
			}
		}
	}

	public void loginGame() {
		PlayerDeadMsg.Builder msg = PlayerDeadMsg.newBuilder();
		msg.setDeadUserId(100);
		msg.setKillUserId(200);
		
		PBMessage message = MessageUtil.buildMessage((short)100, msg);
		sendPacket(message);

		try {
			Thread.sleep(5000);
		} catch (Exception e) {
		}
	}

	public void close() {
		try {
			if (client != null) {
				client.writeAndFlush(new CloseWebSocketFrame());
				client.close().sync();
			}
		} catch (InterruptedException e) {
			System.out.println(e);
		}
	}

	public void sendPacket(PBMessage message) {
		if (!client.isActive()) {
			close();
			return;
		}
		message.setPlayerId(userId);
		int size = message.getMessage().getSerializedSize() + 10;
		ByteBuf respBuffer = Unpooled.buffer(size);
		message.writeHeader(size, respBuffer);

		BinaryWebSocketFrame resp = new BinaryWebSocketFrame(respBuffer);
		client.writeAndFlush(resp);
	}
}

public class WebSocketClient {

	public static final String URL = System.getProperty("url", "ws://127.0.0.1:9400/");

	public static void main(String[] args) throws Exception {
		int clients = 1;
		int userId = 100;

		Client client = null;
		Map<Integer, Client> clientMap = new HashMap<>();
		for (int i = 0; i < clients; i++) {
			client = new Client(userId);
			clientMap.put(userId, client);
			Thread thread = new Thread(client);
			thread.start();

			Thread.sleep(2000);
			userId++;
		}

		System.out.println("fdsafas");
	}

}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值