基于Netty+WebSocket的网页聊天简单实现

一、pom依赖

<dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.6.Final</version>
        </dependency>

二、文件目录

三、服务端代码

WebSocketService

package com.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.WebSocketFrame; public interface WebSocketService { void handleFrame(ChannelHandlerContext ctx,WebSocketFrame frame); }

HttpService

package com.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest; public interface HttpService { void handleHttpRequset(ChannelHandlerContext ctx,FullHttpRequest request);
}

WebSocketServerHandler

package com.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.WebSocketFrame; public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{ private WebSocketService webSocketServiceImpl; private HttpService httpServiceImpl; public WebSocketServerHandler(WebSocketService webSocketServiceImpl,
HttpService httpServiceImpl) {
super();
this.webSocketServiceImpl = webSocketServiceImpl;
this.httpServiceImpl = httpServiceImpl;
} @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
// TODO Auto-generated method stub
if(msg instanceof FullHttpRequest){ httpServiceImpl.handleHttpRequset(ctx, (FullHttpRequest)msg);
}else if(msg instanceof WebSocketFrame){ webSocketServiceImpl.handleFrame(ctx, (WebSocketFrame)msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
ctx.flush();
} }

WebSocketServerImpl

package com.netty;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
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.NioServerSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.AttributeKey; public class WebSocketServerImpl implements WebSocketService, HttpService{ private static final String HN_HTTP_CODEC = "HN_HTTP_CODEC";
private static final String NH_HTTP_AGGREGATOR ="NH_HTTP_AGGREGATOR";
private static final String NH_HTTP_CHUNK = "HN_HTTP_CHUNK";
private static final String NH_SERVER = "NH_LOGIC"; private static final AttributeKey<WebSocketServerHandshaker> ATTR_HANDSHAKER = AttributeKey.newInstance("ATTR_KEY_CHANNELID"); private static final int MAX_CONTENT_LENGTH = 65536; private static final String WEBSOCKET_UPGRADE = "websocket"; private static final String WEBSOCKET_CONNECTION = "Upgrade"; private static final String WEBSOCKET_URI_ROOT_PATTERN = "ws://%s:%d"; //地址
private String host; //端口号
private int port; //存放websocket连接
private Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<ChannelId, Channel>(); private final String WEBSOCKET_URI_ROOT; public WebSocketServerImpl(String host, int port) {
super();
this.host = host;
this.port = port;
WEBSOCKET_URI_ROOT = String.format(WEBSOCKET_URI_ROOT_PATTERN, host, port);
} //启动
public void start(){ EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup);
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() { @Override
protected void initChannel(Channel ch) throws Exception {
// TODO Auto-generated method stub
ChannelPipeline pl = ch.pipeline();
//保存引用
channelMap.put(ch.id(), ch);
ch.closeFuture().addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
//关闭后抛弃
channelMap.remove(future.channel().id());
}
}); pl.addLast(HN_HTTP_CODEC,new HttpServerCodec());
pl.addLast(NH_HTTP_AGGREGATOR,new HttpObjectAggregator(MAX_CONTENT_LENGTH));
pl.addLast(NH_HTTP_CHUNK,new ChunkedWriteHandler());
pl.addLast(NH_SERVER,new WebSocketServerHandler(WebSocketServerImpl.this,WebSocketServerImpl.this)); } }); try {
ChannelFuture future = sb.bind(host,port).addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
if(future.isSuccess()){ System.out.println("websocket started");
}
}
}).sync(); future.channel().closeFuture().addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
System.out.println("channel is closed");
}
}).sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{ bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} System.out.println("websocket shutdown");
} @Override
public void handleHttpRequset(ChannelHandlerContext ctx,
FullHttpRequest request) {
// TODO Auto-generated method stub if(isWebSocketUpgrade(request)){ String subProtocols = request.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(WEBSOCKET_URI_ROOT, subProtocols, false); WebSocketServerHandshaker handshaker = factory.newHandshaker(request); if(handshaker == null){ WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); }else{
//响应请求
handshaker.handshake(ctx.channel(), request);
//将handshaker绑定给channel
ctx.channel().attr(ATTR_HANDSHAKER).set(handshaker);
}
return;
} } @Override
public void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// TODO Auto-generated method stub if(frame instanceof TextWebSocketFrame){ String text = ((TextWebSocketFrame) frame).text();
TextWebSocketFrame rsp = new TextWebSocketFrame(text); for(Channel ch:channelMap.values()){ if(ctx.channel().equals(ch)){ continue;
}
ch.writeAndFlush(rsp); } return;
} //ping 回复 pong
if(frame instanceof PingWebSocketFrame){ ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
return; } if(frame instanceof PongWebSocketFrame){ return;
} if(frame instanceof CloseWebSocketFrame){ WebSocketServerHandshaker handshaker = ctx.channel().attr(ATTR_HANDSHAKER).get(); if(handshaker == null){ return;
}
handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
return;
} } //1、判断是否为get 2、判断Upgrade头 包含websocket字符串 3、Connection头 包换upgrade字符串
private boolean isWebSocketUpgrade(FullHttpRequest request){ HttpHeaders headers = request.headers(); return request.method().equals(HttpMethod.GET)
&& headers.get(HttpHeaderNames.UPGRADE).contains(WEBSOCKET_UPGRADE)
&& headers.get(HttpHeaderNames.CONNECTION).contains(WEBSOCKET_CONNECTION); }
}

DoMain

package com.netty;

public class DoMain {

    public static void main(String[] args) {
WebSocketServerImpl socket = new WebSocketServerImpl("localhost", 9999);
socket.start();
}
}

四、服务端代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
</head>
</head>
<script type="text/javascript">
var socket; if(!window.WebSocket){
window.WebSocket = window.MozWebSocket;
} if(window.WebSocket){
socket = new WebSocket("ws://localhost:9999"); socket.onmessage = function(event){
appendln("receive:" + event.data);
}; socket.onopen = function(event){
appendln("WebSocket is opened"); }; socket.onclose = function(event){
appendln("WebSocket is closed");
};
}else{
alert("WebSocket is not support");
} function send(message){
if(!window.WebSocket){return;}
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
appendln("send:" + message);
}else{
alert("WebSocket is failed");
} } function appendln(text) {
var ta = document.getElementById('responseText');
ta.value += text + "\r\n";
} function clear() {
var ta = document.getElementById('responseText');
ta.value = "";
} </script>
<body>
<form onSubmit="return false;">
<input type = "text" name="message" value="hello"/>
<br/><br/>
<input type="button" value="send" onClick="send(this.form.message.value)"/>
<hr/>
<h3>chat</h3>
<textarea id="responseText" style="width: 800px;height: 300px;"></textarea>
</form>
</body>
</html>

五、结果

六、实际使用阶段、当出现3个以上客户端时回报错,io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1

查找资料后发现是writeAndFlush方法里面有个计数器,导致异常。解决方法:

WebSocketServerImpl不使用 private Map<ChannelId, Channel> channelMap 存放连接,使用netty提供的ChannelGroup存放连接

创建变量 private ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

保存引用由 channelMap.put(ch.id(), ch); 改为 group.add(ch);

关闭由 channelMap.remove(future.channel().id());  改为group.remove(ch);

发送 handleFrame方法 改为

String text = ((TextWebSocketFrame) frame).text();

TextWebSocketFrame rsp = new TextWebSocketFrame(text);

group.writeAndFlush(rsp);

WebSocketServerImpl完整代码:

package com.netty;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor; public class WebSocketServerImpl implements WebSocketService, HttpService{ private static final String HN_HTTP_CODEC = "HN_HTTP_CODEC";
private static final String NH_HTTP_AGGREGATOR ="NH_HTTP_AGGREGATOR";
private static final String NH_HTTP_CHUNK = "HN_HTTP_CHUNK";
private static final String NH_SERVER = "NH_LOGIC"; private static final AttributeKey<WebSocketServerHandshaker> ATTR_HANDSHAKER = AttributeKey.newInstance("ATTR_KEY_CHANNELID"); private static final int MAX_CONTENT_LENGTH = ; private static final String WEBSOCKET_UPGRADE = "websocket"; private static final String WEBSOCKET_CONNECTION = "Upgrade"; private static final String WEBSOCKET_URI_ROOT_PATTERN = "ws://%s:%d"; //地址
private String host; //端口号
private int port; //存放websocket连接
private Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<ChannelId, Channel>();
private ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); private final String WEBSOCKET_URI_ROOT; public WebSocketServerImpl(String host, int port) {
super();
this.host = host;
this.port = port;
WEBSOCKET_URI_ROOT = String.format(WEBSOCKET_URI_ROOT_PATTERN, host, port);
} //启动
public void start(){ EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup);
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() { @Override
protected void initChannel(Channel ch) throws Exception {
// TODO Auto-generated method stub
ChannelPipeline pl = ch.pipeline();
//保存引用
channelMap.put(ch.id(), ch);
group.add(ch);
ch.closeFuture().addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
//关闭后抛弃
channelMap.remove(future.channel().id());
group.remove(ch);
}
}); pl.addLast(HN_HTTP_CODEC,new HttpServerCodec());
pl.addLast(NH_HTTP_AGGREGATOR,new HttpObjectAggregator(MAX_CONTENT_LENGTH));
pl.addLast(NH_HTTP_CHUNK,new ChunkedWriteHandler());
pl.addLast(NH_SERVER,new WebSocketServerHandler(WebSocketServerImpl.this,WebSocketServerImpl.this)); } }); try {
ChannelFuture future = sb.bind(host,port).addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
if(future.isSuccess()){ System.out.println("websocket started");
}
}
}).sync(); future.channel().closeFuture().addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
// TODO Auto-generated method stub
System.out.println("channel is closed");
}
}).sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{ bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} System.out.println("websocket shutdown");
} @Override
public void handleHttpRequset(ChannelHandlerContext ctx,
FullHttpRequest request) {
// TODO Auto-generated method stub if(isWebSocketUpgrade(request)){ String subProtocols = request.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(WEBSOCKET_URI_ROOT, subProtocols, false); WebSocketServerHandshaker handshaker = factory.newHandshaker(request); if(handshaker == null){ WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); }else{
//响应请求
handshaker.handshake(ctx.channel(), request);
//将handshaker绑定给channel
ctx.channel().attr(ATTR_HANDSHAKER).set(handshaker);
}
return;
} } @Override
public void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// TODO Auto-generated method stub if(frame instanceof TextWebSocketFrame){ String text = ((TextWebSocketFrame) frame).text(); TextWebSocketFrame rsp = new TextWebSocketFrame(text);
// System.out.println(channelMap.size());
//
// for(Channel ch:channelMap.values()){
//
//
// if (ctx.channel().equals(ch)) {
// continue;
// }
// ch.writeAndFlush(rsp);
// }
group.writeAndFlush(rsp); //
} //ping 回复 pong
if(frame instanceof PingWebSocketFrame){ ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
return; } if(frame instanceof PongWebSocketFrame){ return;
} if(frame instanceof CloseWebSocketFrame){ WebSocketServerHandshaker handshaker = ctx.channel().attr(ATTR_HANDSHAKER).get(); if(handshaker == null){ return;
}
handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
return;
} } //1、判断是否为get 2、判断Upgrade头 包含websocket字符串 3、Connection头 包换upgrade字符串
private boolean isWebSocketUpgrade(FullHttpRequest request){ HttpHeaders headers = request.headers(); return request.method().equals(HttpMethod.GET)
&& headers.get(HttpHeaderNames.UPGRADE).contains(WEBSOCKET_UPGRADE)
&& headers.get(HttpHeaderNames.CONNECTION).contains(WEBSOCKET_CONNECTION); } public void sendMessage(String message){ TextWebSocketFrame rsp = new TextWebSocketFrame(message);
for(Channel ch:channelMap.values()){ ch.writeAndFlush(rsp); } }
}

Netty+WebSocket简单实现网页聊天的更多相关文章

  1. Netty学习——基于netty实现简单的客户端聊天小程序

    Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import ...

  2. websocket简单实现在线聊天

    WebSocket简介与消息推送 B/S架构的系统多使用HTTP协议,HTTP协议的特点: 1 无状态协议2 用于通过 Internet 发送请求消息和响应消息3 使用端口接收和发送消息,默认为80端 ...

  3. Php7+Mysql8实现简单的网页聊天室功能

    php聊天室 前端页面 chat_room.html <!DOCTYPE html> <html lang="en"> <head>     & ...

  4. Java和WebSocket开发网页聊天室

    小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...

  5. JavaWeb网页聊天室(WebSocket即时通讯)

    原文:http://baike.xsoftlab.net/view/656.html Git地址 http://git.oschina.net/loopcc/WebSocketChat 概要: Web ...

  6. WebSocket 网页聊天室的实现(服务器端:.net + windows服务,前端:Html5)

    websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率.废话不多说,直接进入题. 网页聊天室包括2个部分,后端服务器 ...

  7. WebSocket入门教程(五)-- WebSocket实例:简单多人聊天室

    from:https://blog.csdn.net/u010136741/article/details/51612594 [总目录]   WebSocket入门教程--大纲   [实例简介]   ...

  8. Spring之WebSocket网页聊天以及服务器推送

    Spring之WebSocket网页聊天以及服务器推送 转自:http://www.xdemo.org/spring-websocket-comet/ /Springframework /Spring ...

  9. SpringBoot基于websocket的网页聊天

    一.入门简介正常聊天程序需要使用消息组件ActiveMQ或者Kafka等,这里是一个Websocket入门程序. 有人有疑问这个技术有什么作用,为什么要有它?其实我们虽然有http协议,但是它有一个缺 ...

随机推荐

  1. PatentTips - Transparent unification of virtual machines

    BACKGROUND Virtualization technology enables a single host computer running a virtual machine monito ...

  2. ANSI-X99MAC算法和PBOC的3DES MAC算法

    仅仅要有标准的DES加密和解密算法.类似ANSI-X99MAC算法和PBOC3DES算法就非常好实现.他们都是用DES算法再经过一层算法实现的.实现原理看图就能看明确.3DES算法实现就更简单了.就是 ...

  3. 8.NPM 使用介绍

    转自:http://www.runoob.com/nodejs/nodejs-tutorial.html NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使 ...

  4. 洛谷P1734 最大约数和

    题目描述 选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大. 输入输出格式 输入格式: 输入一个正整数S. 输出格式: 输出最大的约数之和. 输入输出样例 输入样例#1: 复 ...

  5. ImageButton-设置background跟src

    xml中添加ImageButton的background跟src <ImageButton android:id="@+id/tv3" android:layout_widt ...

  6. Log4j.properties 属性详解以及 LOG4J日志级别详解

    转自:https://blog.csdn.net/lovely0903jpp/article/details/82261607 假如项目的生产环境上增加请求以及响应信息的打印,这个时候,对于新手来说, ...

  7. 使用ILMerge将所有引用的DLL和exe文件打成一个exe文件

    今天做了一个IM自动更新的软件,里面牵扯到了文件的解压和接口签名加密,使用了2个第三方的dll,想发布的时候才发现调用的类没几个,就像把它们都跟EXE文件打包在一起,以后复制去别的地方用也方便,于是上 ...

  8. socket UDP简单通讯

    // // SocketUDPServerClient.m // socket_server_client // // Created by lujunjie on 2016/11/26. // Co ...

  9. Java Web学习总结(4)——HttpServletResponse对象入门

    Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象.和代表响应的response对象. request和response对象即然代表请求和响应,那我们 ...

  10. spring使用context:property-placeholder载不进属性问题 wangbiglei 发表于1年前 原 spring使用context:property-placeholder载不进属性问题

    https://my.oschina.net/wangbiglei/blog/489583 http://www.cnblogs.com/leftthen/p/5615066.html