一、编码器、解码器

... ...

@Autowired
private HttpRequestHandler httpRequestHandler;
@Autowired
private TextWebSocketFrameHandler textWebSocketFrameHandler; ... ... .childHandler(new ChannelInitializer<SocketChannel> () { @Override
protected void initChannel(SocketChannel channel) throws Exception {
// WebSocket 是基于 Http 协议的,要使用 Http 解编码器
channel.pipeline().addLast("http-codec", new HttpServerCodec());
// 用于大数据流的分区传输
channel.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
// 将多个消息转换为单一的 request 或者 response 对象,最终得到的是 FullHttpRequest 对象
channel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
// 创建 WebSocket 之前会有唯一一次 Http 请求 (Header 中包含 Upgrade 并且值为 websocket)
channel.pipeline().addLast("http-request",httpRequestHandler);
// 处理所有委托管理的 WebSocket 帧类型以及握手本身
// 入参是 ws://server:port/context_path 中的 contex_path
channel.pipeline().addLast("websocket-server", new WebSocketServerProtocolHandler(socketUri));
// WebSocket RFC 定义了 6 种帧,TextWebSocketFrame 是我们唯一真正需要处理的帧类型
channel.pipeline().addLast("text-frame",textWebSocketFrameHandler);
}
}); ... ...

其中 HttpRequestHandler 和 TextWebSocketFrameHandler 是自定义 Handler

1.1 HttpRequestHandler

@Component
@ChannelHandler.Sharable
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestHandler.class); @Value("${server.socket-uri}")
private String socketUri; @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
if (msg.uri().startsWith(socketUri)) {
String userId = UriUtil.getParam(msg.uri(), "userId");
if (userId != null) {
// todo: 用户校验,重复登录判断
ChannelSupervise.addChannel(userId, ctx.channel());
ctx.fireChannelRead(msg.setUri(socketUri).retain());
} else {
ctx.close();
}
} else {
ctx.close();
}
} }

1.2 TextWebSocketFrameHandler

@Component
@ChannelHandler.Sharable
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { private static final Logger LOGGER = LoggerFactory.getLogger(TextWebSocketFrameHandler.class); @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
ctx.pipeline().remove(HttpRequestHandler.class);
}
super.userEventTriggered(ctx, evt);
} @Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String requestMsg = msg.text();
String responseMsg = "服务端接收客户端消息:" + requestMsg;
TextWebSocketFrame resp = new TextWebSocketFrame(responseMsg);
ctx.writeAndFlush(resp.retain());
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
LOGGER.error(ctx.channel().id().asShortText(), cause);
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6)
ChannelSupervise.removeChannel(ctx.channel());
LOGGER.info("[%s]断开连接", ctx.channel().id().asShortText());
}
}

二、主动向客户端推送消息

2.1 推送工具类

public class ChannelSupervise {

    private static ChannelGroup GlobalGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private static ConcurrentMap<String, ChannelId> UserChannelMap = new ConcurrentHashMap();
private static ConcurrentMap<String, String> ChannelUserMap = new ConcurrentHashMap(); public static void addChannel(String userId, Channel channel){
GlobalGroup.add(channel);
UserChannelMap.put(userId, channel.id());
ChannelUserMap.put(channel.id().asShortText(), userId);
}
public static void removeChannel(Channel channel){
GlobalGroup.remove(channel);
String userId = ChannelUserMap.get(channel.id().asShortText());
UserChannelMap.remove(userId);
ChannelUserMap.remove(channel.id().asShortText());
}
public static void sendToUser(String userId, String msg){
TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg);
Channel channel = GlobalGroup.find(UserChannelMap.get(userId));
channel.writeAndFlush(textWebSocketFrame);
}
public static void sendToAll(String msg){
TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg);
GlobalGroup.writeAndFlush(textWebSocketFrame);
}
}

支持向具体某个客户端发送消息,或者群发消息

2.2 推送接口

@RestController
public class WebsocketController {
@RequestMapping("sendToAll")
public void sendToAll(String msg) {
ChannelSupervise.sendToAll(msg);
} @RequestMapping("sendToUser")
public void sendToUser(String userId, String msg) {
ChannelSupervise.sendToUser(userId, msg);
}
}

三、测试

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
</head>
<body> <script type="text/javascript"> var socket; function connect(){
var userId = document.getElementById('userId').value;
if(window.WebSocket){
// 参数就是与服务器连接的地址
// socket = new WebSocket('ws://localhost:8081/ws');
socket = new WebSocket('ws://localhost:8081/ws?userId=' + userId); // 客户端收到服务器消息的时候就会执行这个回调方法
socket.onmessage = function (event) {
var response = document.getElementById('response');
response.innerHTML = response.innerHTML
+ '<p style="color:LimeGreen;"> 接收:' + event.data + '</p>';
} // 连接建立的回调函数
socket.onopen = function(event){
var status = document.getElementById('status');
status.innerHTML = '<p style="color:YellowGreen;">WebSocket 连接开启</p>';
} // 连接断掉的回调函数
socket.onclose = function (event) {
var status = document.getElementById('status');
status.innerHTML = '<p style="color:Red;">WebSocket 连接关闭</p>';
}
}else{
var status = document.getElementById('status');
status.innerHTML = '<p style="color:Red;">浏览器不支持 WebSocket</p>';
}
} // 发送数据
function send(message){
if(!window.WebSocket){
return;
} var ta = document.getElementById('response');
ta.innerHTML = ta.innerHTML + '<p style="color:SkyBlue;"> 发送:' + message + '</p>'; // 当websocket状态打开
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
}else{
var response = document.getElementById("response");
response.innerHTML = '<p style="color:Red;">连接没有开启</p>';
}
}
</script> <form onsubmit="return false">
<label for="userId">用户ID:</label>
<input type="text" name="userId" id="userId" />
<input type ="button" value="连接服务器" onclick="connect();">
</form> <div id ="status"></div> <form onsubmit="return false">
<input name = "message" style="width: 200px;"></input>
<input type ="button" value="发送消息" onclick="send(this.form.message.value);">
</form> <div id ="response"></div> <input type="button" onclick="javascript:document.getElementById('response').innerHTML=''" value="清空消息">
</body>
</html>

注意

因为自定义 Handler 使用依赖注入实例化,所以需要添加 @ChannelHandler.Sharable 注解,否则会报错:is not a @Sharable handler, so can’t be added or removed multiple times.

参考

  1. 微言Netty:分布式服务框架
  2. 基于netty搭建websocket,实现消息的主动推送
  3. Netty笔记之六:Netty对websocket的支持
  4. 使用Netty做WebSocket服务端

完整代码:GitHub

Netty 搭建 WebSocket 服务端的更多相关文章

  1. Netty搭建WebSocket服务端

    Netty服务端 1.引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=& ...

  2. 使用Netty做WebSocket服务端

    使用Netty搭建WebSocket服务器 1.WebSocketServer.java public class WebSocketServer { private final ChannelGro ...

  3. nodejs搭建简单的websocket服务端

    创建websocket服务端使用了nodejs-websocket ,首先要安装nodejs-websocket,在项目的目录下: npm install nodejs-websocket 1.搭建w ...

  4. 《用OpenResty搭建高性能服务端》笔记

    概要 <用OpenResty搭建高性能服务端>是OpenResty系列课程中的入门课程,主讲人:温铭老师.课程分为10个章节,侧重于OpenResty的基本概念和主要特点的介绍,包括它的指 ...

  5. asp.net网站作为websocket服务端的应用该如何写

    最近被websocket的一个问题困扰了很久,有一个需求是在web网站中搭建websocket服务.客户端通过网页与服务器建立连接,然后服务器根据ip给客户端网页发送信息. 其实,这个需求并不难,只是 ...

  6. C# WebSocket 服务端示例代码 + HTML5客户端示例代码

    WebSocket服务端 C#示例代码 using System; using System.Collections.Generic; using System.Linq; using System. ...

  7. contos7搭建syslog服务端与客户端

    搭建中心服务端1,编辑文件/etc/rsyslog.conf,找到以下内容,将前面的#注释符合去除#$ModLoad imtcp#$InputTCPServerRun 514 2,在/etc/rsys ...

  8. vue.js+koa2项目实战(四)搭建koa2服务端

    搭建koa2服务端 安装两个版本的koa 一.版本安装 1.安装 koa1 npm install koa -g 注:必须安装到全局 2.安装 koa2 npm install koa@2 -g 二. ...

  9. Centos6.9 搭建rsync服务端与客户端 案例:全网备份项目

    rsync的企业工作场景说明 1)定时备份 1.1生产场景集群架构服务器备份方案项目 借助cron+rsync把所有客户服务器数据同步到备份服务器 2)实时复制 本地数据传输模式(local-only ...

随机推荐

  1. 【杂谈】JS相关的线程模型整理

    1.JS是单线程吗? 是的,到目前为止,JS语言没有多线程的语法,它的执行引擎只支持单线程,也就是一个JavaScript进程内只有一个线程. 2.事件循环什么? 事件循环就是执行线程不断的从队列中取 ...

  2. ubuntu设置mentohust开机自动登录校园网

    设置环境: ubuntu14.04  64位 无法忍受校园网ubuntu锐捷客户端登录每次开机都要输一大串命令 step1 首先下载mentohust,链接http://code.google.com ...

  3. salesforce零基础学习(九十八)Salesforce Connect & External Object

    本篇参考: https://trailhead.salesforce.com/en/content/learn/modules/lightning_connect https://help.sales ...

  4. php 断点续传以及100% 后台zip解压

    前台部分 <div class="col-md-12"> <div class="form-group"> <label clas ...

  5. 《精通Spring4.x企业应用开发实战》第二章

    昨天联系了一下学长,学长说这个项目因为种种原因代码比较混乱,感觉最坏的打算是从头开始写. 大概询问了一下学长和xianhua学姐的建议,又看了看网上的资料,这个项目开发的技术栈基本就是SpringBo ...

  6. 自定义MFC对话窗口的类名

    默认情况下,MFC对话框的窗口类名为"#32770",如果想自定义窗口类名呢,需要两步: 1.修改rc文件 这一步需要直接编辑rc文件,使用任意记事本工具即可,找到窗口的相关定义, ...

  7. Fiddler的一系列学习瞎记3

    Http: 不安全.可以很容易被拦截,或者其他的嗅探工具发现.怎么样做到安全?起码一下两点: 1.浏览器和we服务器之间的内容应该只有浏览器和web服务器能看到通信内容. 2.Http请求和Http的 ...

  8. Python_爬虫_urllib解析库

    简介:提取网页保存到txt文件中 + 解析txt文件内容,取出内容 from urllib import request import re.json url="http://www.163 ...

  9. [LeetCode题解]83. 删除排序链表中的重复元素 | 递归 + 迭代

    方法一:递归 解题思路 通过递归法,每次判断目前头节点与给定的节点是否相等.如是,继续判断下一个节点,否则保存当前头节点,设置 next 指向下次递归得到的节点,然后返回当前节点. 代码 /** * ...

  10. Java学习之Swing Gui编程

    Java学习之Swing Gui编程 0x00 前言 前面的使用的Gui是基于Awt 去进行实现,但是在现实写Gui中 AWT实际运用会比较少. 0x01 Swing 概述 AWT 和Swing 区别 ...