純javaAPI (只能在控制臺(tái)操作宇葱,沒有界面)
TestServerString
import java.net.ServerSocket;
import java.net.Socket;
public class TestServerString {
public static void main(String[] args) {
try{
//1.創(chuàng)建ServerSocket類型的對(duì)象并提供端口號(hào)
ServerSocket ss = new ServerSocket(8888);
//2.等待客戶端的連接請(qǐng)求瘦真,調(diào)用accept()方法
//實(shí)現(xiàn)服務(wù)器可以不斷地響應(yīng)客戶端的連接請(qǐng)求
while(true){
System.out.println("等待客戶端的連接請(qǐng)求...");
//當(dāng)沒有客戶端連接時(shí),則阻塞在accept()方法的調(diào)用這里
//只要有客戶端連接成功黍瞧,則阻塞解除
Socket s = ss.accept();
System.out.println("客戶端" + s.getInetAddress() + "連接成功诸尽!");
//每當(dāng)有一個(gè)客戶端連接成功,則開啟一個(gè)新的線程為之服務(wù)
new ServerThread(s).start();
}
//ss.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
TestClientString
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class TestClientString {
public static void main(String[] args) {
try{
//1.構(gòu)造Socket類型的對(duì)象并提供服務(wù)器的IP地址和端口號(hào)
//Socket s = new Socket("XDL-20170621QCO", 8888);
Socket s = new Socket("127.0.0.1", 8888);
System.out.println("連接服務(wù)器成功印颤!");
//2.使用輸入輸出流進(jìn)行通信
Scanner sc = new Scanner(System.in);
PrintStream ps = new PrintStream(s.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
while(true){
//提示用戶從鍵盤輸入要發(fā)送的內(nèi)容然后發(fā)送到服務(wù)器
System.out.println("請(qǐng)輸入要發(fā)送的內(nèi)容:");
String msg = sc.nextLine();
//讓客戶端向服務(wù)器發(fā)送字符串內(nèi)容"hello"
//ps.println("hello");
ps.println(msg);
System.out.println("客戶端發(fā)送數(shù)據(jù)成功您机!");
//當(dāng)客戶端向服務(wù)器發(fā)送"bye"后,則通信結(jié)束
if("bye".equalsIgnoreCase(msg)){
System.out.println("聊天結(jié)束");
break;
}
//實(shí)現(xiàn)客戶端接收服務(wù)器發(fā)來(lái)的消息
String answer = br.readLine();
System.out.println("服務(wù)器回發(fā)的內(nèi)容是:" + answer);
}
//3.關(guān)閉Socket
br.close();
ps.close();
sc.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
ServerThread
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ServerThread extends Thread {
private Socket s;
public ServerThread(Socket s){
this.s = s;
}
@Override
public void run(){
try{
//3.使用輸入輸出流進(jìn)行通信
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
PrintStream ps = new PrintStream(s.getOutputStream());
while(true){
//System.out.println("等待客戶端發(fā)送數(shù)據(jù)內(nèi)容...");
//接收客戶端發(fā)來(lái)的字符串內(nèi)容并打印出來(lái)
String str = br.readLine();
//System.out.println("服務(wù)器接收到的消息是:" + str); //hello
System.out.println("客戶端" + s.getInetAddress() + "說(shuō):" + str);
//當(dāng)服務(wù)器接收到客戶端發(fā)來(lái)的“bye”年局,則聊天結(jié)束
if("bye".equalsIgnoreCase(str)){
System.out.println("客戶端" + s.getInetAddress() + "已下線际看!");
break;
}
//當(dāng)服務(wù)器接收到客戶端發(fā)來(lái)的內(nèi)容向客戶端回發(fā)消息"I received!"
ps.println("I received!");
//System.out.println("成功回發(fā)消息!");
}
//4.關(guān)閉Socket
ps.close();
br.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
vue+原生js WebSocket + javaWebSocket API
1矢否、后端pom.xml導(dǎo)入依賴
<dependencies>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.2</version>
</dependency>
</dependencies>
2仲闽、后端TestServerWebSocket類
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
public class TestServerWebSocket extends WebSocketServer {
public TestServerWebSocket(int port) {
super(new InetSocketAddress(port));
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
System.out.println("客戶端連接已打開");
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("客戶端連接已關(guān)閉");
}
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("從客戶端接收到消息: " + message);
// 向所有連接的客戶端廣播消息
broadcast("hello");
}
@Override
public void onError(WebSocket conn, Exception ex) {
System.out.println("發(fā)生錯(cuò)誤");
ex.printStackTrace();
}
public static void main(String[] args) {
try {
TestServerWebSocket server = new TestServerWebSocket(8888);
server.start();
System.out.println("服務(wù)器啟動(dòng)在端口 8888");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onStart() {
}
}
3、vue + js原生WebSocket
<template>
<div>
<h1>WebSocket 實(shí)時(shí)數(shù)據(jù)</h1>
<ul>
<input v-model="input">
<button @click="sendMessage">發(fā)送</button>
<p>{{ message }}</p>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const socket = new WebSocket('ws://localhost:8888');
socket.addEventListener('open', (event) => {
console.log('WebSocket連接已打開', event);
});
socket.addEventListener('close', (event) => {
console.log('WebSocket連接已關(guān)閉', event);
});
socket.addEventListener('error', (event) => {
console.error('WebSocket發(fā)生錯(cuò)誤:', event);
});
let message = ref('')
socket.addEventListener('message', (event) => {
// message = JSON.parse(event.data);
message.value = event.data;
console.log(message)
});
let input = ref('')
function sendMessage() {
socket.send(input.value);
input.value = '';
}
</script>
springboot+websocket
依賴 pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
EchoChannel.java
import java.io.IOException;
import java.time.Instant;
import javax.websocket.CloseReason;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 使用 @ServerEndpoint 注解表示此類是一個(gè) WebSocket 端點(diǎn)
// 通過(guò) value 注解僵朗,指定 websocket 的路徑
@ServerEndpoint(value = "/channel/echo")
public class EchoChannel {
private static final Logger LOGGER = LoggerFactory.getLogger(EchoChannel.class);
private Session session;
// 收到消息
@OnMessage
public void onMessage(String message) throws IOException{
LOGGER.info("[websocket] 收到消息:id={}赖欣,message={}", this.session.getId(), message);
if (message.equalsIgnoreCase("bye")) {
// 由服務(wù)器主動(dòng)關(guān)閉連接屑彻。狀態(tài)碼為 NORMAL_CLOSURE(正常關(guān)閉)。
this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye"));;
return;
}
this.session.getAsyncRemote().sendText("["+ Instant.now().toEpochMilli() +"]" + new Random().nextInt());
}
// 連接打開
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig){
// 保存 session 到對(duì)象
this.session = session;
LOGGER.info("[websocket] 新的連接:id={}", this.session.getId());
}
// 連接關(guān)閉
@OnClose
public void onClose(CloseReason closeReason){
LOGGER.info("[websocket] 連接斷開:id={}顶吮,reason={}", this.session.getId(),closeReason);
}
// 連接異常
@OnError
public void onError(Throwable throwable) throws IOException {
LOGGER.info("[websocket] 連接異常:id={}社牲,throwable={}", this.session.getId(), throwable.getMessage());
// 關(guān)閉連接。狀態(tài)碼為 UNEXPECTED_CONDITION(意料之外的異常)
this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));
}
}
WebSocketConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
ServerEndpointExporter exporter = new ServerEndpointExporter();
// 手動(dòng)注冊(cè) WebSocket 端點(diǎn)
exporter.setAnnotatedEndpointClasses(EchoChannel.class);
return exporter;
}
}
前端vue(純?cè)鷍s websocket)
<template>
<div>
<h1>WebSocket 實(shí)時(shí)通信</h1>
<ul>
<li v-for="(msg, index) in getMsg" :key="index">{{ msg }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 用來(lái)接收后端返回?cái)?shù)據(jù)
let getMsg = ref([])
let websocket = new WebSocket("ws://localhost:8080/channel/echo");
// 連接斷開
websocket.onclose = e => {
console.log(`連接關(guān)閉: code=${e.code}, reason=${e.reason}`)
}
// 收到消息
websocket.onmessage = e => {
console.log(`收到消息:${e.data}`);
// 接收后端發(fā)送的消息
getMsg.value.push(e.data)
}
// 異常
websocket.onerror = e => {
console.log("連接異常")
console.error(e)
}
// 連接打開
websocket.onopen = e => {
console.log("連接打開", e);
// 創(chuàng)建連接后悴了,往服務(wù)器沒隔一秒連續(xù)寫入1條消息
setInterval(sentData, 1000);
function sentData() {
websocket.send("hello")
}
// 最后發(fā)送 bye膳沽,由服務(wù)器斷開連接
// websocket.send("bye");
// 也可以由客戶端主動(dòng)斷開
// websocket.close();
}
</script>
以上代碼后端是廣播方式發(fā)送消息,想實(shí)現(xiàn)指定客戶端返回?cái)?shù)據(jù)让禀,稍微修改一下以上EchoChannel 代碼
@ServerEndpoint(value = "/channel/echo")
public class EchoChannel {
private static final Logger LOGGER = LoggerFactory.getLogger(EchoChannel.class);
private Session session;
//用來(lái)記錄不同客戶端的sessionID,區(qū)分不同客戶端
private static final Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();
// 收到消息
@OnMessage
public void onMessage(String message) throws IOException{
String clientId = getClientIdFromSession(session);
LOGGER.info("[websocket] 收到消息:clientId={}挑社,message={}", clientId, message);
if (message.equalsIgnoreCase("bye")) {
// 由服務(wù)器主動(dòng)關(guān)閉連接。狀態(tài)碼為 NORMAL_CLOSURE(正常關(guān)閉)巡揍。
this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye"));
return;
}
System.out.println(clientId);
if (clientId.equals("[10]")) {
sendMessageToClient(clientId, "你是" + clientId + "號(hào)客戶端");
}
if (clientId.equals("[20]")) {
sendMessageToClient(clientId, "你好" + clientId + "號(hào)客戶端");
}
}
// 獲取客戶端id痛阻,用來(lái)識(shí)別是哪個(gè)客戶端發(fā)送的消息
private static String getClientIdFromSession(Session session) {
String query = session.getRequestParameterMap().get("clientId").toString();
return query != null ? query : "";
}
// 發(fā)送消息的邏輯
public static void sendMessageToClient(String clientId, String message) {
Session session = SESSION_MAP.get(clientId);
if (session != null && session.isOpen()) {
try {
session.getAsyncRemote().sendText(message);
LOGGER.info("[websocket] 發(fā)送給指定客戶端:clientId={}, message={}", clientId, message);
} catch (Exception e) {
LOGGER.error("[websocket] 發(fā)送消息異常:clientId={}, message={}, error={}", clientId, message, e.getMessage());
// 處理異常情況,可能需要移除無(wú)法通信的Session
}
} else {
LOGGER.warn("[websocket] 嘗試發(fā)送消息到不存在或已關(guān)閉的連接:clientId={}", clientId);
}
}
// 連接打開
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig){
// 假設(shè)這里你有方法獲取客戶端的唯一標(biāo)識(shí)腮敌,比如從session的屬性中獲取
String clientId = getClientIdFromSession(session); // 實(shí)現(xiàn)這個(gè)方法來(lái)獲取客戶端ID
// 保存 session 到對(duì)象
this.session = session;
SESSION_MAP.put(clientId, session);
LOGGER.info("[websocket] 新的連接:id={}, clientId={}", session.getId(), clientId);
}
// 連接關(guān)閉
@OnClose
public void onClose(CloseReason closeReason){
String clientId = getClientIdFromSession(this.session); // 同樣阱当,確保實(shí)現(xiàn)這個(gè)方法
SESSION_MAP.remove(clientId);
LOGGER.info("[websocket] 連接斷開:id={},reason={}, clientId={}", this.session.getId(), closeReason, clientId);
}
// 連接異常
@OnError
public void onError(Throwable throwable) throws IOException {
LOGGER.info("[websocket] 連接異常:id={}糜工,throwable={}", this.session.getId(), throwable.getMessage());
// 關(guān)閉連接弊添。狀態(tài)碼為 UNEXPECTED_CONDITION(意料之外的異常)
this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));
}
}
客戶端連接服務(wù)端的路徑需要加上參數(shù)clientId
"ws://localhost:8080/channel/echo?clientId=" + 10