1.基本概念
首先websocket是基于H5的一种通信。在网页中如果定时获取服务器端的实时数据,我们常采用long poll 和ajax轮询的方式。但是在轮询过程中,由于根本没有新数据的改变,而造成一种资源的浪费,同时也不能够保证数据的实时性。long poll是一种保持长连接的方式获取数据,但是需要进行头文件的各种校验,也是一种资源的浪费。
websocket完美的解决了这种两种方式的不足,首先能够保证数据的实时性,同时保证资源的完整利用,是网页和服务端的全双工通信,即可以接收来自网页端的消息,同时可以发送通知网页端。websocket还支持多种方式,本篇讨论java,C#(WPF)和vue,即服务端为java,客户端分别为vue和WPF来进行验证。
2.基本原理
websocket是基于TCP的一种通信,所以在建立通信之前首先需要建立TCP的一系列连接(三次握手等)。
服务端采用Springboot来实现,首先在pom.xml中添加WebSocect的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
接下来建立WebSocket并实现ServerEndPoint 我这里采用注解的方式
package com.koalin.rpc.websocket;
import com.utils.DateTimeUtils;
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.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @version 1.0
* @ClassName WebSocket
* @Author koalin
* @Description //TODO WebSocket的描述
* @Date 2019/12/24 23:27
*/
@ServerEndpoint("/koalin/websocket/{userName}")
@Component
public class WebSocket {
private Session session;
private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();
private static Map<String, Session> sessionPool = new HashMap<String, Session>();
volatile static boolean isRunning = false;
private String userName;
@OnOpen
public void onOpen(Session session, @PathParam(value = "userName") String userName) {
this.session = session;
this.userName = userName;
webSockets.add(this);
sessionPool.put(session.getId(), session);
System.out.println(userName + "【websocket消息】有新的连接,总数为:" + webSockets.size());
if (webSockets.size()==1) {
isRunning = true;
new Runnable() {
@Override
public void run() {
while (true) {
try {
for (WebSocket client :
webSockets) {
String msg = "Hello I am WebSocekt " + client.userName + "我的时间:" + DateTimeUtils.DateTimeYYYYMMDDHHMMSS(new Date());
client.session.getAsyncRemote().sendText(msg);
System.out.println(msg);
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
isRunning=false;
}
}
}
}.run();
}
}
@OnClose
public void onClose() {
if (webSockets.contains(this) ){
webSockets.remove(this);
System.out.println(this.userName+"【websocket消息】连接断开,总数为:" + webSockets.size());
}
}
@OnMessage
public void onMessage(String message,Session session) {
//System.out.println("【websocket消息】收到客户端消息:" + message);
System.out.println("【websocket消息】收到客户端消息:" + message);
}
// 此为广播消息
public void sendAllMessage(String message) {
for (WebSocket webSocket : webSockets) {
System.out.println("【websocket消息】广播消息:" + message);
try {
webSocket.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息
public void sendOneMessage(String userName, String message) {
System.out.println("【websocket消息】单点消息:" + message);
Session session = sessionPool.get(userName);
if (session != null) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
添加WebSocketConfig 创建默认的EndPointServer
package com.koalin.rpc.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @version 1.0 * @ClassName WebSocketConfig * @Author koalin * @Description //TODO WebSocketConfig的描述 * @Date 2019/12/24 23:36 */ @Configuration public class WebSocketConfig { /** * @return * @Author koalin * @Description //TODO这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint * @Date 22:47 2019/12/24 * @Param **/ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
以上完成服务端代码
接下来先验证C#客户端
首选在nuget中下载websocket-sharp
创建WebSocketClient
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using WebSocketSharp; namespace WebSocketLib { public class WebSocketClient { private WebSocket ws; private string url = ""; CancellationToken token = new CancellationToken(); public WebSocketClient(string url) { this.url = url; } public void Start() { try { ws = new WebSocket(url, token, 102392, () => {//OnOpen return Task.Run(() => { Console.WriteLine("websocket连接正常...."); }); }, (e) => {//OnClose return Task.Run(() => { Console.WriteLine("websocket关闭正常..."); }); }, (e) => {//OnMessage return Task.Run(() => { Console.WriteLine("接收到服务端的消息" + e.Text.ReadToEnd()); }); }, (e) => {//OnError return Task.Run(() => { Console.WriteLine("连接异常..." + e.Message); }); } ); ws.Connect(); } catch (Exception e) { Console.WriteLine(e.ToString()); } }
public void StartSendMessage()
{
{
Task.Run(async () =>
{
await Task.Delay(1000);
while (true)
{
try
{
if (ws != null )
{
{
await Task.Delay(1000);
while (true)
{
try
{
if (ws != null )
{
Task<bool> tast= ws.Send(("HI i am C# client"+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
Console.WriteLine(tast.Result);
}
}
catch (Exception ex)
{
}
}
catch (Exception ex)
{
}
await Task.Delay(2000);
}
});
}
public void Close() { if (ws!=null) { ws.Close(); ws.Dispose(); } } } }
简单的建立wpf窗体应用程序然后添加引用
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WebSocketDemo { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } WebSocketLib.WebSocketClient client = null; private void Test() { client = new WebSocketLib.WebSocketClient("ws://www.koalin.com:8081/koalin/websocket/test"); client.Start(); client.StartSendMessage(); } private void Button_Click(object sender, RoutedEventArgs e) { Test(); } protected override void OnClosed(EventArgs e) { if (client!=null) { client.Close(); } base.OnClosed(e); } } }
启动服务和客户端进行简单的验证
建立vue工程,然后添加如下websockt关键代码
initWebSocket () {
// 连接错误
this.websocket.onerror = this.setErrorMessage
// 连接成功
this.websocket.onopen = this.setOnopenMessage
// 收到消息的回调
this.websocket.onmessage = this.setOnmessageMessage
// 连接关闭的回调
this.websocket.onclose = this.setOncloseMessage
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = this.onbeforeunload
},
setErrorMessage () {
console.log('WebSocket连接发生错误 状态码:' + this.websocket.readyState)
},
setOnopenMessage () {
console.log('WebSocket连接成功 状态码:' + this.websocket.readyState)
},
setOnmessageMessage (event) {
// 根据服务器推送的消息做自己的业务处理
console.log('服务端返回:' + event.data)
},
setOncloseMessage () {
console.log('WebSocket连接关闭 状态码:' + this.websocket.readyState)
},
onbeforeunload () {
this.closeWebSocket()
},
closeWebSocket () {
this.websocket.close()
}
},
mounted() {
this.restaurants = this.loadAll();
// WebSocket
if ('WebSocket' in window) {
// var url='ws://www.koalin.com:8081/koalin/websocket/' + new Date();
this.websocket = new WebSocket('ws://www.koalin.com:8081/koalin/websocket/' + new Date());
console.log( this.websocket);
this.initWebSocket();
} else {
alert('当前浏览器 Not support websocket')
}
},
beforeDestroy () {
this.onbeforeunload()
}
启动vue服务。然后在网页中输入对应的链接。
完成简单的客户端与服务端的通信