即时通信是基于TCP长连接,建立连接之后,客户段/服务器可以无限次随时向对端发送数据,实现服务器数据发送的即时性
HTTP是短链接,设计的目的是减少服务器的压力
HTTP伪即时通讯

应用场景
实现即时通讯
websocket和http都是基于Tcp,不同在于HTTP建立的是短链接,而websocket建立的是长连接
缺点是websocket仅能支持部分平台

Socket.io是基于WebSocket协议的一套成熟的解决方案,可以在浏览器和服务器之间实现实时,双向和基于事件的通信,SocketIO将WebSocket、AJAX和其它的通信方式全部封装成了统一的通信接口,也就是说,我们在使用SocketIO时,不用担心兼容问题,底层会自动选用最佳的通信方式。
优点
缺点
目录结构如下

<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.11</version>
</dependency>
server.port=8080
server.servlet.context-path=/
spring.mvc.view.prefix=/templates
spring.mvc.view.suffix=.html
#============================================================================
# netty socket io setting
#============================================================================
# host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
socketio.host=localhost
socketio.port=9092
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
socketio.maxFramePayloadLength=1048576
# 设置http交互最大内容长度
socketio.maxHttpContentLength=1048576
# socket连接数大小(如只监听一个端口boss线程组为1即可)
socketio.bossCount=1
socketio.workCount=100
socketio.allowCustomRequests=true
# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
socketio.upgradeTimeout=1000000
# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
socketio.pingTimeout=6000000
# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
socketio.pingInterval=25000

package com.erha.socketio.cache;
import com.corundumstudio.socketio.SocketIOClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassNameClientCache
* @Description TODO 缓存用户 - 页面sessionId - 通道连接
* @Author DELL
* @Date 2022/1/2113:55
* @Version 1.0
**/
@Component
public class ClientCache {
/**
* @Author 二哈老头子
* @Description //TODO 用户信息缓存
* @Date 14:00 2022/1/21
* @Param
* @return
**/
private static Map<String, HashMap<UUID, SocketIOClient>> concurrentHashMap = new ConcurrentHashMap<>();
/**
* @Author 二哈老头子
* @Description //TODO userId-用户ID | sessionId-页面sessionId | socketIOClient-页面对应的通道连接
* @Date 14:03 2022/1/21
* @Param [userId, sessionId, socketIOClient]
* @return void
**/
public void saveClient(String userId,UUID sessionId,SocketIOClient socketIOClient){
HashMap<UUID, SocketIOClient> sessionIdClientCache = concurrentHashMap.get(userId);
if(sessionIdClientCache == null){
sessionIdClientCache = new HashMap<>();
}
sessionIdClientCache.put(sessionId,socketIOClient);
concurrentHashMap.put(userId,sessionIdClientCache);
}
/**
* @Author 二哈老头子
* @Description //TODO 获取用户的页面通道信息
* @Date 14:12 2022/1/21
* @Param [userId]
* @return java.util.HashMap<java.util.UUID,com.corundumstudio.socketio.SocketIOClient>
**/
public HashMap<UUID,SocketIOClient> getUserClient(String userId){
return concurrentHashMap.get(userId);
}
/**
* @Author 二哈老头子
* @Description //TODO 根据用户Id及页面sessionID删除页面通道连接
* @Date 14:14 2022/1/21
* @Param [userId, sessionId]
* @return void
**/
public void deleteSessionClientByUserId(String userId,UUID sessionId){
concurrentHashMap.get(userId).remove(sessionId);
}
/**
* @Author 二哈老头子
* @Description //TODO 根据用户Id删除用户通道连接缓存 暂无使用
* @Date 14:19 2022/1/21
* @Param [userId]
* @return void
**/
public void deleteUserCacheByUserId(String userId){
concurrentHashMap.remove(userId);
}
}
package com.erha.socketio.pojo;
import org.springframework.stereotype.Component;
/**
* @ClassNameMessageInfo
* @Description TODO
* @Author DELL
* @Date 2022/1/2114:36
* @Version 1.0
**/
@Component
public class MessageInfo {
private String userID;
private String userName;
private String message;
public MessageInfo() {
}
public MessageInfo(String userID, String userName, String message) {
this.userID = userID;
this.userName = userName;
this.message = message;
}
public MessageInfo(String userName, String message) {
this.userName = userName;
this.message = message;
}
@Override
public String toString() {
return "MessageInfo{" +
"userID='" + userID + ''' +
", userName='" + userName + ''' +
", message='" + message + ''' +
'}';
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
package com.erha.socketio.config;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.erha.socketio.handler.SocketIOHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* @ClassNameSocketIOConfig
* @Description TODO
* @Author DELL
* @Date 2022/1/2114:19
* @Version 1.0
**/
@Configuration
public class SocketIOConfig implements InitializingBean {
@Resource
private SocketIOHandler socketIOHandler;
@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;
@Override
public void afterPropertiesSet() throws Exception {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setReuseAddress(true);
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
configuration.setSocketConfig(socketConfig);
// host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
configuration.setHostname(host);
configuration.setPort(port);
// socket连接数大小(如只监听一个端口boss线程组为1即可)
configuration.setBossThreads(bossCount);
configuration.setWorkerThreads(workCount);
configuration.setAllowCustomRequests(allowCustomRequests);
// 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
configuration.setUpgradeTimeout(upgradeTimeout);
// Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
configuration.setPingTimeout(pingTimeout);
// Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
configuration.setPingInterval(pingInterval);
SocketIOServer socketIOServer = new SocketIOServer(configuration);
//添加事件监听器
socketIOServer.addListeners(socketIOHandler);
//启动SocketIOServer
socketIOServer.start();
System.out.println("SocketIO启动完毕");
}
}
package com.erha.socketio.handler;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.erha.socketio.cache.ClientCache;
import com.erha.socketio.pojo.MessageInfo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
/**
* @ClassNameSocketIOHandler
* @Description TODO
* @Author DELL
* @Date 2022/1/2114:29
* @Version 1.0
**/
@Component
public class SocketIOHandler {
@Resource
private ClientCache clientCache;
/**
* @Author 二哈老头子
* @Description //TODO 客户端连接的时候触发,前端js触发:socket = io.connect("http://localhost:9092");
* @Date 12:12 2022/1/20
* @Param [client]
* @return void
**/
@OnConnect
public void onConnect(SocketIOClient client){
String userId = client.getHandshakeData().getSingleUrlParam("userId");
UUID sessionId = client.getSessionId();
clientCache.saveClient(userId,sessionId, client);
System.out.println("userId: "+userId+"连接建立成功 - "+sessionId);
}
/**
* @Author 二哈老头子
* @Description //TODO 客户端关闭连接时触发:前端js触发:socket.disconnect();
* @Date 12:14 2022/1/20
* @Param [client]
* @return void
**/
@OnDisconnect
public void onDisconnect(SocketIOClient client){
String userId = client.getHandshakeData().getSingleUrlParam("userId");
UUID sessionId = client.getSessionId();
clientCache.deleteSessionClientByUserId(userId,sessionId);
System.out.println("userId: "+userId+"连接关闭成功 - "+sessionId);
}
/**
* @Author 二哈老头子
* @Description
* //TODO 自定义消息事件,客户端js触发:socket.emit('messageevent', {msgContent: msg});时触发该方法
* //TODO 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的
* //TODO 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
* @Date 13:51 2022/1/20
* @Param [client, request, data]
* @return void
**/
@OnEvent("chatevent")
public void chatEvent(SocketIOClient client, AckRequest ackRequest, MessageInfo message){
HashMap<UUID, SocketIOClient> userClient = clientCache.getUserClient("79");
Iterator<Map.Entry<UUID, SocketIOClient>> iterator = userClient.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<UUID, SocketIOClient> next = iterator.next();
next.getValue().sendEvent("chatevent", message);
}
System.out.println(message);
}
}
@Controller
public class baseController {
@RequestMapping(value = "/")
public String index(){
return "index";
}
}
@RestController
@RequestMapping("/push")
public class PushController {
@Resource
private ClientCache clientCache;
@GetMapping("/user/{userId}")
public String pushTuUser(@PathVariable("userId") String userId){
HashMap<UUID, SocketIOClient> userClient = clientCache.getUserClient(userId);
userClient.forEach((uuid, socketIOClient) -> {
//向客户端推送消息
socketIOClient.sendEvent("chatevent",new MessageInfo("管理员","向客户段发送的消息"));
});
return "success";
}
}
前端页面需要引入socket.io.js 的js包
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Demo Chat</title>
<link href="css/bootstrap.css" rel="stylesheet">
<style>
body {
padding:20px;
}
#console {
height: 400px;
overflow: auto;
}
.username-msg {color:orange;}
.connect-msg {color:green;}
.disconnect-msg {color:red;}
.send-msg {color:#888}
</style>
<script src="js/socket.io/socket.io.js"></script>
<script src="js/moment.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
var userName = 'user' + Math.floor((Math.random()*1000)+1);
//创建通道连接
var socket = io.connect('http://localhost:9092?userId=79');
socket.on('connect', function() {
output('<span class="connect-msg">Client has connected to the server!</span>');
});
socket.on('chatevent', function(message) {
output('<span class="username-msg">' + message.userName + ':</span> ' + message.message);
});
socket.on('disconnect', function() {
output('<span class="disconnect-msg">The client has disconnected!</span>');
});
//关闭通道连接 可被@OnDisconnect注解的方法监听
function sendDisconnect() {
socket.disconnect();
}
function sendMessage() {
var message = $('#msg').val();
$('#msg').val('');
var jsonObject = {
userName: userName,
message: message
};
//发往后端@OnEvent("chatevent")注解的方法
socket.emit('chatevent', jsonObject);
}
function output(message) {
var currentTime = "<span class='time'>" + moment().format('HH:mm:ss.SSS') + "</span>";
var element = $("<div>" + currentTime + " " + message + "</div>");
$('#console').prepend(element);
}
$(document).keydown(function(e){
if(e.keyCode == 13) {
$('#send').click();
}
});
</script>
</head>
<body>
<h1>Netty-socketio Demo Chat</h1>
<br/>
<div id="console" class="well">
</div>
<form class="well form-inline" onsubmit="return false;">
<input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/>
<button type="button" onClick="sendMessage()" class="btn" id="send">Send</button>
<button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
</form>
</body>
</html>
核心代码
创建连接,该语句的执行对应会调用服务端的SocketIO事件相应类中@OnConnect注解的方法
//创建通道连接
var socket = io.connect('http://localhost:9092?userId=79'); // --@OnConnect注解的方法
监听代码
//事件监听服务器端监听器的@OnConnect注解方法
socket.on('connect', function() {
output('<span class="connect-msg">Client has connected to the server!</span>');
});
//事件监听服务器端监听器的@OnDisconnect注解的方法
socket.on('disconnect', function() {
output('<span class="disconnect-msg">The client has disconnected!</span>');
});
//自定义页面监听事件监听后端发送来的信息 后端页面对应代码 socketIOClient.sendEvent("chatevent",message);
socket.on('chatevent', function(message) {
output('<span class="username-msg">' + message.userName + ':</span> ' + message.message);
});
//自定义事件格式
socket.on("自定义事件名称",function(message){.....})
功能代码
//关闭通道连接 可被@OnDisconnect注解的方法监听
socket.disconnect();
//发往服务器端 @OnEvent("chatevent")注解的方法
socket.emit('chatevent', message);
输入集成链接 http://localhost:8080/ 进入聊天页面

后台提示:

输入 http://localhost:8080/push/user/79 先从服务端发送消息

页面信息发送

关闭链接


先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.
一边学习thisRailscast我从Rack中看到了以下源代码:defself.middleware@middleware||=beginm=Hash.new{|h,k|h[k]=[]}m["deployment"].concat[[Rack::ContentLength],[Rack::Chunked],logging_middleware]m["development"].concatm["deployment"]+[[Rack::ShowExceptions],[Rack::Lint]]mendend我的问题是关于第三行。什么是传递block{|h,k|h[k]=[]}到Has
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式
我不确定如何为我的搜索功能添加自动完成表单。"get"do%>nil%>我有一个具有自定义操作的Controllerdefquery@users=Search.user(params[:query])@article=Search.article(params[:query])end模型如下:defself.user(search)ifsearchUser.find(:all,:conditions=>['first_nameLIKE?',"%#{search}%"])elseUser.find(:all)endenddefself.article(search)ifsearchArt
编辑#2这是类(class)ControllerclassCoursesController编辑#1因此,根据下面Jagdeep的回答,我现在完成了以下操作:类(class).rbclassCoursecourse_modules_user.rbclassCourseModulesUsercourses_user.rbclassCoursesUser用户.rbclassUser迁移classCreateCoursesUsers但是,我遇到这样的错误原始问题所以这是previousquestion的延续,然而,这会偏离那个主题,所以这里是一个新的主题。在此之后,我大致得到了我想要开始工作
#!/usr/bin/envrubyrequire'optparse'options={}OptionParser.newdo|opts|opts.on("--languageLANGUAGE",["Ruby","JavaScript"])do|language|options[:language]=languageendend.parse!puts"Language:#{options[:language]}"如果我用./bin/example--languageRu运行它,它将输出:Language:Ruby我想禁用此自动完成/最接近的匹配行为,并在未提供确切名称时引发Option