Websocket聊天系统简要实现(实践篇)

chloe

一. Websocket

Websocket是一种新的网络协议,实现了浏览器与服务器全双工通信,比以前的长轮询的方式效率高很多。

二.实现

1.基本功能:聊天

首先在客户端定义websocket实体,并实现其四个函数(onopen ,onmessage ,onerror ,onclose)

var ws = null;
ws = new WebSocket("ws://" + location.host + "/工程名/xxx/{username}");//xxx后面再解释
ws.onopen = function (event) {log("WebSocket连接成功","OK");}    
ws.onmessage = function (event) {log(event.data);}//接收到消息的回调方法
ws.onerror = function(event){log("WebSocket连接错误","ERROR");}
ws.onclose = function (event){log("WebSocket连接关闭","OK");}

其中最重要是onmessage方法,因为这是接收服务器消息回调的方式。log函数是一个简单的显示文本的函数。

function sendData(message){
	ws.send(message);
	}

这是简单的上传数据的函数,只要发送键触发这个函数就行了。

接着讲服务端的。

服务端是基于注解的形式实现的websocket类,也是实现四个函数,与客户端的一一对应。首先第一句是下面这句(除了导包)

@ServerEndpoint(value="/xxx/{username}")

这个xxx是和客户端websocket实现的xxx是对应的。
{}的意思是可写可不写,如果不写的话记得要把最后的“/”去掉(卡在这好久。。)
username我是用session传递的,即session.setAttribute()

接下来实现Websocket类和四个函数


public class websocket {
	private static int onlineCount = 0;
	private Session session=null;
	private String username;
    private static Map<String, websocket> clients = new ConcurrentHashMap<String, websocket>(); 
    //这个Map是会话的集合,存储多个session,key为username


	@OnOpen
	public void OnOpen(@PathParam("username") String username,Session session){
		this.session = session;
		this.username = username; 
		clients.put(username, this);       addOnlineCount(); // 在线数加1
        System.out.println("有新连接加入!id为"+username + ",当前在线人数为" + getOnlineCount());
	}
	
	@OnClose
	public void onClose() {
	    subOnlineCount(); // 在线数减1
	    clients.remove(username); //从clients中删除
	    System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
	}
	
	@OnMessage
    public void onMessage(String message, Session session){
        System.out.println("来自客户端的消息:" + message);
        for( websocket item: clients.values()){              
    		try { 
    		item.session.getBasicRemote().sendText(message);//服务器向客户端发送数据
    		} catch (IOException e) { 
    			e.printStackTrace(); 
    			continue;} 	
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
    	websocket.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
    	websocket.onlineCount--;
    }

	
	

好了,现在我们实现了基本的聊天室聊天了。

2.点对点聊天

点对点聊天其实很简单。主要是对onMessage函数和传递方式进行一些修改。
首先我们传递数据要用json类型,且包含一个“to”

var msg = {
    	type: "message",
    	text: document.getElementById("sendData").value,
    	id:   "<%=name%>",
    	to: s,
    	}

s是一个变量,想给谁发信息只用修改s值就行了。

onMessage函数需要用两个函数实现, 一个是给“All”(所有人)发信息,一个是给“to”(某人)发信息。服务端通过遍历Map的username来实现。

@OnMessage
    public void onMessage(String message, Session session){
        System.out.println("来自客户端的消息:" + message);
        if(message.indexOf("{")!= -1 && message.indexOf("}")!= -1 && JSONObject.fromObject(message) != null){
        	JSONObject Jmsg = JSONObject.fromObject(message);
        	if(Jmsg.get("to").equals("All")){
        		onMessageAll(message, session);
        	}else{
        		onMessageA(message,(String)Jmsg.get("to"));
        	}      	
        }
        else{
        	System.out.println("发生错误");
        	onClose();
        } 
    }
    
    public void onMessageAll(String message, Session session){
    	for( websocket item: clients.values()){              
    		try { 
    				JSONObject Jmsg = JSONObject.fromObject(message);
    				JSONObject msg =  new JSONObject();
    				msg.put("text", (String) Jmsg.get("text"));
    				msg.put("id",(String) Jmsg.get("id"));
    				msg.put("type", (String) Jmsg.get("type"));
    				msg.put("to","All");
    				item.session.getBasicRemote().sendText(msg.toString());
        	//this.session.getAsyncRemote().sendText(message);
    		} catch (IOException e) { 
    			e.printStackTrace(); 
    			continue;} 
    	}
    }

    public void onMessageA(String message, String To){
    	for( websocket item: clients.values()){
    		if(item.username.equals(To) ||item.username.equals(username)){
    			try { 
    				JSONObject Jmsg = JSONObject.fromObject(message);
    				JSONObject msg =  new JSONObject();
    				msg.put("text", (String) Jmsg.get("text"));
    				msg.put("id",(String) Jmsg.get("id"));
    				msg.put("type", (String) Jmsg.get("type"));
    				msg.put("to",To);
    				item.session.getBasicRemote().sendText(msg.toString());
    				//this.session.getAsyncRemote().sendText(message);
    			}catch(IOException e) { 
    			e.printStackTrace(); 
    			continue;}
    		}    		
    	}
    }
3.表情发送

我借鉴了微博聊天的做法。输入的是表情代号,然后通过正则表达式将代号转成图片。emojis是一个表情集合,遍历表情的name替换成<img>图片。

	var emojis = [{
        name: '微笑',
        src: 'emoji/0.png'
    }, {
        name: '呲牙',
        src: 'emoji/1.png'
    }, {
        name: '大笑',
        src: 'emoji/2.png'
    }]
    
	function JudgeEmoji(input) {
       	// 循环emojis配置表,匹配对应表情
       	emojis.forEach(function(emoji) {
        	input = filterEmoji(input, emoji);
        });
        return input;
    };
	
	function filterEmoji(input, emoji) {
    var regexp = new RegExp('\\[' + emoji.name + '\\]', 'gm');
    var result = input.replace(regexp, '<img class="emoji" src="' + emoji.src + '"/>');
    return result;
	}

接下来要处理表情框了,就是这个
在这里插入图片描述
选择好表情会自动在文本框输入代号。

先建一个div然后把图片一个个存进去。

<a class="aimg"  onclick="showimg()"><img src="emoji/img.png" style="width: 20px;height: 20px;"></a>					
					<div  id="imgcontent" style="display:none;">
						<li class="li" onclick="getimg('[微笑]')"><img src="emoji/0.png" class="pic" ></li>
						<li class="li" onclick="getimg('[呲牙]')"><img src="emoji/1.png" class="pic" ></li>
						<li class="li" onclick="getimg('[大笑]')"><img src="emoji/2.png" class="pic" ></li>
					</div>

然后创建点击事件,把内容加入文本框就好了。(不过有一个问题,它只能加在文本的最后面,不能根据光标位置填入,百度有解决方法,但我还没有看懂)

function showimg(){  
		 var e = document.getElementById('imgcontent');
		 if(e.style.display == 'none')
      		e.style.display = 'block';
   		else
     		e.style.display = 'none';
	}
	
	function getimg(string){
		document.getElementById("sendData").value += string ;
		var e = document.getElementById('imgcontent');
     	e.style.display = 'none';
     	document.getElementById('sendData').focus();
	}
4.图片发送

使用阅读器将图片读成base64形式,再转化为blob对象,通过json上传。


<script>
window.onload = function(){	
	// 抓取上传图片,转换代码结果,显示图片的dom
	var img_upload=document.getElementById("file");
	img_upload.addEventListener('change',readFile,false);
	}
	
function readFile(){
		var file=this.files[0];
		if(!/image\/\w+/.test(file.type)){
			alert("请确保文件为图像类型");
		return false;
		}	
		var reader=new FileReader();
		reader.readAsDataURL(file);
		reader.onload=function(){
		console.log(reader);
		var img_b64 = this.result; 
		var b = b64_blob(img_b64);//blob对象
		sendData3(b);
		
		}
	}
	
	function b64_blob(base64){
    	var base64Arr = base64.split(',');
    	var imgtype = '';
    	var base64String = '';
    	if(base64Arr.length > 1){
        	//如果是图片base64,去掉头信息
        	base64String = base64Arr[1];
        	imgtype = base64Arr[0].substring(base64Arr[0].indexOf(':')+1,base64Arr[0].indexOf(';'));
  		}
    	// 将base64解码
   		var bytes = atob(base64String);
    	//var bytes = base64;
    	var bytesCode = new ArrayBuffer(bytes.length);
     	// 转换为类型化数组
    	var byteArray = new Uint8Array(bytesCode);
    
    	// 将base64转换为ascii码
    	for (var i = 0; i < bytes.length; i++) {
        	byteArray[i] = bytes.charCodeAt(i);
    	}
   
    	// 生成Blob对象(文件对象)
    	return window.URL.createObjectURL(new Blob( [bytesCode] , {type : imgtype}));
	};

	function sendData3(data){//发送数据
    	var msg = {
    	type: "img",
    	text: data,
    	id:   "<%=name%>",
    	to: s};
    	ws.send(JSON.stringify(msg));
    }
</script>
<label title="选择图片"><input style="display: none;" type="file" name="file" id="file"  accept="image/*" /></label>

5.历史消息

利用localstorage保存对话。localstorage有两个元素,一个是key,一个是value。根据不同的对话框(与不同好友对话)设置不同的key保存对话到value里。

//输出函数
function log(Text,MessageType){
    document.getElementById(t).innerHTML = document.getElementById(t).innerHTML + "<br />"+JudgeEmoji(Text) + "<br />";//给标签赋值
    localStorage.setItem(t, document.getElementById(t).innerHTML);//更新localstorage   
    var LogContainer = document.getElementById(t);
    LogContainer.scrollTop = LogContainer.scrollHeight;
    }

在页面开始加载之后调用localstorage,为每一个文本框标签赋值。(加载历史记录)

 if(localStorage.getItem(name+"context")== null){localStorage.setItem(name, "<br >");}
    else{document.getElementById(name+"context").innerHTML = document.getElementById(name+"context").innerHTML+localStorage.getItem(name+"context")+"<br />";}

6.安全考虑

安全方面,首先实现了登录时username和password的SHA256加密,传到后端将数据库的数据SHA256加密后比对,比对成功就返回主界面,失败则返回登录窗口。

<script src="sha256.js"></script>
<script type="text/javascript">
	function encrypt(){
		myform.username.value = SHA256(myform.username.value);
		myform.password.value = SHA256(myform.password.value);
		return true;
	}
</script>
<form  name="myform" class="center" method="post" onsubmit="return encrypt()"  action="check_login.jsp" >
	<input type="text" placeholder="username" name="username" id="username">
	<input type="password" placeholder="password" name="password" >
	<button type="submit">Login😂😍😘</button>
	<button type="button" onclick="window.location.href = 'https://www.baidu.com/'">Register</button>
	</form>

其次是禁止未登录url直接访问页面,通过session传递的name是否为空来判断。

<%
if(session.getAttribute("name")== null){response.sendRedirect("login.jsp");
}String name = (String)session.getAttribute("name");

%>

害怕xss和sql注入。。。不太会防御

7.界面制作

其实前端还是做得比较一般的。。。。毕竟也第一次做html登陆界面。。。。
登录的背景图还是从必应那扒下来的。。。
利用:hover和:focus可以做出比较好看的鼠标动态变化:

form input {
			border: 1px solid rgba(255, 255, 255, 0.6);
  			background-color: rgba(255, 255, 255, 0.4);
  			width: 250px;
  			border-radius: 3px;
  			padding: 10px 15px;
  			margin: 0 auto 10px auto;
  			display: block;
  			text-align: center;
  			font-size: 18px;
  			color: #f5f5f5ee;
  			-webkit-transition-duration: 0.25s;
            transition-duration: 0.25s;
  			font-weight: 300;
			}
			
	form input:hover {
  			background-color: rgba(255, 255, 255, 0.75);
}
	form input:focus {
  			background-color: white;
  			width: 300px;
  			color: #708090;
}

利用animation可以做页面加载完毕时的动画效果。比如从无到有:

.tool{
    	background-color: rgba(255, 255, 255, 0.2);
    	border: 1px solid rgba(255, 255, 255, 0.0);
    	border-radius: 1px;
    	width: 700px;
    	animation:movetool 1.5s ;
        -webkit-animation:movetool 1.5s ;
    }
    @keyframes movetool
    {
    0% {opacity: 0;}
    25% {opacity: 0;}
    50% {opacity: 0;}
    75% {opacity: 0;}
    100% {opacity: 1;}
    }
    
    @-webkit-keyframes movetool /*Safari and Chrome*/
    {
    0% {opacity: 0;}
    25% {opacity: 0;}
    50% {opacity: 0;}
    75% {opacity: 0;}
    100% {opacity: 1;}
    }

或者从小到大等等,都比较有意思。

8.好友列表

目前实现的好友列表动态加载仅仅基于表层,还未连接数据库储存。(时间不够)
好友列表动态加载是指根据用户好友动态生成好友列表以及好友对话框。并且实现私聊。

首先是根据好友生成列表和对话框。定义names里面是用户的好友。(这个应该连接数据库查询的,但是时间不够,就先这样了)

var names = ["All","Alice","Bob"];

然后在window.onload函数里根据names创建对话框和列表

window.onload = function(){	
	var rightdiv = document.getElementsByClassName("people")[0];//class数组
 	var leftdiv = document.getElementsByClassName("outcontext")[0];//class数组
	for(var i = 0; i < names.length; i++){//遍历
	var name = names[i].toString();
    var div = document.createElement('div');    
    
   //创建好友对话框
    var ldiv = document.createElement('div');
    ldiv.className = "context";
    ldiv.id = name + "context";
    leftdiv.appendChild(ldiv);
   
   //创建好友列表
	div.className = "person";
	div.id = name;
	div.onclick=function(){getcontext(this.id);}
    var diiv = document.createElement('span');
    diiv.className = "name";
    diiv.innerText = name;
    div.appendChild(diiv);
    rightdiv.appendChild(div);
    }
   }

三.总结

其实把上面的代码糅合一下就差不多可以生成一个比较简单的聊天系统了。课设时间两星期到了,还有注册功能和添加好友功能还没实现,也是有点遗憾,就先做到这吧。以后有时间再来修改。

总的来说做得比我预期的要好一点点,毕竟我上web课 也没怎么听 不专心,所以选这个题目就大概都是要自己百度资料,自己学习。而且这个题目比数据库增删查改设置角色等等好玩多了。前端的话,如果我早一点找到一个好的框架就好了,就不用自己写那么久了 还丑 …(昨天才发现一个比较好的框架,以后有时间实现一下)

还有就是一个人做太辛苦了,白天做功能实现,晚上做页面美化,还要发挥想象力,不想再做系统了。。。

参考资料比较多
https://mmmaming.github.io/mmmaming.github.io/
https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
https://segmentfault.com/q/1010000017246580/
https://www.cnblogs.com/freud/p/8397934.html
https://blog.csdn.net/cxfly957/article/details/79297650/

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值