Servlet实现WebSocket的简单聊天室(二)

勿以恶小而为之,勿以善小而不为--------------------------刘备

劝诸君,多行善事积福报,莫作恶

上一章简单介绍了WebSocket的了解(一),如果没有看过,请观看上一章

本文代码参考了李刚老师编写的 轻量级Java EE 企业应用实战(Struts2+Spring4+Hibernate 整合开发) 一书 中的"Servlet 3.0 的Web 模块支持" 章节代码。

一. Servlet 实现 WebSocket

Servlet 实现 WebSocket 需要 @ServerEndpoint 注解, 该注解可以实现Web Socket, 需要用 3.1 版本,Tomcat服务器最好是 8.0+ 版本。

通过@ServerEndPoint 注解的Servlet 类中需要提供四个方法, 服务器连接时操作,服务器断开时操作,服务器接收到客户端消息时操作,异常错误操作 来分别对应前端浏览器的四个事件。 这四个方法,要想起作用,需要分别添加 @OnOpen, @OnClose, @OnMessage,@OnError 四个注解。

即:

  • 被 @OnOpen 注解标识的方法,可以处理连接时操作
  • 被 @OnClose 注解标识的方法,可以处理 断开时操作
  • 被 @OnMessage 注解标识的方法, 可以处理接收消息时操作
  • 被 @OnError 注解标识的方法,可以处理异常错误时操作

二. Servlet 实现 WebSocket 的详细开发步骤

二.一 创建动态项目(Dynamic Web Project) WebSocket

Servlet 版本号是 3.1, Tomcat 版本号是 8.0

有图片

有图片

有图片

有图片

有图片

二.二 服务器端代码

创建 ChatEntPoint.java 类, 用 @ServerEndPoint 注解标记起来

package com.yjl.socket;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;


//用注解  @ServerEndPoint @ServerEndPoint 
@ServerEndpoint(value="/websocket/chat")
public class ChatEntPoint {
	
	//用于构建用户名称,是前缀    
	private static final String GUEST_PREFIX="访客";
	
	//定义后面的名称,是个不重复的索引值,从0开始。  AtomicInteger AtomicInteger 
	private static final AtomicInteger connectionIds=new AtomicInteger(0);
	
	//定义Set 集合,用于存放客户端连接, 不重复
	private static final Set<ChatEntPoint> clientSet=new CopyOnWriteArraySet<>();
	
	//显示的昵称
	private String nickName;
	
	//session 对象,用于接收 
	private Session session;
	
	//构造方法,初始化 每一个 ChatEntPoint 对象的  nickName,构建成 访问+数字的形式 
	public ChatEntPoint(){
		//后面生成一个不重复的数字 
		nickName=GUEST_PREFIX+connectionIds.getAndIncrement();
	}
	
	// OnOpen ,表示每一个客户端连接时的事件 
	@OnOpen
	public void start(Session session){
		//单独接收一个客户端与服务器端的Session 
		this.session=session;
		//添加到这里面
		clientSet.add(this);
		
		//设置消息
		String message=String.format("【%s %s 】",nickName,"加入聊天室");
		
		//用于发送消息
		broadcast(message);
		
	}
	//OnClose 注解, 每一个关闭时的事件
	@OnClose
	public void end(){
		//移除掉
		clientSet.remove(this);
		//格式化消息
		String message=String.format("【%s %s】",nickName,"离开聊天室");
		//发送消息
		broadcast(message);
	}
	//OnMessage, 接收客户端发送过来的消息的事件  OnMessage OnMessage 
	@OnMessage
	public void incoming(String message){
		String filteredMessage=String.format("【%s:%s】",nickName,filter(message));
		//发送消息
		broadcast(filteredMessage);
	}
	// 服务器端错误时的,事件处理
	@OnError
	public void onError(Throwable t) throws Throwable{
		
		System.out.println("WebScoket 服务端错误"+t.getMessage());
	}
	//发送消息的方法
	private static void broadcast(String message){
		//遍历每一个客户端
		for(ChatEntPoint client:clientSet){
			try{
				//同步操作
				synchronized(client){
					//通过 session.getBasicRemote() .sendText() 发送消息  getBasicRemote(). sendText() 
					client.session.getBasicRemote().sendText(message);
				}
			}catch(IOException e){
				System.out.println("聊天错误,向客户端 "+client+"发送消息出现错误 ");
				//移除这个不存在的客户端
				clientSet.remove(client);
				
				try{
					//关闭这个session 
					client.session.close();
				}catch(IOException e2){
					e2.printStackTrace();
				}
				//发送消息, 也检测一下,是否还有其他死客户端
				String msg=String.format("【%s %s 】",client.nickName,"已经断开连接");
				broadcast(msg);
			}
		}
	}
	//格式化前端传递过来的消息
	private static String filter(String message){
		if(null==message){
			return null;
		}
		//定义数据
		char[] content=new char[message.length()];
		
		//往 char 数据里面放置数据  
		message.getChars(0,message.length(), content, 0);
		
		//判断后,进行转换
		StringBuilder result=new StringBuilder(content.length+50);
		
		for(int i=0;i<content.length;i++){
			switch(content[i]){
			case '<':{
				result.append("&lt;");
				break;
			}
			case '>':{
				result.append("&gt;");
				break;
			}
			case '&':{
				result.append("&amp;");
				break;
			}
			case '"':{
				result.append("&quot;");
				break;
			}
			default:{
				result.append(content[i]);
			}
			}
		}
		return result.toString();
	}
	
 	
}

二.三 客户端代码

在 index.jsp 页面,添加客户端代码

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>主页</title>
</head>
<body>

<script>

//定义WebSocket 对象  new WebSocket () 协议是 ws 协议 
// 注意 url 的地址 ,暂时是固化了 
var webSocket=new WebSocket("ws://127.0.0.1:80/WebSocket/websocket/chat");

//发送消息
var sendMsg=function(){
	//获取元素
	var inputElement=document.getElementById("msg");
	//调用  webSocket.send(text) 方法,发送给服务器端消息  
	webSocket.send(inputElement.value);
	//清空输入框
	inputElement.value="";
}

//回车事件,发送消息 
var send=function(event){
	if(event.keyCode==13){
		sendMsg();
	}
}

//退出按钮事件
var closeWS=function(){
	//调用close 方法,进行关闭
	webSocket.close();
	
	//清空消息
	document.getElementById("show").innerHTML="";
	//清空输入框内的消息
	document.getElementById("msg").value="";
}

//webSocket.onopen 当连接时,绑定事件,避免出来未连接,就点击按钮.
webSocket.onopen=function(){
	//回车事件
	document.getElementById('msg').onkeydown=send;
	//发送按钮事件
	document.getElementById("sendBn").onclick=sendMsg;
	//退出事件
	document.getElementById("closeBn").onclick=closeWS;
	console.log("WebSocket 连接成功!!");
}
//接收消息 onmessage  
webSocket.onmessage=function(event){
	var show=document.getElementById("show");
	//event.data 用于获取消息   event.data 用于获取消息,并且拼装
	show.innerHTML+=event.data+"<br/>";
	//滚动条处理
	show.scrollTop=show.scrollHeight;
}
//webSocket 关闭 onclose 事件
webSocket.onclose=function(){
	//去掉事件
	document.getElementById("msg").onkeydown=null;
	document.getElementById("sendBn").onclick=null;
	//退出
	document.getElementById("closeBn").onclick=null;
	//提示已经被关闭了
	console.log("WebSocket 已经被关闭了!!");
}



</script>

<!--展示的div -->
<div style="width:600px;height:400px; overflow-y:auto;border:1px solid #333;" id="show">
</div>
<br/>
<!-- 填入内容的框 -->
<input type="text" size="80" id="msg" name="msg" placeholder="请输入聊天内容">
<!-- 发送按钮框 -->
<input type="button" value="发送" id="sendBn" name="sendBn">
<br/>
<br/>
<!-- 退出登录按钮框 -->
<input type="button" value="退出登录" id="closeBn" name="closeBn">

</body>
</html>

二.四 重启服务器,验证 WebSocket

操作1: 打开窗口1, 输入网址: http://localhost/WebSocket/

窗口1显示:

有图片

操作2: 再打开一个窗口2, 输入网址: http://localhost/WebSocket/

窗口2 显示:

有图片

此时,窗口1 显示:

有图片

操作3: 在窗口1发送消息, “你好,我是两个蝴蝶飞”,点击发送按钮

此时,窗口1显示:
有图片

窗口2显示:

有图片

可以发现,自动推送消息到各个客户端。

操作4: 再打开一个窗口3, 输入网址: http://localhost/WebSocket/

窗口3显示:

有图片

并不会接收到他参与之前的消息记录,保证了消息的隐私性。

窗口1 显示:

有图片

窗口2 显示:

有图片

操作5: 点击窗口2的 退出登录按钮,点击之后

此时窗口2 显示:

有图片

窗口1显示:

有图片

窗口3 显示:

有图片

操作6: 直接点击 窗口3的 X ,直接关闭网页:

窗口1 显示:

有图片

可以接收到 窗口3退出的事件

窗口2 已经被关闭了,所以没有任何接收信息。
在这里插入图片描述

本章节代码链接为:


链接:https://pan.baidu.com/s/1DIOzdoLIS-jhIwZKIq3sDA 
提取码:txrs 

谢谢您的观看!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

两个蝴蝶飞

你的鼓励,是老蝴蝶更努力写作的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值