在线聊天室的实现(3)--简易聊天室的实现
前言:
就如前文所讲述的, 聊天室往往是最基本的网络编程的学习案例. 本文以WebSocket为底层协议, 实现一个简单的聊天室服务.
服务器采用Netty 4.x来实现, 源于其对websocket的超强支持, 基于卓越的性能和稳定.
本系列的文章链接如下:
1). websocket协议和javascript版的api
2). 基于Netty 4.x的Echo服务器实现
初步构想:
本文对聊天室服务的定位还是比较简单. 只需要有简单的账户体系, 能够实现简单的群聊功能即可.
流程设计初稿:
1). 用户登陆
2). 群聊界面
这里没有聊天室的选择, 只有唯一的一个. 这边有没有表情支持, 内容过滤. 一切皆从简.
协议约定:
在websocket协议的基础之上, 我们引入应用层的聊天协议.
协议以JSON作为数据交互格式, 并进行扩展和阐述.
• 请求形态约定
1 | request: {cmd: "$cmd" , params: {}} |
• 响应形态约定
1 | response: {cmd: "$cmd" , retcode:$retcode, datas:{}} // retcode => 0: success, 1: fail |
1). 用户登陆请求:
1 2 3 4 | request: {cmd: "login" , params: {username: "$username" }} response: 成功: {cmd: "login" , retcode: 0, datas:{username:$username, userid:$userid}} 失败: {cmd: "login" , retcode:1, datas:{}} |
注: 成功后, 返回分配的userid(全局唯一).
2). 消息发送请求:
1 2 3 4 | request: {cmd: "send_message" , params:{message:$message, messageid:$messageid}} response: 成功: {cmd: "send_message" , retcode:0, datas:{messageid:$messageid}} 失败: {cmd: "send_message" , retcode:1, datas:{messageid:$messageid}} |
注: 这边的messageid是必须的, 可以提示那条消息成功还是失败
3). 消息接受(OnReceive):
1 2 3 4 | 刷新用户列表 {cmd: "userlist" , retcode:0, datas: {users: [{userid:$userid, username:$username}, {userid:$userid, username:$username}]}} 接收消息 {cmd: "receive_message" , retcode:0, datas: {message: "$message" , username: "$username" , userid: "$userid" , timestamp: "$timestamp" }} |
服务端实现:
借助Netty 4.x来构建服务器端, 其Channel的pipeline设定如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel. class ) .childHandler( new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline cp = socketChannel.pipeline(); // *) 支持http协议的解析 cp.addLast( new HttpServerCodec()); cp.addLast( new HttpObjectAggregator( 65535 )); // *) 对于大文件支持 chunked方式写 cp.addLast( new ChunkedWriteHandler()); // *) 对websocket协议的处理--握手处理, ping/pong心跳, 关闭 cp.addLast( new WebSocketServerProtocolHandler( "/chatserver" )); // *) 对TextWebSocketFrame的处理 cp.addLast( new ChatLogicHandler(userGroupManager)); } }); |
注: HttpServerCodec / HttpObjectAggregator / ChunkedWriteHandler / WebSocketServerProtocolHandler, 依次引入极大简化了websocket的服务编写.
ChatLogicHandler的定义, 其对TextWebSocketFrame进行解析处理, 并从中提取聊天协议的请求, 并进行相应的状态处理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame twsf) throws Exception { String text = twsf.text(); JSONObject json = JSON.parseObject(text); if ( !json.containsKey( "cmd" ) || !json.containsKey( "params" ) ) { ctx.close(); return ; } String cmd = json.getString( "cmd" ); if ( "login" .equalsIgnoreCase(cmd) ) { // *) 处理登陆事件 handleLoginEvent(ctx, json.getJSONObject( "params" )); } else if ( "send_message" .equalsIgnoreCase(cmd) ) { // *) 处理群聊消息事件 handleMessageEvent(ctx, json.getJSONObject( "params" )); } } |
其实这边还可以再加一层, 用于聊天协议的请求/响应的封装, 这样结构上更清晰. 但为了简单起见, 就省略了.
客户端的实现:
web客户端最重要的还是, 对chatclient的封装. 其封装了websocket了, 实现了业务上的login和sendmessage函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | ( function (window) { ChatEvent.LOGIN_ACK = 1001; ChatEvent.SEND_MESSAGE_ACK = 1002; ChatEvent.USERLIST = 2001; ChatEvent.RECEIVE_MESSAGE = 2002; ChatEvent.UNEXPECT_ERROR = 10001; function ChatEvent(type, retcode, msg) { this .type = type; this .retcode = retcode; // retcode : 0 => success, 1 => fail this .msg = msg; } WebChatClient.UNCONNECTED = 0; WebChatClient.CONNECTED = 1; WebChatClient.LOGINED = 1; function WebChatClient() { this .websocket = null ; this .state = WebChatClient.UNCONNECTED; this .chatEventListener = null ; } WebChatClient.prototype.init = function (wsUrl, chatEventListener) { this .state = WebChatClient.UNCONNECTED; this .chatEventListener = chatEventListener; this .websocket = new WebSocket(wsUrl); //创建WebSocket对象 var self = this ; this .websocket.onopen = function (evt) { self.state = WebChatClient.CONNECTED; } this .websocket.onmessage = function (evt) { var res = JSON.parse(evt.data); if ( !res.hasOwnProperty( "cmd" ) || !res.hasOwnProperty( "retcode" ) || !res.hasOwnProperty( "datas" ) ) { self.chatEventListener( new ChatEvent(ChatEvent.UNEXPECT_ERROR, 0)); } else { var cmd = res[ "cmd" ]; var retcode = res[ "retcode" ]; var datas = res[ "datas" ]; switch (cmd) { case "login" : self.chatEventListener( new ChatEvent(ChatEvent.LOGIN_ACK, retcode, datas)); break ; case "send_message" : self.chatEventListener( new ChatEvent(ChatEvent.SEND_MESSAGE_ACK, retcode, datas)); break ; case "userlist" : self.chatEventListener( new ChatEvent(ChatEvent.USERLIST, retcode, datas)); break ; case "receive_message" : self.chatEventListener( new ChatEvent(ChatEvent.RECEIVE_MESSAGE, retcode, datas)); break ; } } } this .websocket.onerror = function (evt) { } } WebChatClient.prototype.login = function (username) { if ( this .websocket.readyState == WebSocket.OPEN && this .state == WebChatClient.CONNECTED ) { var msgdata = JSON.stringify({cmd: "login" , params:{username:username}}); this .websocket.send(msgdata); } } WebChatClient.prototype.sendMessage = function (message) { if ( this .websocket.readyState == WebSocket.OPEN && this .state == WebChatClient.LOGINED ) { var msgdata = JSON.stringify({cmd: "send_message" , params:{message:message}}); this .websocket.send(msgdata); } } // export window.ChatEvent = ChatEvent; window.WebChatClient = WebChatClient; })(window); |
代码下载:
由于采用websocket作为底层网络通讯协议, 因此需要浏览器支持(最好为Chrome).
服务器和web客户端的源码下载地址为: http://pan.baidu.com/s/1pJDYo3p.
文件的目录结构如下:
后期展望:
编写完初步版本后, 一群有爱的小伙伴们帮我友情测试了一把. 中间反馈了很多体验上的改进意见. 比如Enter键自动发送, 最新留言与页面底部同步, 自己名称高亮显示.
也有反馈添加表情等高大上的功能, ^_^. 总之感觉棒棒哒, 觉得再做一件很伟大的事情. oh yeah.
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.
posted on 2015-08-11 14:32 mumuxinfei 阅读(7118) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 你所不知道的 C/C++ 宏知识
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 我干了两个月的大项目,开源了!
· 推荐一款非常好用的在线 SSH 管理工具
· 千万级的大表,如何做性能调优?
· 盘点!HelloGitHub 年度热门开源项目
· Phi小模型开发教程:用C#开发本地部署AI聊天工具,只需CPU,不需要GPU,3G内存就可以运行,