Java游戏服务器开发之二十四--在服务器中添加WebSocket的支持.md

Posted by lizhao on 07-09,2019

Java游戏服务器开发之二十四--在服务器中添加WebSocket的支持

[toc]

写在前面

对于只想了解基于netty的WebSocket协议可以看这篇,现在我们是在项目添加,会对其中的一些内容进行修改,

本次更新变化的内容

添加

  1. WebSocketChannelInitializer.java 服务初始化
  2. WebSocketHandler.java 消息处理类
  3. IMessageToWebSocketFrameEncoder 消息发出去的时候进行编码
  4. WebSocketFrameToIMessageDecoder 消息接收到时进行解码
  5. HttpResponseUtil.java 返回握手时的http请求消息
  6. WebSocketClientHandlerTest.java 客户端测试
  7. WebSocketClientTest.java 客户端消息处理

修改

  1. MessageDecoder.java 添加一个decodePub方法
  2. BasicServerImpl.java 根据不同的protocolType设置ChannelInitializer
  3. server-config-dev.properties 添加一个protocolType=WEBSOCKET

执行过程

  1. 服务器启动,启动过程中根据server-config-dev.properties中配置的protocolType创建不同的服务,这里是WEBSOCKET
  2. 配置WEBSOCKET对应的ChannelInitializer,这里是WebSocketChannelInitializer,在其中添加http协议的支持及编解码的过滤器,最后在之中添加消息处理器WebSocketHandler
  3. 在WebSocketHandler中消息接收的时候添加对HTTP请求和webSocket请求的处理
  4. 在客户端中进行模拟访问,WebSocketClientTest

代码中的关键

  1. IMessageToWebSocketFrameEncoder和WebSocketFrameToIMessageDecoder都是继承MessageToMessageEncoder
  2. 在WebSocketHandler中要针对不同的情况进行处理
if (msg instanceof FullHttpRequest) {
      // 传统的HTTP接入
      handleHttpMessage(ctx, msg);
    } else if (msg instanceof WebSocketFrame) {
      // WebSocket接入
      handleWebSocketMessage(ctx, msg);
    } else if (msg instanceof IMessage) {
      // 这里已经通过WebSocketFrameToIMessageDecoder进行解码,获得我们设置好的IMessage类了
      consumer.consume((IMessage) msg, ctx.channel());
    }
  1. 在模拟客户端的时候,WebSocketClientHandlerTest中要持有WebSocketClientHandshaker对象,用于发送http握手请求

具体代码

  1. WebSocketChannelInitializer.java 服务初始化
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketChannelInitializer
 * Author:   zhao
 * Date:     2018/8/10 11:42
 * Description: webSocket的channel
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.server.channel.websocket;

import com.lizhaoblog.base.message.codec.IMessageToWebSocketFrameEncoder;
import com.lizhaoblog.base.message.codec.WebSocketFrameToIMessageDecoder;
import com.lizhaoblog.server.pojo.ServerConfig;

import org.springframework.stereotype.Component;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * 〈一句话功能简述〉<br>
 * 〈webSocket的channel〉
 *
 * @author zhao
 * @date 2018/8/10 11:42
 * @since 1.0.1
 */
@Component
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {

  @Override
  protected void initChannel(SocketChannel ch) {
    ChannelPipeline pipeline = ch.pipeline();
    pipeline.addLast("http-codec", new HttpServerCodec()); // Http消息编码解码
    pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // Http消息组装
    pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // WebSocket通信支持
    // 消息编解码
    pipeline.addLast("encoder", new IMessageToWebSocketFrameEncoder());
    pipeline.addLast("decoder", new WebSocketFrameToIMessageDecoder());

    WebSocketHandler webSocketHandler = (WebSocketHandler) ServerConfig.getInstance().getApplicationContext()
            .getBean("webSocketHandler");
    pipeline.addLast(webSocketHandler);

  }

}

  1. WebSocketHandler.java 消息处理类
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketHandler
 * Author:   zhao
 * Date:     2018/8/10 11:44
 * Description: websocket的消息处理
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.server.channel.websocket;

import com.lizhaoblog.base.constant.ConstantValue;
import com.lizhaoblog.base.exception.MessageCodecException;
import com.lizhaoblog.base.message.IMessage;
import com.lizhaoblog.base.message.codec.MessageDecoder;
import com.lizhaoblog.base.message.impl.ByteMessage;
import com.lizhaoblog.base.message.impl.MessageFactory;
import com.lizhaoblog.base.network.customer.INetworkConsumer;
import com.lizhaoblog.base.network.listener.INetworkEventListener;
import com.lizhaoblog.base.session.SessionManager;
import com.lizhaoblog.base.util.HttpResponseUtil;
import com.lizhaoblog.server.pojo.ServerConfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;

/**
 * 〈一句话功能简述〉<br>
 * 〈websocket的消息处理〉
 *
 * @author zhao
 * @date 2018/8/10 11:44
 * @since 1.0.1
 */
@Component
@Scope("prototype")
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {
  @Autowired
  private INetworkEventListener listener;
  @Autowired
  private INetworkConsumer consumer;

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws MessageCodecException {
    if (msg instanceof FullHttpRequest) {
      // 传统的HTTP接入
      handleHttpMessage(ctx, msg);
    } else if (msg instanceof WebSocketFrame) {
      // WebSocket接入
      handleWebSocketMessage(ctx, msg);
    } else if (msg instanceof IMessage) {
      // 这里已经通过WebSocketFrameToIMessageDecoder进行解码,获得我们设置好的IMessage类了
      consumer.consume((IMessage) msg, ctx.channel());
    }
  }

  /**
   * 处理WebSocket中的Http消息
   *
   * @param ctx 上下文
   * @param msg 消息
   */
  private void handleHttpMessage(ChannelHandlerContext ctx, Object msg) {
    // 传统的HTTP接入
    FullHttpRequest request = (FullHttpRequest) msg;

    // 如果HTTP解码失败,返回HHTP异常
    if (!request.decoderResult().isSuccess() || (!"websocket".equals(request.headers().get("Upgrade")))) {
      HttpResponseUtil.sendHttpResponse(ctx, request,
              new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
      return;
    }

    // 正常WebSocket的Http连接请求,构造握手响应返回
    WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
            "ws://" + request.headers().get(HttpHeaderNames.HOST), null, false);
    WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
    if (handshaker == null) { // 无法处理的websocket版本
      WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
    } else { // 向客户端发送websocket握手,完成握手
      handshaker.handshake(ctx.channel(), request);
    }
  }

  /**
   * 处理WebSocket中的WebSocket消息
   *
   * @param ctx 上下文
   * @param msg 消息
   */
  private void handleWebSocketMessage(ChannelHandlerContext ctx, Object msg) throws MessageCodecException {
    ByteBuf content = ((WebSocketFrame) msg).content();
    MessageDecoder messageDecoder = new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
            ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,
            ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP, false,
            ServerConfig.getInstance().getMessageType());
    IMessage iMessage = messageDecoder.decodePub(ctx, content);
    // WebSocket接入
    consumer.consume(iMessage, ctx.channel());
  }

  @Override
  public void channelActive(ChannelHandlerContext ctx) {
    listener.onConnected(ctx);
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) {
    listener.onDisconnected(ctx);
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
    listener.onExceptionCaught(ctx, throwable);
  }

}

  1. IMessageToWebSocketFrameEncoder 消息发出去的时候进行编码
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketFrameEncoder
 * Author:   zhao
 * Date:     2018/8/13 17:12
 * Description: WebSocketFrameEncoder编码器
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.message.codec;

import com.lizhaoblog.base.message.IMessage;

import java.util.List;

import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;

/**
 * 〈一句话功能简述〉<br>
 * 〈WebSocketFrameEncoder编码器〉
 *
 * @author zhao
 * @date 2018/8/13 17:12
 * @since 1.0.1
 */
public class IMessageToWebSocketFrameEncoder extends MessageToMessageEncoder<IMessage> {
  @Override
  protected void encode(ChannelHandlerContext channelHandlerContext, IMessage iMessage, List<Object> list)
          throws Exception {
    //组合缓冲区
    CompositeByteBuf byteBuf = Unpooled.compositeBuffer();

    byte[] bodyBytes = iMessage.getBodyByte();
    byteBuf.writeShort(iMessage.getMessageId());
    byteBuf.writeShort(iMessage.getStatusCode());
    byteBuf.writeInt(bodyBytes.length);
    byteBuf.writeBytes(bodyBytes);

    list.add(new BinaryWebSocketFrame(byteBuf));
  }
}

  1. WebSocketFrameToIMessageDecoder 消息接收到时进行解码
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketFrameToIMessageDecoder
 * Author:   zhao
 * Date:     2018/8/13 19:25
 * Description: WebSocketFrame转换成IMessage
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.message.codec;

import com.lizhaoblog.base.constant.ConstantValue;
import com.lizhaoblog.base.message.IMessage;
import com.lizhaoblog.server.pojo.ServerConfig;

import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;

/**
 * 〈一句话功能简述〉<br>
 * 〈WebSocketFrame转换成IMessage〉
 *
 * @author zhao
 * @date 2018/8/13 19:25
 * @since 1.0.1
 */
public class WebSocketFrameToIMessageDecoder extends MessageToMessageDecoder<Object> {

  @Override
  protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
    if (msg instanceof FullHttpRequest) {
      // 传统的HTTP接入
      out.add(msg);
    } else if (msg instanceof WebSocketFrame) {
      // out.add(msg)

      // WebSocket接入
      ByteBuf content = ((WebSocketFrame) msg).content();
      MessageDecoder messageDecoder = new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
              ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,
              ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP, false,
              ServerConfig.getInstance().getMessageType());
      IMessage iMessage = messageDecoder.decodePub(ctx, content);
      out.add(iMessage);

    } else {
      out.add(msg);
    }

  }
}

  1. HttpResponseUtil.java 返回握手时的http请求消息
/*
 * Copyright (C), 2015-2018
 * FileName: HttpResponseUtil
 * Author:   zhao
 * Date:     2018/8/11 12:16
 * Description: 处理http的工具类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.base.util;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.CharsetUtil;

/**
 * 〈一句话功能简述〉<br>
 * 〈处理http的工具类〉
 *
 * @author zhao
 * @date 2018/8/11 12:16
 * @since 1.0.1
 */
public final class HttpResponseUtil {

  /**
   * Http返回
   *
   * @param ctx
   * @param request
   * @param response
   */
  public static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
    // 返回应答给客户端
    if (response.status().code() != 200) {
      ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
      response.content().writeBytes(buf);
      buf.release();
      HttpUtil.setContentLength(response, response.content().readableBytes());
    }

    // 如果是非Keep-Alive,关闭连接
    ChannelFuture f = ctx.channel().writeAndFlush(response);
    if (!HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
      f.addListener(ChannelFutureListener.CLOSE);
    }
  }

  private HttpResponseUtil() {
  }

}
  1. WebSocketClientHandlerTest.java 客户端测试
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketClientHandlerTest
 * Author:   zhao
 * Date:     2018/8/2 17:32
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.server.channel.websocket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.CharsetUtil;

/**
 * 〈一句话功能简述〉<br>
 * 〈〉
 *
 * @author zhao
 * @date 2018/8/2 17:32
 * @since 1.0.1
 */
public class WebSocketClientHandlerTest extends SimpleChannelInboundHandler<Object> {
  private static final Logger logger = LoggerFactory.getLogger(WebSocketClientHandlerTest.class);

  private final WebSocketClientHandshaker handshaker;
  private ChannelPromise handshakeFuture;

  public WebSocketClientHandlerTest(WebSocketClientHandshaker handshaker) {
    this.handshaker = handshaker;
  }

  public ChannelFuture handshakeFuture() {
    return handshakeFuture;
  }

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) {
    handshakeFuture = ctx.newPromise();
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable arg1) {
    logger.info("异常发生", arg1);
  }

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
//    logger.info("数据内容:data=" + data);

    Channel ch = ctx.channel();
    if (!handshaker.isHandshakeComplete()) {
      handshaker.finishHandshake(ch, (FullHttpResponse) msg);
      System.out.println("WebSocket Client connected!");
      handshakeFuture.setSuccess();
      return;
    }

    if (msg instanceof FullHttpResponse) {
      FullHttpResponse response = (FullHttpResponse) msg;
      throw new IllegalStateException(
              "Unexpected FullHttpResponse (getStatus=" + response.getStatus() +
                      ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
    }

    WebSocketFrame frame = (WebSocketFrame) msg;
    if (frame instanceof TextWebSocketFrame) {
      TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
      System.out.println("WebSocket Client received message: " + textFrame.text());
    } else if (frame instanceof PongWebSocketFrame) {
      System.out.println("WebSocket Client received pong");
    } else if (frame instanceof CloseWebSocketFrame) {
      System.out.println("WebSocket Client received closing");
      ch.close();
    }


    //关闭链路
    //    ctx.close();
    //    channelInactive(ctx);
  }

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    logger.info("客户端连接建立");
    // 在通道连接成功后发送握手连接
    handshaker.handshake(ctx.channel());
    super.channelActive(ctx);
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    logger.info("客户端连接断开");
    super.channelInactive(ctx);
  }
}
  1. WebSocketClientTest.java
/*
 * Copyright (C), 2015-2018
 * FileName: WebSocketClientTest
 * Author:   zhao
 * Date:     2018/8/2 17:32
 * Description: websocket测试
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package com.lizhaoblog.server.channel.websocket;

import com.lizhaoblog.base.message.codec.IMessageToWebSocketFrameEncoder;
import com.lizhaoblog.base.message.codec.WebSocketFrameToIMessageDecoder;
import com.lizhaoblog.base.message.impl.ByteMessage;
import com.lizhaoblog.common.CommonValue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;

/**
 * 〈一句话功能简述〉<br>
 * 〈websocket测试〉
 *
 * @author zhao
 * @date 2018/8/2 17:32
 * @since 1.0.1
 */
public class WebSocketClientTest {
  private static final Logger logger = LoggerFactory.getLogger(WebSocketClientTest.class);
  private static EventLoopGroup group = new NioEventLoopGroup();

  private static WebSocketClientHandlerTest handler;

  private String uriStr = "ws//" + CommonValue.IP + ":" + CommonValue.PORT;

  public void run() throws InterruptedException, URISyntaxException {
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(group);
    bootstrap.channel(NioSocketChannel.class);

    // 主要是为handler(自己写的类)服务
    URI wsUri = new URI(uriStr);
    WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory
            .newHandshaker(wsUri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), 100 * 1024 * 1024);
    handler = new WebSocketClientHandlerTest(webSocketClientHandshaker);

    bootstrap.handler(new ChannelInitializer() {
      @Override
      protected void initChannel(Channel ch) {

        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new HttpClientCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast("encoder", new IMessageToWebSocketFrameEncoder());
        pipeline.addLast("decoder", new WebSocketFrameToIMessageDecoder());

//        pipeline.addLast("encoder", new MessageEncoder());
//        pipeline.addLast("decoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
//                ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,
//                ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP,
//                false, ServerConfig.getInstance().getMessageType()));

        pipeline.addLast(handler);
      }
    });

    ByteMessage byteMessage = new ByteMessage();
    byteMessage.setMessageId(com.lizhaoblog.server.biz.constant.CommonValue.CM_MSG_TEST_BYTE);
    byteMessage.setStatusCode(com.lizhaoblog.server.biz.constant.CommonValue.MSG_STATUS_CODE_SUCCESS);
    byteMessage.addAttr(2);

    // 连接服务端
    //    ChannelFuture channelFuture = bootstrap.connect(CommonValue.IP, CommonValue.PORT).sync();
    ChannelFuture channelFuture = bootstrap.connect(CommonValue.IP, CommonValue.PORT).sync();
    handler.handshakeFuture().sync();
//    channelFuture.channel().writeAndFlush(byteMessage);
    //    WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
//        WebSocketFrame frame = new TextWebSocketFrame("aaa");
//        channelFuture.channel().writeAndFlush(frame);
//    byte[] bytes = new byte[14];
//    bytes[0] = 0;
//    bytes[1] = 1;
//    bytes[2] = 0;
//    bytes[3] = 1;
//    bytes[4] = 0;
//    bytes[5] = 0;
//    bytes[6] = 0;
//    bytes[7] = 6;
//    bytes[8] = 2;
//    bytes[9] = 0;
//    bytes[10] = 0;
//    bytes[11] = 0;
//    bytes[12] = 0;
//    bytes[13] = 1;
////    channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer(bytes));
//    BinaryWebSocketFrame frame = new BinaryWebSocketFrame(Unpooled.copiedBuffer(bytes));
    //    channelFuture.channel().writeAndFlush(frame);
        channelFuture.channel().writeAndFlush(byteMessage);

    logger.info("向Socket服务器发送数据:" + byteMessage);
    channelFuture.channel().closeFuture().sync();
  }
}

上面的代码在码云上 https://gitee.com/lizhaoandroid/JgServer 以加qq群一起探讨Java游戏服务器开发的相关知识 676231564