后端 + 前端简单HTML页面
1. 群发,所有人都能收到
2. 局部群发,部分人群都能收到
3. 单点推送, 指定某个人的页面
惯例,先看看本次实战示例项目结构:
可以看到内容不多,也就是说,springboot 整合socket, 跟着我学,轻轻松松。
不多说,开始:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
server:
port: 8089
socketio:
host: localhost
port: 8503
maxFramePayloadLength: 1048576
maxHttpContentLength: 1048576
bossCount: 1
workCount: 100
allowCustomRequests: true
upgradeTimeout: 10000
pingTimeout: 60000
pingInterval: 25000
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/06/13 21:50
*/
@Configuration
public class MySocketConfig{
@Value("${socketio.host}")
private String host;
@Value("${socketio.port}")
private Integer port;
@Value("${socketio.bossCount}")
private int bossCount;
@Value("${socketio.workCount}")
private int workCount;
@Value("${socketio.allowCustomRequests}")
private boolean allowCustomRequests;
@Value("${socketio.upgradeTimeout}")
private int upgradeTimeout;
@Value("${socketio.pingTimeout}")
private int pingTimeout;
@Value("${socketio.pingInterval}")
private int pingInterval;
@Bean
public SocketIOServer socketIOServer() {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
buildSocketConfig(socketConfig, config);
return new SocketIOServer(config);
}
/**
* 扫描netty-socketIo的注解( @OnConnect、@OnEvent等)
*/
@Bean
public SpringAnnotationScanner springAnnotationScanner() {
return new SpringAnnotationScanner(socketIOServer());
}
private void buildSocketConfig(SocketConfig socketConfig, com.corundumstudio.socketio.Configuration config) {
config.setSocketConfig(socketConfig);
config.setHostname(host);
config.setPort(port);
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
}
}
/**
* @Author: JCccc
* @Date: 2022-07-23 9:05
* @Description:
*/
public class MyMessage {
private String type;
private String content;
private String from;
private String to;
private String channel;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
}
代码简析:
MySocketHandler.java
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.socket.mysocket.util.SocketUtil;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/6/23 21:21
*/
@Component
public class MySocketHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SocketIOServer socketIoServer;
@PostConstruct
private void start(){
try {
socketIoServer.start();
}catch (Exception e){
e.printStackTrace();
}
}
@PreDestroy
private void destroy(){
try {
socketIoServer.stop();
}catch (Exception e){
e.printStackTrace();
}
}
@OnConnect
public void connect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
SocketUtil.connectMap.put(userFlag, client);
log.info("客户端userFlag: "+ userFlag+ "已连接");
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
log.info("客户端userFlag:" + userFlag + "断开连接");
SocketUtil.connectMap.remove(userFlag, client);
}
}
代码简析:
SocketUtil.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/6/23 21:28
*/
@Component
public class SocketUtil {
private final Logger log = LoggerFactory.getLogger(this.getClass());
//暂且把用户&客户端信息存在缓存
public static ConcurrentMap<String, SocketIOClient> connectMap = new ConcurrentHashMap<>();
@OnEvent(value = "CHANNEL_SYSTEM")
public void systemDataListener(String receiveMsg) {
if (!StringUtils.hasLength(receiveMsg)){
return;
}
JSONObject msgObject = (JSONObject) JSON.parse(receiveMsg);
String userFlag = String.valueOf(msgObject.get("from"));
String content = String.valueOf(msgObject.get("content"));
log.info("收到用户 : {} 推送到系统频道的一条消息 :{}",userFlag,content );
}
public void sendToAll(Map<String, Object> msg,String sendChannel) {
if (connectMap.isEmpty()){
return;
}
//给在这个频道的每个客户端发消息
for (Map.Entry<String, SocketIOClient> entry : connectMap.entrySet()) {
entry.getValue().sendEvent(sendChannel, msg);
}
}
public void sendToOne(String userFlag, Map<String, Object> msg,String sendChannel) {
//拿出某个客户端信息
SocketIOClient socketClient = getSocketClient(userFlag);
if (Objects.nonNull(socketClient) ){
//单独给他发消息
socketClient.sendEvent(sendChannel,msg);
}
}
/**
* 识别出客户端
* @param userFlag
* @return
*/
public SocketIOClient getSocketClient(String userFlag){
SocketIOClient client = null;
if (StringUtils.hasLength(userFlag) && !connectMap.isEmpty()){
for (String key : connectMap.keySet()) {
if (userFlag.equals(key)){
client = connectMap.get(key);
}
}
}
return client;
}
}
代码简析:
TestController.java
import com.socket.mysocket.dto.MyMessage;
import com.socket.mysocket.util.SocketUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/06/13 21:50
*/
@RestController
public class TestController {
public final static String SEND_TYPE_ALL = "ALL";
public final static String SEND_TYPE_ALONE = "ALONE";
@Autowired
SocketUtil socketUtil;
@PostMapping("/testSendMsg")
public String testSendMsg(@RequestBody MyMessage myMessage){
Map<String, Object> map = new HashMap<>();
map.put("msg",myMessage.getContent());
//群发
if (SEND_TYPE_ALL.equals(myMessage.getType())){
socketUtil.sendToAll( map,myMessage.getChannel());
return "success";
}
//指定单人
if (SEND_TYPE_ALONE.equals(myMessage.getType())){
socketUtil.sendToOne(myMessage.getTo(), map, myMessage.getChannel());
return "success";
}
return "fail";
}
}
代码简析:
好了,7步了。一切已经就绪了。
接下来搞点前端HTML页面, 玩一玩看看效果:

第一个页面:
TestClientStudentJC.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>我要连SOCKET</title>
<base>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<style>
body {
padding: 20px;
}
#console {
height: 450px;
overflow: auto;
}
.msg-color {
color: green;
}
</style>
</head>
<body>
<div id="console" class="well"></div>
<div id="conversationDiv">
<labal>给系统推消息</labal>
<input type="text" id="content"/>
<button id="btnSendToSystem" onclick="sendSys();">发送</button>
</div>
</body>
<script type="text/javascript">
var socket;
connect();
function connect() {
var userFlag = 'user_JC';
var opts = {
query: 'userFlag=' + userFlag
};
socket = io.connect('http://localhost:8503', opts);
socket.on('connect', function () {
console.log("连接成功");
output('当前用户是:' + userFlag );
output('<span class="msg-color">连接成功了。</span>');
});
socket.on('disconnect', function () {
output('<span class="msg-color">下线了。 </span>');
});
socket.on('CHANNEL_STUDENT', function (data) {
let msg= JSON.stringify(data)
output('收到学生频道消息了:' + msg );
console.log(data);
});
socket.on('CHANNEL_SYSTEM', function (data) {
let msg= JSON.stringify(data)
output('收到系统全局消息了:' + msg );
console.log(data);
});
}
function sendSys() {
console.log('发送消息给服务端');
var content = document.getElementById('content').value;
socket.emit('CHANNEL_SYSTEM',JSON.stringify({
'content': content,
'from': 'user_JC'
}));
}
function output(message) {
var element = $("<div>" + message + "</div>");
$('#console').prepend(element);
}
</script>
</html>
代码简析:
第二个页面,跟第一个基本一样,改一下用户唯一标识:
TestClientStudentPU.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>我要连SOCKET</title>
<base>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<style>
body {
padding: 20px;
}
#console {
height: 450px;
overflow: auto;
}
.msg-color {
color: green;
}
</style>
</head>
<body>
<div id="console" class="well"></div>
<div id="conversationDiv">
<labal>给系统推消息</labal>
<input type="text" id="content"/>
<button id="btnSendToSystem" onclick="sendSys();">发送</button>
</div>
</body>
<script type="text/javascript">
var socket;
connect();
function connect() {
var userFlag = 'user_PU';
var opts = {
query: 'userFlag=' + userFlag
};
socket = io.connect('http://localhost:8503', opts);
socket.on('connect', function () {
console.log("连接成功");
output('当前用户是:' + userFlag );
output('<span class="msg-color">连接成功了。</span>');
});
socket.on('disconnect', function () {
output('<span class="msg-color">下线了。 </span>');
});
socket.on('CHANNEL_STUDENT', function (data) {
let msg= JSON.stringify(data)
output('收到学生频道消息了:' + msg );
console.log(data);
});
socket.on('CHANNEL_SYSTEM', function (data) {
let msg= JSON.stringify(data)
output('收到系统全局消息了:' + msg );
console.log(data);
});
}
function sendSys() {
console.log('发送消息给服务端');
var content = document.getElementById('content').value;
socket.emit('CHANNEL_SYSTEM',JSON.stringify({
'content': content,
'from': 'user_PU'
}));
}
function output(message) {
var element = $("<div>" + message + "</div>");
$('#console').prepend(element);
}
</script>
</html>
OK,把项目跑起来,开始玩。
直接访问客户端页面 模拟学生 JC连接socket:
http://127.0.0.1:8089/TestClientStudentJC.html

可以看到服务端有监测到:
这里监测的:
先试试客户端给系统推消息先:

可以看到服务端成功收到消息:

这种方式,其实是因为服务监听了相关的频道:
前端使用JS推到这个系统频道:
ps: 其实前端给服务端推消息,其实调用接口就可以。
OK,进入核心应用场景1:

{
"type": "ALL",
"content":"你们好,这是一条广播消息,全部人都能收到",
"channel":"CHANNEL_SYSTEM"
}
看看效果:
其实也就是通过HTML 客户端监听主题做区分就好:
直接拉人口,升3 :
模拟2个学生,1个老师都连接上了socket
当然,老师监听的是 老师频道:
然后我们模拟推送一下消息到指定的老师频道:

{
"type": "ALL",
"content":"给老师们推一条消息!!!",
"channel":"CHANNEL_TEACHER"
}

模拟 学生 PU 给 学生JC 推消息:

可以看到在学生频道的JC正常收到了PU的消息:

好了,该篇就到这吧。
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送
我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复