具体代码已经放到github上面去了,有兴趣的朋友可以看一下:
https://github.com/chyw12798/websocket-chat
我们先实现自己向自己聊天的功能:
先创建一个SpringBoot项目,然后添加相应的依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
下载完依赖后,编写一个配置类WebSocketConfig。
@Configuration
public class WebSocketConfig {
/**
* 再来看服务端开发, java 定义了一套 javax.servlet-api, 一个 HttpServlet 就是一个 HTTP 服务。
* java websocket 并非基于 servlet-api 简单扩展, 而是新定义了一套 javax.websocket-api。
* 一个 websocket 服务对应一个 Endpoint。
* 与 ServletContext 对应, websocket-api 也定义了 WebSocketContainer, 而编程方式注册 websocket 的接口是继承自 WebSocketContainer 的 ServerContainer。
* 一个 websocket 可以接受并管理多个连接, 因此可被视作一个 server。
* 主流 servlet 容器都支持 websocket, 如 tomcat, jetty 等。
* 看 ServerContainer api 文档, 可从 ServletContext attribute 找到 ServerContainer。
**/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
然后定义一个消息类(这里的构造器、get、setter方法可以用lombok相应注解)。
@Builder // 建造者模式(具体看这个对象生成的样子)
public class Message {
private String userId;
private String message;
public Message() {
}
public Message(String userId, String message) {
this.userId = userId;
this.message = message;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
然后再创建一个服务类:
@ServerEndpoint("/test-one")
@Component
@Slf4j
public class MyOneToOneServer {
/**
* 用于存放所有在线客户端
**/
private static Map<String, Session> clients = new ConcurrentHashMap<>(); // ConcurrentHashMap细节
private Gson gson = new Gson();
@OnOpen
public void onOpen(Session session) {
log.info("有新的客户端上线:{}", session.getId());
clients.put(session.getId(), session);
}
@OnClose
public void onClose(Session session) {
String sessionId = session.getId();
log.info("有客户端离线:{}", sessionId);
clients.remove(sessionId);
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
if (clients.get(session.getId()) != null) {
clients.remove(session.getId());
}
}
@OnMessage
public void onMessage(Session session, String message) {
log.info("当前SessionId:{},收到客户端发来的消息:{}", session.getId(), message);
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后再 编写一个html文件内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
</head>
<body>
<script type="text/javascript">
var socket;
if(window.WebSocket) {
socket = new WebSocket("ws://localhost:8866/test-one"); // 与服务器建立websocket连接
socket.onmessage = function (event) { // 客户端收到服务端发送过来的消息时就会被调用
var ta = document.getElementById("responseText");
ta.value = ta.value + "\n" + event.data;
}
socket.onopen = function (event) {
var ta = document.getElementById("responseText");
ta.value = "连接开启!";
}
socket.onclose = function (event) {
var ta = document.getElementById("responseText");
ta.value = ta.value + "\n" + "连接关闭!";
}
} else {
alert('浏览器不支持WebSocket!')
}
function send(message) {
if (!window.WebSocket) {
return ;
}
if (socket.readyState == WebSocket.OPEN){
socket.send(message);
} else {
alert("连接尚未开启!");
}
}
</script>
<form onsubmit="return false;">
<textarea id="pushMessage" name="message" style="width: 400px; height: 200px;"></textarea>
<input type="button" value="发送数据" onclick="send(this.form.message.value)">
<h3>服务端输出:</h3>
<textarea id="responseText" style="width: 400px;height: 300px;"></textarea>
<input type="button" onclick="javascript: document.getElementById('responseText').value=''" value="清空内容">
</form>
</body>
</html>
最后的目录效果图:
然后启动WebSocketApplication类,启动完毕后,右击test.html,运行页面,出来的效果:
然后再上面的输入框里输入内容,点击发送,下面的服务端输出框就显示你输入的内容:
下面我们实现一下一对一聊天功能。
其他代码不变,只需要修改MyOneToOneServer类的代码即可:
@ServerEndpoint("/test-one")
@Component
@Slf4j
public class MyOneToOneServer {
/**
* 用于存放所有在线客户端
**/
private static Map<String, Session> clients = new ConcurrentHashMap<>(); // ConcurrentHashMap细节
private Gson gson = new Gson();
@OnOpen
public void onOpen(Session session){
log.info("有新的客户端上线:{}",session.getId());
clients.put(session.getId(), session);
}
@OnClose
public void onClose(Session session){
String sessionId = session.getId();
log.info("有客户端离线:{}", sessionId);
clients.remove(sessionId);
}
@OnError
public void onError(Session session,Throwable throwable){
throwable.printStackTrace();
if(clients.get(session.getId()) != null){
clients.remove(session.getId());
}
}
@OnMessage
public void onMessage(Session session,String message) {
log.info("当前SessionId:{},收到客户端发来的消息:{}",session.getId(),message);
this.sendTo(session.getId(),gson.fromJson(message, Message.class));
}
public void sendTo(String sessionId,Message message){
Session s = clients.get(message.getUserId());
if (s != null){
try {
s.getBasicRemote().sendText(sessionId+"发给你的消息:"+message.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后右击两次test.html,出现两个页面:
然后其中一个进行数据发送:
最后来个群发消息。
同样只需要改动MyOneToOneServer的代码:
@ServerEndpoint("/test-one")
@Component
@Slf4j
public class MyOneToOneServer {
/**
* 用于存放所有在线客户端
**/
private static Map<String, Session> clients = new ConcurrentHashMap<>(); // ConcurrentHashMap细节
private Gson gson = new Gson();
@OnOpen
public void onOpen(Session session){
log.info("有新的客户端上线:{}",session.getId());
clients.put(session.getId(), session);
}
@OnClose
public void onClose(Session session){
String sessionId = session.getId();
log.info("有客户端离线:{}", sessionId);
clients.remove(sessionId);
}
@OnError
public void onError(Session session,Throwable throwable){
throwable.printStackTrace();
if(clients.get(session.getId()) != null){
clients.remove(session.getId());
}
}
@OnMessage
public void onMessage(Session session,String message) {
log.info("当前SessionId:{},收到客户端发来的消息:{}",session.getId(),message);
this.sendAll(session.getId(),message);
}
public void sendTo(String sessionId,Message message){
Session s = clients.get(message.getUserId());
if (s != null){
try {
s.getBasicRemote().sendText(sessionId+"发给你的消息:"+message.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendAll(String sessionId,String message){
for(Map.Entry<String,Session> sessionEntry : clients.entrySet()){
if (!sessionEntry.getValue().getId().equals(sessionId)){
sessionEntry.getValue().getAsyncRemote().sendText(sessionId+"的群发消息:"+message);
} else {
sessionEntry.getValue().getAsyncRemote().sendText("我的群发消息:"+message);
}
}
}
}
打开三个浏览器,效果图:
参考:
https://yq.aliyun.com/articles/706878?spm=a2c4e.11155435.0.0.18a93312Q0aULD