客户端:
var ws = new WebSocket("ws://localhost:8080/websocket");
ws.onopen = function(event) {
console.log("Connection open ...");
};
ws.onmessage = function(event) {
console.log("Received Message: " + event.data);
};
ws.onclose = function(event) {
console.log("Connection closed ...");
};
ws.onerror = function(event) {
console.log("Error: " + event.data);
};
ws.send("Hello Server!");
ws.close();
可以直接使用js写个小页面
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">Send Message</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(){
setMessageInnerHTML("连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '
';
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
服务端:
导入WebSocket的maven坐标
org.springframework.boot
spring-boot-starter-websocket
导入WebSocket服务端组件WebSocketServer,用于与客户端通信
package com.sky.websocket;
import com.sky.handler.TurnoverReportVOEncoder;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket服务
*/
@Component
@ServerEndpoint(value = "/ws/{sid}",encoders = {TurnoverReportVOEncoder.class}) // 为对象指定编码器(目前是转成json发送给客户端)
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void sendObjToAllClient(Object object) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送对象--注意第4步骤,需要为该对象指定一个编码器
session.getBasicRemote().sendObject(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
导入配置类WebSocketConfiguration,注册WebSocket的服务端组件ServerEndpointExporter
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
如果想向客户端推送封装好的对象,在WebSocket中,需要提供一个编码器来将这个对象转换为可以通过网络传输的格式,通常是字符串或者二进制数据。
package com.sky.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sky.vo.TurnoverReportVO;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
/**
* @projectName: sky-take-out
* @package: com.sky.handler
* @className: TurnoverReportVOEncoder
* @author: fangjiayueyuan
* @description: TODO
* @date: 2023/12/24 16:16
* @version: 1.0
*/
public class TurnoverReportVOEncoder implements Encoder.Text<TurnoverReportVO>{
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public String encode(TurnoverReportVO turnoverReportVO) throws EncodeException {
try {
// 使用Jackson库将对象转换为JSON字符串
return objectMapper.writeValueAsString(turnoverReportVO);
} catch (Exception e) {
throw new EncodeException(turnoverReportVO, "对象转换为JSON字符串时发生错误", e);
}
}
@Override
public void init(EndpointConfig endpointConfig) {
// 这里可以进行编码器的初始化操作,但在这个例子中我们不需要进行任何操作
}
@Override
public void destroy() {
// 这里可以进行编码器的清理操作,但在这个例子中我们不需要进行任何操作
}
}
导入定时任务类WebSocketTask,定时向客户端推送数据
package com.sky.task;
import com.sky.service.ReportService;
import com.sky.vo.TurnoverReportVO;
import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
@Autowired
private ReportService reportService;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendObjMessageToClient() {
TurnoverReportVO turnoverStatistics = reportService.getTurnoverStatistics(LocalDate.parse("2023-01-01"), LocalDate.now());
webSocketServer.sendToAllClient("传个对象过去");
webSocketServer.sendObjToAllClient(turnoverStatistics);
}
}
**RPC(Remote Procedure Call)**是一种通信协议,它允许运行在一台计算机上的程序调用另一台计算机上的程序中的函数或方法,就像调用本地函数一样,无需程序员显式处理底层的网络细节。
RPC的主要特征包括:
透明性:对于调用者来说,远程过程调用和本地过程调用是透明的,调用者无需关心过程调用的是本地过程还是远程过程。
语言无关性:RPC通常支持多种编程语言,只要两个通信的程序遵循同一RPC协议,它们就可以进行通信,无论它们是用什么编程语言编写的。
同步性:RPC通常是同步的,也就是说,当一个RPC调用发出后,调用者会停止执行,直到得到结果。然而,也有一些RPC系统支持异步调用。
为什么使用RPC:
简化分布式系统的开发:RPC隐藏了底层的网络通信和数据传输的复杂性,使得开发分布式应用更加简单。
提高代码的可重用性:通过RPC,可以将一些通用的功能实现为服务,然后在多个应用中重用这些服务。
提高系统的可扩展性:通过RPC,可以将一个大的系统分解为多个可以独立开发和部署的小的服务。
RPC的替代方案:
以Thrift为例:
定义数据类型和服务接口:使用Thrift的IDL(接口定义语言)定义数据类型和服务接口,然后通过Thrift的编译器生成对应语言的代码。
namespace java com.sankuai.mdp.thrift
struct User{
1:i32 id
2:string name
3:i32 age=0
}
service UserService{
User getById(1:i32 id)
bool isExist(1:string name)
}
通过Thrift编译器生成Java代码:会生成两个对象:User、UserService
thrift --gen java HelloWorld.thrift
服务端代码,实现UserService.Iface接口;启动服务端.
package com.sankuai.mdp.thriftserversnapshot.service.impl;
import com.sankuai.mdp.thriftapisnapshot.entity.User;
import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
import org.apache.thrift.TException;
/**
* @projectName: thrift-api-snapshot
* @package: com.sankuai.mdp.thriftserversnapshot.service.impl
* @className: UserServiceImpl
* @author: fangjiayueyuan
* @description: TODO
* @date: 2023/12/17 21:33
* @version: 1.0
*/
public class UserServiceImpl implements UserService.Iface{
@Override
public User getById(int id) throws TException {
System.out.println("-----调用getById-----");
User user = new User();
user.setId(id);
user.setName("dog");
user.setAge(18);
return user;
}
@Override
public boolean isExist(String name) throws TException {
return false;
}
}
package com.sankuai.mdp.thriftserversnapshot.service.impl;
import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
/**
* @projectName: thrift-api-snapshot
* @package: com.sankuai.mdp.thriftserversnapshot.service.impl
* @className: SimpleService
* @author: fangjiayueyuan
* @description: TODO
* @date: 2023/12/17 21:59
* @version: 1.0
*/
public class SimpleService {
public static void main(String[] args) {
try{
TServerTransport serverTransport = new TServerSocket(9090);
UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
TSimpleServer.Args targs = new TSimpleServer.Args(serverTransport);
targs.processor(processor);
targs.protocolFactory(protocolFactory);
TServer server = new TSimpleServer(targs);
server.serve();
} catch (TTransportException e) {
throw new RuntimeException(e);
}
}
}
客户端代码,调用服务端的方法,就像调用本地方法一样
package com.sankuai.mdp.thriftclientsnapshot.service.impl;
import com.sankuai.mdp.thriftapisnapshot.entity.User;
import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
/**
* @projectName: thrift-api-snapshot
* @package: com.sankuai.mdp.thriftclientsnapshot.service.impl
* @className: SimpleClient
* @author: fangjiayueyuan
* @description: TODO
* @date: 2023/12/17 21:58
* @version: 1.0
*/
public class SimpleClient {
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("localhost", 9090);
TBinaryProtocol protocol = new TBinaryProtocol(transport);
UserService.Client client = new UserService.Client(protocol);
transport.open();
User result = client.getById(1);
System.out.println("Result:" + result);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
throw new RuntimeException(e);
} finally {
if (transport != null) {
transport.close();
}
}
}
}