【Netty】八、Netty实现Netty+http+websocket聊天室案例

Netty+http+websocket聊天室案例

  • 一、实现流程
  • 二、实现效果
  • 三、实现代码
    • ChatServer
    • HttpHandler
    • WebSocktHandler
    • ChatMessageProcessor

一、实现流程

本案例可以 掌握netty对http协议的处理;掌握netty对websocket协议的处理;
1、浏览器地址栏输入netty服务器的ip和端口,通过http短连接访问到了Netty服务器;
2、Netty服务器处理http请求并返回一个页面的html源码给浏览器,浏览器解析html源码并渲染出页面效果;
3、紧接着页面上的js被执行,js发起一个websocket连接到netty服务器,netty服务器接收到连接,建立起一个浏览器到netty服务器的websocket的长连接;
4、页面上通过websocket协议发送聊天信息到netty服务器;
5、netty服务接收到页面的聊天信息后,将信息写向浏览器,如果有多个浏览器在访问netty服务器,会把信息像广播一样写给每一个浏览器;
6、当netty服务端写出数据到浏览器,实际上是触发页面上js的websocket.onmessage()方法,接收到数据后,通过js在页面渲染数据即可;

二、实现效果

在这里插入图片描述

三、实现代码

ChatServer

import com.mytest.server.handler.HttpHandler;
import com.mytest.server.handler.WebSocktHandler;
import io.netty.bootstrap.ServerBootstrap;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.apache.log4j.Logger;import java.io.IOException;/*** 聊天室服务端* Netty+http+websocket聊天室案例*/
public class ChatServer {private static Logger LOG = Logger.getLogger(ChatServer.class);private static final int PORT = 8088;public static void main(String[] args) throws IOException {new ChatServer().start();}public void start() {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//Http请求编码和解码pipeline.addLast(new HttpServerCodec());//将同一个http请求或响应的多个消息对象变成一个fullHttpRequest完整的消息对象pipeline.addLast(new HttpObjectAggregator(64 * 1024));//用于处理大数据流,比如1G大小的文件传输时会切成小块处理,加上该handler就不用考虑大文件传输的问题pipeline.addLast(new ChunkedWriteHandler());//我们自己编写的http请求逻辑处理Handlerpipeline.addLast(new HttpHandler());//WebSocket请求处理(是netty内置的handler,直接使用即可,websocket的请求路径是 ws://ip:port/im)pipeline.addLast(new WebSocketServerProtocolHandler("/im"));//我们自己编写的websocket请求逻辑处理Handlerpipeline.addLast(new WebSocktHandler());}});ChannelFuture future = b.bind(PORT).sync();LOG.info("服务已启动, 监听端口" + PORT);future.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}
}

HttpHandler

import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import org.apache.log4j.Logger;import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;/*** 对Http请求的处理Handler*/
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {private static Logger LOG = Logger.getLogger(HttpHandler.class);//web页面文件所在目录private final String webroot = "web";//获取class路径private URL baseURL = HttpHandler.class.getProtectionDomain().getCodeSource().getLocation();private File getResource(String fileName) throws Exception {String path = baseURL.toURI() + webroot + "/" + fileName;path = !path.contains("file:") ? path : path.substring(5);path = path.replaceAll("//", "/");return new File(path);}@Overridepublic void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {String uri = request.uri();RandomAccessFile file = null;try {String page = uri.equals("/") ? "chat.html" : uri;file = new RandomAccessFile(getResource(page), "r");} catch (Exception e) {ctx.fireChannelRead(request.retain());return;}HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);String contextType = "text/html;";if (uri.endsWith(".css")) {contextType = "text/css;";} else if (uri.endsWith(".js")) {contextType = "text/javascript;";} else if (uri.toLowerCase().matches("(jpg|png|gif)$")) {String ext = uri.substring(uri.lastIndexOf("."));contextType = "image/" + ext;}response.headers().set(HttpHeaderNames.CONTENT_TYPE, contextType + "charset=utf-8;");boolean keepAlive = HttpUtil.isKeepAlive(request);if (keepAlive) {response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);}ctx.write(response);ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));//ctx.write(new ChunkedNioFile(file.getChannel()));ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);if (!keepAlive) {future.addListener(ChannelFutureListener.CLOSE);}file.close();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {LOG.info("Client:" + ctx.channel().remoteAddress() + "异常");cause.printStackTrace();ctx.close();}
}

WebSocktHandler

/*** WebSocket的请求处理**/
public class WebSocktHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {private static Logger LOG = Logger.getLogger(WebSocktHandler.class);//消息的处理private ChatMessageProcessor processor = new ChatMessageProcessor();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {processor.sendMsg(ctx.channel(), msg.text());}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) {Channel client = ctx.channel();String addr = processor.getAddress(client);LOG.info("WebSocket Client:" + addr + "加入");}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) {Channel client = ctx.channel();processor.logout(client);LOG.info("WebSocket Client:" + processor.getNickName(client) + "离开");}@Overridepublic void channelActive(ChannelHandlerContext ctx) {Channel client = ctx.channel();String addr = processor.getAddress(client);LOG.info("WebSocket Client:" + addr + "上线");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {Channel client = ctx.channel();String addr = processor.getAddress(client);LOG.info("WebSocket Client:" + addr + "掉线");}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {Channel client = ctx.channel();String addr = processor.getAddress(client);LOG.info("WebSocket Client:" + addr + "异常");cause.printStackTrace();ctx.close();}
}

ChatMessageProcessor

import com.alibaba.fastjson.JSONObject;
import com.mytest.protocol.ChatDecoder;
import com.mytest.protocol.ChatEncoder;
import com.mytest.protocol.ChatMessage;
import com.mytest.protocol.ChatType;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;public class ChatMessageProcessor {//记录在线用户private static ChannelGroup onlineUsers = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);//定义一些扩展属性private final AttributeKey<String> NICK_NAME = AttributeKey.valueOf("nickName");private final AttributeKey<String> IP_ADDR = AttributeKey.valueOf("ipAddr");private final AttributeKey<JSONObject> ATTRS = AttributeKey.valueOf("attrs");//解码器private ChatDecoder decoder = new ChatDecoder();//编码器private ChatEncoder encoder = new ChatEncoder();/*** 获取用户昵称** @param client* @return*/public String getNickName(Channel client) {return client.attr(NICK_NAME).get();}/*** 获取用户远程IP地址** @param client* @return*/public String getAddress(Channel client) {return client.remoteAddress().toString().replaceFirst("/", "");}/*** 获取扩展属性** @param client* @return*/public JSONObject getAttrs(Channel client) {try {return client.attr(ATTRS).get();} catch (Exception e) {return null;}}/*** 设置扩展属性** @param client* @return*/private void setAttrs(Channel client, String key, Object value) {try {JSONObject json = client.attr(ATTRS).get();json.put(key, value);client.attr(ATTRS).set(json);} catch (Exception e) {JSONObject json = new JSONObject();json.put(key, value);client.attr(ATTRS).set(json);}}/*** 登录退出通知** @param client*/public void logout(Channel client) {if (getNickName(client) == null) {return;}for (Channel channel : onlineUsers) {ChatMessage request = new ChatMessage(ChatType.SYSTEM.getName(), sysTime(), onlineUsers.size(), getNickName(client) + "离开");String content = encoder.encode(request);channel.writeAndFlush(new TextWebSocketFrame(content));}onlineUsers.remove(client);}/*** 发送消息** @param client* @param msg*/public void sendMsg(Channel client, ChatMessage msg) {sendMsg(client, encoder.encode(msg));}/*** 发送消息** @param client* @param msg*/public void sendMsg(Channel client, String msg) {ChatMessage request = decoder.decode(msg);if (null == request) {return;}String addr = getAddress(client);//登录消息if (request.getCmd().equals(ChatType.LOGIN.getName())) {client.attr(NICK_NAME).getAndSet(request.getSender());client.attr(IP_ADDR).getAndSet(addr);onlineUsers.add(client);for (Channel channel : onlineUsers) {if (channel != client) {request = new ChatMessage(ChatType.SYSTEM.getName(), sysTime(), onlineUsers.size(), getNickName(client) + "加入");} else {request = new ChatMessage(ChatType.SYSTEM.getName(), sysTime(), onlineUsers.size(), "已与服务器建立连接!");}String content = encoder.encode(request);//把数据通过websocket协议写到浏览器channel.writeAndFlush(new TextWebSocketFrame(content));}//聊天消息} else if (request.getCmd().equals(ChatType.CHAT.getName())) {for (Channel channel : onlineUsers) {if (channel == client) {request.setSender("你");} else {request.setSender(getNickName(client));}request.setTime(sysTime());String content = encoder.encode(request);channel.writeAndFlush(new TextWebSocketFrame(content));}//送鲜花消息} else if (request.getCmd().equals(ChatType.FLOWER.getName())) {JSONObject attrs = getAttrs(client);long currTime = sysTime();if (null != attrs) {long lastTime = attrs.getLongValue("lastFlowerTime");//10秒之内不允许重复刷鲜花int secends = 10;long sub = currTime - lastTime;if (sub < 2000 * secends) {request.setSender("你");request.setCmd(ChatType.SYSTEM.getName());request.setContent("送鲜花太频繁了, " + (secends - Math.round(sub / 1000)) + "秒后再试.");String content = encoder.encode(request);client.writeAndFlush(new TextWebSocketFrame(content));return;}}//正常送花for (Channel channel : onlineUsers) {if (channel == client) {request.setSender("你");request.setContent("你给大家送了一波鲜花雨");setAttrs(client, "lastFlowerTime", currTime);} else {request.setSender(getNickName(client));request.setContent(getNickName(client) + "送来一波鲜花雨");}request.setTime(sysTime());String content = encoder.encode(request);channel.writeAndFlush(new TextWebSocketFrame(content));}}}/*** 获取系统时间** @return*/private Long sysTime() {return System.currentTimeMillis();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/26107.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小米AI音箱发布,但它哪里不对?

今天&#xff08;7 月 26 日&#xff09;上午九点&#xff0c;小米智能产品部&#xff0c;小米探索实验室总经理唐沐发了条微博&#xff0c;“今天发布会有个 one more thing&#xff0c;猜对了我抽送一台。”评论区几乎清一色的“智能语音音箱”&#xff0c;唐沐清一色地回应“…

仿作小米商城页面

历时一周半的时间&#xff0c;终于在我的不懈努力下&#xff0c;完成了小米商城页面的静态仿作。真的&#xff0c;这个过程我觉得极其漫长&#xff0c;到最后也不敢相信自己能够完成。因为自己距上一次html和css的学习已经有好久了。好多好多的知识都已经忘记了&#xff0c;以至…

小米商城界面

效果图 分为三部分 一&#xff1a;css 1.content ul { list-style: none; }h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; font-weight: normal; }/*大导航下面的六个小css块样式*/ .content-picli_1 li { width: 33%; height: 60px; color: #ccc; font-si…

AI一分钟 | 小米发布小爱音箱mini,169元;天猫汽车无人贩卖机大楼落地,刷脸可购车试驾

2018 区块链技术及应用峰会(BTA)中国 倒计时 3 天 2018&#xff0c;想要follow最火的区块链技术&#xff1f;你还差一场严谨纯粹的技术交流会——2018区块链技术及应用峰会(BTA)中国将于2018年3月30-31日登陆北京喜来登长城饭店。追求专业性&#xff1f;你要的这里全都有&#…

天猫精灵方糖AI智能音箱拆解报告

天猫精灵方糖AI智能音箱拆解报告 前言拆解总结 前言 天猫精灵方糖AI智能音箱可以设闹钟、讲故事、听音乐、语音控制智能家电、购物等多种功能。机身尺寸134 mm65 mm59mm&#xff0c;重265g。有三种颜色&#xff1a;白色、魔岩灰、烈焰红&#xff0c;小编听说这是李剑叶加入阿里…

homeassistant 接入小米温湿度计2

方法可能有很多种&#xff0c;但是我只用这种方法介入成功了 环境&#xff1a; - 香橙派&#xff08;自带蓝牙&#xff0c;树莓派应该也可以&#xff09; - 小米温湿度计2 - 网站&#xff1a;Telink Flasher v4.7 、 Telink Flasher (atc1441.github.io) - 固件&#xff1…

5款主流智能音箱入门款测评:苹果小米华为天猫小度,谁的表现更胜一筹?

智能音箱那么多&#xff0c;究竟谁更好&#xff1f;要说智能行不行&#xff0c;就让它们各自吵。纵观各个智能家居生态&#xff0c;智能音箱几乎都是作为智能家居交互入口的必备产品之一。除了可以播放音乐&#xff0c;这个音箱本体的功能以外&#xff0c;内置网关是智能家居中…

智汀如何连接小米智能音箱?

本文将给大伙盘点那些通过第三方平台&#xff0c;来语音控制HomeKit设备&#xff0c;如小米智能音箱。 仅用唤醒词“小爱同学”唤醒小米智能音箱&#xff0c;这时可以听到音箱应答&#xff0c;然后再说“打开/关闭设备名称”的语音指令即可&#xff1b;比如&#xff1a;“小爱同…

小爱音箱 电脑 麦克风_颜值音质皆出色,还有丰富功能,小米小爱音箱体验

近日小米有两款新品智能音箱正式上市&#xff0c;分别是小米小爱音箱以及小米小爱音箱Pro&#xff0c;其中后者除了配色不同外还多了红外模块&#xff0c;支持通过语音以及APP对传统家电进行操控&#xff0c;其他方面两款产品完全一致。因此假如你不需要操控传统家电的话&#…

用了这么多年苹果手机,居然才发现桌面图标可以这样快速转移

苹果手机因为ios系统备受人喜爱&#xff0c;当然我们安卓也不差。每次我把一大堆图标换到另一页的时候&#xff0c;都是一个一个移动&#xff0c;太麻烦了。今天才发现原来可以群移。下面我们来分享一下苹果手机怎么把一堆桌面图标搬到另一页的操作方法。 首先&#xff0c;轻长…

Mac新手需要知道的显示桌面的快捷方式

在桌面上打开了太多的应用窗口&#xff0c;想要返回桌面时&#xff0c;一个一个的缩小窗口是非常麻烦的&#xff0c;这时候就需要用到显示桌面的快捷方法啦。下面小编就来给大家介绍几种 Mac上显示桌面的快捷方法&#xff0c;Mac显示桌面的快捷方式&#xff0c;我个人还是觉得蛮…

iOS App桌面图标和名称

1、App桌面图标如下图&#xff1a; 2、App桌面名称如下图&#xff1a; OK&#xff01;祝您生活愉快&#xff01; 要是对您有用&#xff0c;问好&#xff0c;谢谢。

iOS 换了AppIcon后切换回桌面总是先显示之前旧图标再显示新图标问题

问题描述&#xff1a; APP换了Assets.xcassets里面的AppIcon&#xff0c;从图1.png换到图2.png之后,在使用HOME切回桌面总是图1先显示一下之后会换回图2。 系统环境&#xff1a;MAC 10.15 开发环境&#xff1a;xcode 11.1 排除问题 &#xff1a;尝试清空AppIcon以及检索项目内所…

适用于iOS的远程桌面软件

全球远程桌面软件市场最近达到19.2亿美元&#xff0c;表明使用任意设备实现随处远程控制越来越受欢迎。 近年来&#xff0c;企业的运营方式发生了重大改变&#xff0c;远程桌面软件已成为广泛使用的解决方案。Splashtop 是目前最好用的远程桌面工具之一&#xff0c;安全可靠且…

Mac显示桌面的快捷方式

很多朋友不太清楚苹果Mac怎么快速显示桌面,而有的时候在桌面上打开了太多的应用窗口,想要返回桌面时,一个一个的缩小窗口是非常麻烦的,这时候就需要用到显示桌面的快捷方法啦。下面小编就来给大家介绍几种 Mac上显示桌面的快捷方法吧。方法一:通过快捷键显示桌面 1、系统自…

iPhone 计算机 桌面,2分钟学会Windows仿苹果任务栏,你的电脑桌面也可如此炫酷!...

首先看看设置完的效果 设置方法&#xff1a; 1、首先打下需要下载RocketDock插件&#xff0c;无需安装&#xff0c;点击RocketDock.exe开始运行 2、在出现的任务栏上&#xff0c;单击右键&#xff0c;选择“程序设置” ①在常规中&#xff0c;选择语言为简体中文&#xff0c;开…

iPhone微信支持更换桌面图标了,超简单

几天前&#xff0c;#微信图标#话题冲上了微博热搜&#xff0c;引发了许多关注。 微信图标发生什么事了&#xff1f;原来iOS版微信支持更换桌面图标了。 由于iOS系统的封闭性&#xff0c;iPhone用户如果想为手机更换主题&#xff0c;或者为App设置个性化图标&#xff0c;只能通…

一款开源的专业桌面级 iOS 终端应用

&#x1f447;&#x1f447;关注后回复 “进群” &#xff0c;拉你进程序员交流群&#x1f447;&#x1f447; 作者丨小集 来源丨小集&#xff08;ID&#xff1a;zsxjtip&#xff09; iOS 和 iPadOS 由于 Apple 的限制&#xff0c;没有原生的终端&#xff0c;同时由于应用程序被…

读《沸腾十五年》

《沸腾十五年》记录了中国互联网行业从 1995 年到 2009 年的发展历程&#xff0c;深入探讨了互联网行业的商业模式、技术发展、市场竞争等方面的问题&#xff0c;讲述了一群热血青年使用技术改变世界的故事。 它不仅仅是一本关于初代互联网人的创业史&#xff0c;也是一本关于互…

姓氏头像制作生成头像组合微信小程序源码

这是一个头像类型的小程序源码 支持多种流量主 比如激励视频,Banner,视频,插屏,原生模板等 小程序内包含多种头像非类,都是自动采集 比如男生头像,男声头像,动漫头像等等 另外该小程序还支持姓氏头像生成制作 自定义姓氏输入,标语,印章等输入制作 另外拥有标语选择,可以…