在线聊天室的实现(3)--简易聊天室的实现
前言:
就如前文所讲述的, 聊天室往往是最基本的网络编程的学习案例. 本文以WebSocket为底层协议, 实现一个简单的聊天室服务.
服务器采用Netty 4.x来实现, 源于其对websocket的超强支持, 基于卓越的性能和稳定.
本系列的文章链接如下:
1). websocket协议和javascript版的api
2). 基于Netty 4.x的Echo服务器实现
初步构想:
本文对聊天室服务的定位还是比较简单. 只需要有简单的账户体系, 能够实现简单的群聊功能即可.
流程设计初稿:
1). 用户登陆

2). 群聊界面

这里没有聊天室的选择, 只有唯一的一个. 这边有没有表情支持, 内容过滤. 一切皆从简.
协议约定:
在websocket协议的基础之上, 我们引入应用层的聊天协议.
协议以JSON作为数据交互格式, 并进行扩展和阐述.
• 请求形态约定
request: {cmd:"$cmd", params: {}} • 响应形态约定
response: {cmd:"$cmd", retcode:$retcode, datas:{}} // retcode => 0: success, 1: fail 
1). 用户登陆请求:
request: {cmd:"login", params: {username:"$username"}}response: 成功: {cmd:"login", retcode: 0, datas:{username:$username, userid:$userid}}失败: {cmd:"login", retcode:1, datas:{}} 注: 成功后, 返回分配的userid(全局唯一).
2). 消息发送请求:
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):
刷新用户列表{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设定如下:
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer() {@Overrideprotected 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进行解析处理, 并从中提取聊天协议的请求, 并进行相应的状态处理.
@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函数.
(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 => failthis.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);}}// exportwindow.ChatEvent = ChatEvent;window.WebChatClient = WebChatClient;})(window);
代码下载:
由于采用websocket作为底层网络通讯协议, 因此需要浏览器支持(最好为Chrome).
服务器和web客户端的源码下载地址为: http://pan.baidu.com/s/1pJDYo3p.
文件的目录结构如下:

后期展望:
编写完初步版本后, 一群有爱的小伙伴们帮我友情测试了一把. 中间反馈了很多体验上的改进意见. 比如Enter键自动发送, 最新留言与页面底部同步, 自己名称高亮显示.
也有反馈添加表情等高大上的功能, ^_^. 总之感觉棒棒哒, 觉得再做一件很伟大的事情. oh yeah.
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
