勿以恶小而为之,勿以善小而不为--------------------------刘备
劝诸君,多行善事积福报,莫作恶
上一章简单介绍了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("<");
break;
}
case '>':{
result.append(">");
break;
}
case '&':{
result.append("&");
break;
}
case '"':{
result.append(""");
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
谢谢您的观看!!!