前言:
此时姐妹们对“聊天窗口html”都比较关切,小伙伴们都想要了解一些“聊天窗口html”的相关文章。那么小编同时在网络上汇集了一些对于“聊天窗口html””的相关知识,希望同学们能喜欢,看官们一起来了解一下吧!本篇主要讲websocket的服务端实现,主要基于Tomcat9.0,关于登录,用户列表下篇再讲,
前端可以借鉴上篇文章,地址如下:
自己动手实现基于websocket的聊天web聊天功能高仿仿qq
基于Tomcat9的websocket服务实现
首先创建一个javaweb项目(此处以eclipse开发工具为例)
然后选中项目build path
假如tomcat的依赖jar包
websocket-api.jar
tomcat-websocket.jar
这样就可以了
下面是websocket的简单的实现:
此处“imchat”为访问路径,“{id}”为需传递的参数,是OnOpen时接收的参数这两处变量名需要写一样,如果是多参数可以后面继续追加如/imchat/{id}/{name}
@ServerEndpoint(value="/imchat/{id}")
public class ImSocket {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static CopyOnWriteArraySet<ImSocket> webSocketSet = new CopyOnWriteArraySet<ImSocket>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法,只在建立连接时调用
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("id") String id,Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法,连接后所有交互数据都在此处理
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
//群发消息
for(ImSocket item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
ImSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
ImSocket.onlineCount--;
}
}
前端测试代码
<!DOCTYPE html>
<html>
<head>
<title>Java后端WebSocket的Tomcat实现</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/项目名/imchat/id123");
}
else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
以上是简单的websocket后端与前端的连接与交互,以上测试通过后咱们写一下我们聊天的简单实现
基于上面的认识我们已经能够实现简单的前端与websocket服务端的交互,下面我们接着上篇的自己动手实现基于websocket的聊天web聊天功能高仿仿qq
首先我们看一下qq的逻辑,简单的讲就这 三步,登录---》获取好友列表---》发送消息给好友;
第一步登录和获取好友列表,这个我们有两种方式去实现,一种是http请求,一种是websocket去实现,考虑到这样请求websocket会使处理流程变的复杂,所以我们采用http的方式实现,这样我们得websocket主要用来处理消息转发 服务,
首先我们创建一个实体作为消息的承载体
public class ImMsgModel { private boolean system;//消息类型 private String key;//事件类型// offline离线消息 online在线消息 private String avatar; private String id; private String sign; private String status; private String username; private String name; private String type; private String content; private long timestamp; private String fromid; public boolean isSystem() { return system; } public void setSystem(boolean system) { this.system = system; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getId() { return id; } public void setId(String id) { this.id = id; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public String getFromid() { return fromid; } public void setFromid(String fromid) { this.fromid = fromid; } public void setAvatar(String avatar) { this.avatar = avatar; } public String getAvatar() { return avatar; } public void setSign(String sign) { this.sign = sign; } public String getSign() { return sign; } public void setStatus(String status) { this.status = status; } public String getStatus() { return status; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setType(String type) { this.type = type; } public String getType() { return type; } public void setContent(String content) { this.content = content; } public String getContent() { return content; }第二步我们对websocket进行封装,此处消息实体根据layim前端封装,需要了解详情的请移步layui官网访问layim模块
@ServerEndpoint(value="/imchat/{id}")public class WebsocketsListener { private static final Set<WebsocketsListener> connections = new CopyOnWriteArraySet<WebsocketsListener>(); private Session session; private String userid; Logger log = null; public WebsocketsListener() { log = Logger.getGlobal(); } @OnOpen public void start(@PathParam(value="id") String id,Session session) { // TODO Auto-generated method stub //log.log(Level.INFO, "打开监听onOpen"); this.session=session; this.userid=id; connections.add(this);// Redis.use().hmset("userid="+id, hash); System.out.println(id+"用户session:"+session); } @OnMessage public void incoming(String message) { log.info("------------------"+message); try { ImMsgModel m = JSON.parseObject(message, ImMsgModel.class); System.out.println(m.getFromid()+"发送给"+m.getId()); Session s = getSessionByID(m.getId());//接收者id //消息接收方掉线 if(s==null){ Session s1 = getSessionByID(m.getFromid()); //发送方也不在线 if(s1==null) { log.info( "用户已掉线线"); }else { //发送消息给消息发送方提示消息接收方掉线 ImMsgModel msg = new ImMsgModel(); msg.setKey("offline"); msg.setSystem(true); msg.setId(m.getId()); msg.setType("friend"); msg.setContent("对方已掉线"); log.info( "对方已掉线"); send2user(JSON.toJSONString(msg), s1); } }else{ //发送消息给消息接收方 m.setId(m.getFromid()); m.setKey("online"); send2user(JSON.toJSONString(m), s); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private Session getSessionByID(String id) { System.out.println("连接用户数"+connections.size()); for (WebsocketsListener wb : connections) { //接收对象id对应的session if(id.equals(wb.userid)) { return wb.session; } } return null; } @OnClose public void end() { connections.remove(this); } @OnError public void onError(Throwable t) throws Throwable { log.info( "发生错误onError"); t.printStackTrace(); } private void send2user(String msg,Session session){ try { session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } } public static void sendAll(String string) { for (WebsocketsListener wb : connections) { try { wb.session.getBasicRemote().sendText(string); } catch (Exception e) { e.printStackTrace(); connections.remove(wb); try { wb.session.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } }
以上完成了websocket的服务前端消息转发代码(数据持久化与 redis后面继续进行补充)
下面我贴一下上篇中讲的前端代码的完整版
前端代码
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>layui</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="js/css/modules/layui.css" media="all"> <!-- 注意:如果你直接复制所有代码到本地,上述css路径需要改成你本地的 --></head><body> <script src="js/layui.js" charset="utf-8"></script><!-- 注意:如果你直接复制所有代码到本地,上述js路径需要改成你本地的 --><style>/* img */i{font-style:normal;}.qq-login{width:430px;height:330px;margin:0 0 -165px -215px;bottom:50%;left:50%;position:fixed;z-index:9999;border-radius:3px;overflow:hidden;box-shadow:0 0 5px #333;background:#ebf2f9 url(js/images/bj/qq-login-bg.jpg) center top no-repeat;display:block;}.login-menu{width:90px;height:30px;top:0;right:0;position:absolute;}.login-menu span{float:left;width:30px;height:30px;background-image:url(js/images/bj/qq-login-bg.jpg);}.login-menu span:hover{background-color:#3a95de;}.login-menu span:nth-child(1){background-position:left center;}.login-menu span:nth-child(2){background-position:-30px center;}.login-menu span:nth-child(3){background-position:-90px center;}.login-menu span:nth-child(3):hover{background-color:#ea4848;}.login-ner{margin-top:182px;float:left;width:100%;height:148px;}.login-left{float:left;width:133px;height:148px;}.login-head{float:left;width:80px;height:80px;border-radius:50%;border:1px solid #ccc;overflow:hidden;margin:12px 11px 0 40px;}.login-head img{width:80px;height:80px;}.login-on{width:194px;height:148px;float:left;}.login-txt{float:left;margin-top:12px;height:60px;width:100%;}.login-txt input{border:1px solid #d1d1d1;float:left;height:30px;padding:0 7px;font-size:12px;width:100%;}.login-txt input:nth-child(1){border-radius:4px 4px 0 0;}.login-txt input:nth-child(2){border-radius:0 0 4px 4px;margin-top:-1px;}.login-xuan{width:100%;float:left;height:14px;line-height:14px;margin-top:8px;}.login-xuan input{width:14px;height:14px;float:left;}.login-xuan i{float:left;padding-left:4px;}.login-right{width:103px;height:60px;float:left;margin-top:12px;}.login-right a{float:left;padding-left:10px;width:90%;color:#2786e4;line-height:30px;text-indent:10px;}.login-but{width:100%;height:30px;margin:13px 0;float:left;background:#09a3dc;color:#fff;text-align:center;line-height:30px;border-radius:4px;font-size:14px; cursor:context-menu;}.login-menu span { float: left; width: 30px; height: 30px; background-image: url(js/images/bj/wins.png);}.login-tips{line-height:40px;width:300px;padding:10px;color: white;top:0;left:0;position:absolute;}</style><script id="login_html" type="text/html"><div class="qq-login"> <div class="login-tips" id="login-tips"></div><div class="login-menu"> <span></span><span></span><span class="login-close"></span></div><div class="login-ner"> <div class="login-left"> <div class="login-head"><img src="js/css/modules/layim/skin/4.jpg"></div> </div> <div class="login-on"> <div class="login-txt"><input type="text" id="username" placeholder="QQ号码/手机/邮箱"><input id="password" type="password" placeholder="密码"></div> <div class="login-xuan"><span class="fl"><input type="checkbox"><i>记住密码</i></span><span class="fr"><input type="checkbox"><i>自动登录</i></span></div> <div class="login-but" id="login-but">安全登录</div> </div> <div class="login-right"> <a href="" target="_blank">注册账号</a><a href=";aquin=" target="_blank">找回密码</a> </div></div></div></script><script>layui.use('layim', function(){ var layim = layui.layim,id; $ = layui.jquery, layer.open({ title:false ,type: 1 ,offset: 'auto' //具体配置参考: ,content: login_html.innerHTML ,btn: false ,shadeClose: false ,closeBtn: 0 ,moveType: 0 ,move: '.login-head' ,btnAlign: 'r' //按钮居中 ,shade: 0 //不显示遮罩 }); //绑定登陆事件 $(document).on('click', '#login-but', function(data) { login(); }); var tips= $('#login-tips'); function login(){ tips.html("正在登陆……"); var un= $("#username").val(); var ps= $("#password").val(); var d={"m":"login","username":un,"password":ps} $.ajax({ type:"POST", url:"../chat", dataType:"json", data:d, success:function(data){ if(data.code==20000){ id=data.data.id tips.html("登陆成功"); chartSetting(); connection(); }else{ tips.html("登陆失败请重试!"+data.msg); } }, error:function(jqXHR){ tips.html("发生错误"+jqXHR.status); } }); } function chartSetting(){ //基础配置 layim.config({ //初始化接口 init: { url: 'chat?m=list&id='+id ,data: {} } //查看群员接口 ,members: { url: 'chat?m=getMembers&id='+id ,data: {} } ,uploadImage: { url: 'uploadv2?filepath=' //(返回的数据格式见下文) ,type: '' //默认post } ,uploadFile: { url: 'uploadv2?filepath=' //(返回的数据格式见下文) ,type: '' //默认post } ,isAudio: true //开启聊天工具栏音频 ,isVideo: true //开启聊天工具栏视频 //扩展工具栏 ,tool: [{ alias: 'code' ,title: '代码' ,icon: '' }] ,brief: false //是否简约模式(若开启则不显示主面板) ,title: '消息' //自定义主面板最小化时的标题 ,right: '10px' //主面板相对浏览器右侧距离 ,minRight: '90px' //聊天面板最小化时相对浏览器右侧距离 ,initSkin: '3.jpg' //1-5 设置初始背景 ,skin: ['js/css/modules/layim/skin/6.jpg', 'js/css/modules/layim/skin/1.jpg'] //新增皮肤 ,isfriend: true //是否开启好友 ,isgroup: true //是否开启群组 ,min: false //是否始终最小化主面板,默认false ,notice: true //是否开启桌面消息提醒,默认false ,voice: true //声音提醒,默认开启,声音文件为:default.mp3 ,msgbox: 'msgbox.html' //消息盒子页面地址,若不开启,剔除该项即可 ,find: 'find.html' //发现页面地址,若不开启,剔除该项即可 ,chatLog: 'chatlog.html' //聊天记录页面地址,若不开启,剔除该项即可 }); } //监听在线状态的切换事件 layim.on('online', function(status){ layer.msg(status); }); //演示自动回复 var autoReplay = [ '您好,我现在有事不在,一会再和您联系。', '你没发错吧?face[微笑] ', '洗澡中,请勿打扰,偷窥请购票,个体四十,团体八折,订票电话:一般人我不告诉他!face[哈哈] ', '你好,我是主人的美女秘书,有什么事就跟我说吧,等他回来我会转告他的。face[心] face[心] face[心] ', 'face[威武] face[威武] face[威武] face[威武] ', '<(@ ̄︶ ̄@)>', '你要和我说话?你真的要和我说话?你确定自己想说吗?你一定非说不可吗?那你说吧,这是自动回复。', 'face[黑线] 你慢慢说,别急……', '(*^__^*) face[嘻嘻] ,是贤心吗?' ]; //监听在线状态的切换事件 layim.on('online', function(status){ layer.msg(status); }); //监听签名修改 layim.on('sign', function(value){ layer.msg(value); }); //监听自定义工具栏点击,以添加代码为例 layim.on('tool(code)', function(insert){ layer.prompt({ title: '插入代码 - 工具栏扩展示例' ,formType: 2 ,shade: 0 }, function(text, index){ layer.close(index); insert('[pre class=layui-code]' + text + '[/pre]'); //将内容插入到编辑器 }); }); //监听layim建立就绪 layim.on('ready', function(res){ //console.log(res.mine); layim.msgbox(5); //模拟消息盒子有新消息,实际使用时,一般是动态获得 }); //监听发送消息 layim.on('sendMessage', function(data){ var To = data.to; var Me = data.mine; if(To.type === 'friend'){ layim.setChatStatus('<span style="color:#FF5722;">对方正在输入。。。</span>'); } if(To.id==Me.id){ alert("无法和自己发起聊天"); return; }else{ var data={ username: Me.username //消息来源用户名 ,avatar: Me.avatar //消息来源用户头像 ,id: To.id //消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id) ,type:To.type //聊天窗口来源类型,从发送消息传递的to里面获取 ,content: Me.content //消息内容 ,cid: 0 //消息id,可不传。除非你要对消息进行一些操作(如撤回) ,mine: false //是否我发送的消息,如果为true,则会显示在右方 ,fromid:Me.id //消息的发送者id(比如群组中的某个消息发送者),可用于自动解决浏览器多窗口时的一些问题 ,timestamp:new Date().getTime() //服务端时间戳毫秒数。注意:如果你返回的是标准的 unix 时间戳,记得要 *1000 }; //模拟系统消息 websocket.send(JSON.stringify(data)); layim.setChatStatus('<span style="color:#FF5722;">在线</span>'); } }); //监听查看群员 layim.on('members', function(data){ //console.log(data); }); //监听聊天窗口的切换 layim.on('chatChange', function(res){ var type = res.data.type; console.log(res.data.id) if(type === 'friend'){ //模拟标注好友状态 layim.setChatStatus('<span style="color:#FF5722;">在线</span>'); } else if(type === 'group'){ //模拟系统消息 layim.getMessage({ system: true ,id: res.data.id ,type: "group" ,content: '模拟群员'+(Math.random()*100|0) + '加入群聊' }); } }); function connection(){ tips.html("开始连接服务……"); if('WebSocket' in window){ websocket = new WebSocket("ws://"+sy()+"/imchat/"+id); }else{ tips.html("不支持websocket"); } //连接发生错误的回调方法 websocket.onerror = function(ev,data){ tips.html("连接发生错误的回调方法"); }; //连接成功建立的回调方法 websocket.onopen = function(e){ tips.html(""); }; //接收到消息的回调方法 websocket.onmessage = function(event){ // layim.getMessage(event.data); var json=JSON.parse(event.data); console.log("接收信息:"); console.log(event.data); if(json.key=="offline"){ //用户离线 layim.setFriendStatus(json.id, 'offline'); layim.setChatStatus('<span style="color:gray;">离线</span>'); layer.msg(json.content+"无法接收到消息", { icon: 1 }); }else if(json.key=="online"){ //接收在线消息 //制造好友消息 layim.setFriendStatus(json.id, 'online'); layim.setChatStatus('<span style="color:#FF5722;">在线</span>'); layim.getMessage(json); } }; //连接关闭的回调方法 websocket.onclose = function(event){ //alert('连接关闭的回调方法'); tips.html("连接已关闭,尝试重连……"); disConnect(); }; //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function(){ websocket.close(); }; }//检查链接,短线重连 var disConnect = function(){ setTimeout(function(){ connection(); },5000); } //关闭连接 function closeWebSocket(){ tips.html("关闭closeWebSocket"); websocket.close(); } function sy(){ var curWwwPath = window.document.location.href; var pathName = window.document.location.pathname; var pos = curWwwPath.indexOf(pathName); var localhostPaht = curWwwPath.substring(0,pos); var projectName = pathName.substring(0,pathName.substr(1).indexOf('/')+1); var ip=window.location.host; var prot=window.location.port; return (ip + projectName); } });</script></body></html>
结合以上服务端代码和前端代码可以实现基本的聊天功能,本篇没涉及到登录接口和用户列表接口,下篇再做补充。需要的同学关注一下下篇。有问题欢迎留言指正
标签: #聊天窗口html