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");
}
}