WebSocket的代码框架:
注意:
服务地址
ws://127.0.0.1:8080/demo/websocket/chat,ws:// 表示协议为websocket,与 http:// 区分javax.websocket.Session代表了一个客户端连接
客户端:chat.html
<html>
<head>
<meta charset="utf-8">
<title>title>
<script src="js/jquery.min.js" >script>
<script src="js/afquery.js" >script>
<link rel="stylesheet" href="css/common.css" />
head>
<body>
<div>
div>
body>
<script>
// 打开WebSocket
// ws://127.0.0.1:8080/demo/websocket/chat
var sock = new WebSocket("ws://" + location.host + "/demo/websocket/chat");
// 指定回调方法
//连接完成触发的回调方法
sock.onopen = function() {
console.log('Client Socket Open');
};
//连接关闭触发的回调方法
sock.onclose = function(event) {
console.log('Client Socket Closed');
};
//连接通讯发生异常触发的回调方法
sock.onerror = function(event) {
console.log('Client Socket Error');
};
//通讯过程中受到信息触发的回调方法
sock.onmessage = function(event) {
console.log('** Client Receive:' + event.data);
};
script>
html>
服务端:
package my;
import java.io.IOException;
import javax.websocket.EndpointConfig;
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("/websocket/chat")
public class ChatEndpoint
{
Session session; // 一个WebSocket连接,不是 HttpSession
public ChatEndpoint()
{
System.out.println("** 创建一个Endpoint...");
}
//连接完成触发的回调方法
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
this.session = session;
System.out.println("** 新的用户连接: ");
}
//通讯连接关闭触发的回调方法
@OnClose
public void onClose()
{
System.out.println("** 用户连接已关闭");
}
//通讯发生异常触发的回调方法
@OnError
public void onError(Session session, Throwable error)
{
System.out.println("** 用户连接出错");
error.printStackTrace();
}
//通讯过程中受到信息触发的回调方法
@OnMessage
public void onMessage(String message, Session session)
{
System.out.println("用户消息:" + message);
try
{
session.getBasicRemote().sendText("OK, welcome");
} catch (IOException e)
{
e.printStackTrace();
}
}
}
不需要记住这些代码,但是要记住发生的四个回调事件。
<html>
<head>
<meta charset="utf-8">
<title>title>
<script src="js/jquery.min.js" >script>
<script src="js/afquery.js" >script>
<link rel="stylesheet" href="css/common.css" />
<style>
.container{
margin: 30px auto auto auto;
width: 800px;
}
.line{
margin: 10px 0;
}
.message{
width: calc(100% - 80px);
padding: 6px;
}
.chatroot{
width: 100%;
height: 300px;
font-size: 15px;
line-height: 130%;
}
style>
head>
<body>
<div class='container'>
<div class='line'>
<input type='text' class='message'>
<button onclick='my.doSend()'> 发送 button>
div>
<div class='line'>
<textarea class='chatroot'>textarea>
div>
div>
body>
<script>
// 打开WebSocket
// ws://127.0.0.1:8080/demo/websocket/chat
var sock = new WebSocket("ws://" + location.host + "/demo/websocket/chat");
// 指定回调方法
sock.onopen = function() {
console.log('Client Socket Open');
};
sock.onclose = function(event) {
console.log('Client Socket Closed');
};
sock.onerror = function(event) {
console.log('Client Socket Error');
};
sock.onmessage = function(event) {
console.log('** Client Receive:' + event.data);
my.showMessage('<< ' + event.data);
};
var my = {};
// 发送
my.doSend = function(){
var message = $('.message').val();
my.showMessage('>> ' + message );
// 发送消息
sock.send( message );
}
// 显示消息
my.showMessage = function(message){
$('.chatroot').append(message + '\n');
}
script>
html>
```java
package my;
import java.io.IOException;
import javax.websocket.EndpointConfig;
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("/websocket/chat")
public class ChatEndpoint
{
Session session; // 一个WebSocket连接,不是 HttpSession
public ChatEndpoint()
{
System.out.println("** 创建一个Endpoint...");
}
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
this.session = session;
System.out.println("** 新的用户连接: ");
}
@OnClose
public void onClose()
{
System.out.println("** 用户连接已关闭");
}
@OnError
public void onError(Session session, Throwable error)
{
System.out.println("** 用户连接出错");
error.printStackTrace();
}
@OnMessage
public void onMessage(String message, Session session)
{
System.out.println("用户消息:" + message);
try
{
session.getBasicRemote().sendText("OK, welcome");
} catch (IOException e)
{
e.printStackTrace();
}
}
}
思路如下:
添加一个管理器 ChatRoom ,由它维护所有的客户端列表
chat.html
<html>
<head>
<meta charset="utf-8">
<title>title>
<script src="js/jquery.min.js" >script>
<script src="js/afquery.js" >script>
<link rel="stylesheet" href="css/common.css" />
<style>
.container{
margin: 30px auto auto auto;
width: 800px;
}
.line{
margin: 10px 0;
}
.message{
width: calc(100% - 80px);
padding: 6px;
}
.chatroot{
width: 100%;
height: 300px;
font-size: 15px;
line-height: 130%;
}
style>
head>
<body>
<div class='container'>
<div class='line'>
<input type='text' class='message'>
<button onclick='my.doSend()'> 发送 button>
div>
<div class='line'>
<textarea class='chatroot'>textarea>
div>
div>
body>
<script>
// 打开WebSocket
// ws://127.0.0.1:8080/demo/websocket/chat
var sock = new WebSocket("ws://" + location.host + "/demo/websocket/chat");
// 指定回调方法
sock.onopen = function() {
console.log('Client Socket Open');
};
sock.onclose = function(event) {
console.log('Client Socket Closed');
};
sock.onerror = function(event) {
console.log('Client Socket Error');
};
sock.onmessage = function(event) {
console.log('** Client Receive:' + event.data);
my.showMessage(': ' + event.data);
};
var my = {};
// 发送
my.doSend = function(){
var message = $('.message').val();
// my.showMessage('>> ' + message );
$('.message').val(''); // 清空输入
// 发送消息
sock.send( message );
}
// 显示消息
my.showMessage = function(message){
$('.chatroot').append(message + '\n');
}
script>
html>
聊天室ChatRoom.java
package my;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class ChatRoom
{
// 创建全局实例 ChatRoom.i
public static ChatRoom i = new ChatRoom();
// 客户端的列表
private List<ChatEndpoint> clientList = new LinkedList<>();
// 当有客户端连接时,将此客户端添加到 clientList
public void addClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.add(client);
}
}
// 当有客户端断开时,将此客户端从 clientList 移除
public void removeClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.remove(client);
}
}
// 发给所有人
public void sendAll(String message)
{
// 1 并发问题
// 2 效率问题
synchronized (clientList)
{
Iterator iter = clientList.iterator();
while(iter.hasNext())
{
ChatEndpoint client = (ChatEndpoint)iter.next();
client.sendMessage( message );
}
}
}
}
长连接类ChatEndpoint.java
package my;
import java.io.IOException;
import javax.websocket.EndpointConfig;
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("/websocket/chat")
public class ChatEndpoint
{
Session session; // 一个WebSocket连接,不是 HttpSession
public ChatEndpoint()
{
System.out.println("** 创建一个Endpoint...");
}
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
this.session = session;
ChatRoom.i.addClient(this);
System.out.println("** 新的用户连接: ");
}
@OnClose
public void onClose()
{
ChatRoom.i.removeClient(this);
System.out.println("** 用户连接已关闭");
}
@OnError
public void onError(Session session, Throwable error)
{
ChatRoom.i.removeClient(this);
System.out.println("** 用户连接出错");
error.printStackTrace();
}
@OnMessage
public void onMessage(String message, Session session)
{
System.out.println("用户消息:" + message);
ChatRoom.i.sendAll(message);
}
public void sendMessage(String message)
{
try
{
session.getBasicRemote().sendText(message);
} catch (IOException e)
{
e.printStackTrace();
}
}
}
public class ChatEndpointConfig extends Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig sec
, HandshakeRequest request
, HandshakeResponse response)
{
// 把 HttpSession 放到 ServerEndpointConfig 中
HttpSession httpSession = (HttpSession) request.getHttpSession();
sec.getUserProperties().put("httpSession", httpSession);
}
}
@ServerEndpoint(value="/websocket/chat", configurator=ChatEndpointConfig.class)
public class ChatEndpoint
{
}
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
// 从 EndpointConfig 获取当前 HttpSession对象
HttpSession httpSession =(HttpSession)config.getUserProperties().get("httpSession");
}
客户端登录页面login.html,直接复制即可;
<html>
<head>
<meta charset="utf-8">
<title>在线聊天室title>
<script th:src="@{/js/jquery.min.js}" >script>
<script th:src="@{/js/afquery.js}" >script>
<link rel="stylesheet" th:href="@{/css/common.css}" />
<style>
.container{
margin: 30px auto auto auto;
width: 500px;
}
.form{
background:#fcfcfc;
padding: 10px;
}
.line{
margin: 10px 0;
}
.label{
display:inline-block;
width: 80px;
}
.line input{
width: 300px;
}
style>
head>
<body>
<div class='container form' >
<div class='line'>
登录聊天室
div>
<div class='line'>
<span class='label'>用户名span>
<input type='text' class='user' >
div>
<div class='line'>
<span class='label'>密码span>
<input type='password' class='password' >
div>
<div class='line' style='margin-top: 40px'>
<button onclick='my.login()'> 登录 button>
div>
div>
body>
<script>
var my = {};
// 用户登录
my.login = function(){
var req = {};
req.user = $('.form .user').val().trim();
req.password = $('.form .password').val().trim();
Af.rest('[[@{/login.do}]]' , req, function(data){
location.href = '[[@{/chatroom}]]' ;
})
}
script>
html>
客户端聊天室页面chatroom.html,直接复制即可;
<html>
<head>
<meta charset="utf-8">
<title>在线聊天室title>
<script th:src="@{/js/jquery.min.js}" >script>
<script th:src="@{/js/afquery.js}" >script>
<link rel="stylesheet" th:href="@{/css/common.css}" />
<style>
.container{
margin: 30px auto auto auto;
width: 800px;
}
.line{
margin: 10px 0;
}
.message{
width: calc(100% - 80px);
padding: 6px;
}
.chatroot{
width: 100%;
height: 300px;
font-size: 15px;
line-height: 130%;
}
style>
head>
<body>
<div class='container'>
<div class='line'>
当前用户: <span th:text='${session.user}' style='color:blue'>span>
div>
<div class='line'>
<input type='text' class='message'>
<button onclick='my.doSend()'> 发送 button>
div>
<div class='line'>
<textarea class='chatroot'>textarea>
div>
div>
body>
<script>
// 打开WebSocket
// ws://127.0.0.1:8080/demo/websocket/chat
var sock = new WebSocket("ws://" + location.host + "/demo/websocket/chat");
// 指定回调方法
sock.onopen = function() {
console.log('Client Socket Open');
};
sock.onclose = function(event) {
console.log('Client Socket Closed');
};
sock.onerror = function(event) {
console.log('Client Socket Error');
};
sock.onmessage = function(event) {
console.log('** Client Receive:' + event.data);
my.showMessage(event.data);
};
var my = {};
// 发送
my.doSend = function(){
var message = $('.message').val();
// my.showMessage('>> ' + message );
$('.message').val(''); // 清空输入
// 发送消息
sock.send( message );
}
// 显示消息
my.showMessage = function(message){
$('.chatroot').append(message + '\n');
}
script>
html>
登录验证类LoginController
package my;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.alibaba.fastjson.JSONObject;
import af.spring.AfRestData;
@Controller
public class LoginController
{
@GetMapping("/login")
public String login()
{
return "login";
}
@PostMapping("/login.do")
public Object login(@RequestBody JSONObject jreq
, HttpSession session) throws Exception
{
// 登录
String user = jreq.getString("user");
session.setAttribute("user", user);
return new AfRestData("");
}
}
服务类EndPoint
package my;
import java.io.IOException;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
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(value="/websocket/chat", configurator=ChatEndpointConfig.class)
public class ChatEndpoint
{
Session session; // 一个WebSocket连接,不是 HttpSession
String user; // 当前用户身份,考虑从 HttpSession中获取
public ChatEndpoint()
{
System.out.println("** 创建一个Endpoint...");
}
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
this.session = session;
ChatRoom.i.addClient(this);
System.out.println("** 新的用户连接: ");
// 从 EndpointConfig 获取当前 HttpSession对象
HttpSession httpSession = (HttpSession)config.getUserProperties().get("httpSession");
this.user = (String)httpSession.getAttribute("user");
if(user == null) user = "未登录";
}
@OnClose
public void onClose()
{
ChatRoom.i.removeClient(this);
System.out.println("** 用户连接已关闭");
}
@OnError
public void onError(Session session, Throwable error)
{
ChatRoom.i.removeClient(this);
System.out.println("** 用户连接出错");
error.printStackTrace();
}
@OnMessage
public void onMessage(String message, Session session)
{
System.out.println("用户消息:" + message);
ChatRoom.i.sendAll(user + ": " + message);
}
public void sendMessage(String message)
{
try
{
session.getBasicRemote().sendText(message);
} catch (IOException e)
{
e.printStackTrace();
}
}
}
配置类Configurator:
public class ChatEndpointConfig extends Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig sec
, HandshakeRequest request
, HandshakeResponse response)
{
// 把 HttpSession 放到 ServerEndpointConfig 中
HttpSession httpSession = (HttpSession) request.getHttpSession();
sec.getUserProperties().put("httpSession", httpSession);
}
}
聊天室类ChatRoom.java
package my;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class ChatRoom
{
// 创建全局实例 ChatRoom.i
public static ChatRoom i = new ChatRoom();
// 客户端的列表
private List<ChatEndpoint> clientList = new LinkedList<>();
// 当有客户端连接时,将此客户端添加到 clientList
public void addClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.add(client);
}
}
// 当有客户端断开时,将此客户端从 clientList 移除
public void removeClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.remove(client);
}
}
// 发给所有人
public void sendAll(String message)
{
// 1 并发问题
// 2 效率问题
synchronized (clientList)
{
Iterator iter = clientList.iterator();
while(iter.hasNext())
{
ChatEndpoint client = (ChatEndpoint)iter.next();
client.sendMessage( message );
}
}
}
}
至此聊天室代码基本完工了。剩下效率问题没搞定;
分析用户的操作:
这种场景就是典型的读多写少,且数据一致性不会那么严苛。
package my;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class ChatRoom
{
// 创建全局实例 ChatRoom.i
public static ChatRoom i = new ChatRoom();
// 客户端的列表
private List<ChatEndpoint> clientList = new LinkedList<>();
private List<ChatEndpoint> clientListCopy = new LinkedList<>();;
// 当有客户端连接时,将此客户端添加到 clientList
public void addClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.add(client);
// 生成一份新的拷贝
clientListCopy = new LinkedList<>( clientList);
}
}
// 当有客户端断开时,将此客户端从 clientList 移除
public void removeClient(ChatEndpoint client)
{
synchronized (clientList)
{
clientList.remove(client);
// 生成一份新的拷贝
clientListCopy = new LinkedList<>( clientList);
}
}
// 发给所有人
public void sendAll(String message)
{
// 1 并发问题
// 2 效率问题
Iterator iter = clientListCopy.iterator();
while(iter.hasNext())
{
ChatEndpoint client = (ChatEndpoint)iter.next();
client.sendMessage( message );
}
}
}
聊天室代码已经上传到资源里,想要的就去下载。