一、构建Springboot项目
目录结构如下
0.创建基础springboot项目,此处不再赘述
1.在pom文件中导入socket相关依赖
<!-- web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok插件,可以自动生成getter、setter等方法,简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 主要依赖 socket通信 -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.15</version>
</dependency>
<!-- 日志打印 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
二、 Socket相关代码的后台实现
以下代码仅供参考,完整代码地址: https://github.com/Szwget/springboot-socket.git
1.在application.properties 中编写socket相关配置文件
# netty-socket-io
nettySocketIO.port=10089
nettySocketIO.linkedCount=200
nettySocketIO.allowRequest=true
# 协议升级超时时间(毫秒),默认10秒,HTTP握手升级为ws协议超时时间
nettySocketIO.upgradeTimeOut=10000
# 60s内未接受到消息发送超时事件
nettySocketIO.pingTimeOut=60000
nettySocketIO.pingSpace=25000
#设置交互内容长度
nettySocketIO.contextLength=2071738
#设置每帧处理数据大小(防注入攻击)
nettySocketIO.payloadLength=2071738
2.编写相关java代码
0) 注入配置文件的配置信息
@Configuration
@Slf4j
public class SocketConfig {
@Value("${nettySocketIO.port}")
private int port;
@Value("${nettySocketIO.linkedCount}")
private int linkedCount;
@Value("${nettySocketIO.allowRequest}")
private Boolean allowRequest;
@Value("${nettySocketIO.upgradeTimeOut}")
private Integer upgradeTimeOut;
@Value("${nettySocketIO.pingTimeOut}")
private Integer pingTimeOut;
@Value("${nettySocketIO.pingSpace}")
private Integer pingSpace;
@Value("${nettySocketIO.contextLength}")
private Integer contextLength;
@Value("${nettySocketIO.payloadLength}")
private Integer payloadLength;
//把socketServer注册为一个bean
@Bean("socketIOServer")
public SocketIOServer socketIOServer(){
com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
configuration.setPort(port);
com.corundumstudio.socketio.SocketConfig socketConfig = new com.corundumstudio.socketio.SocketConfig();
socketConfig.setReuseAddress(true);
configuration.setSocketConfig(socketConfig);
configuration.setWorkerThreads(linkedCount);
configuration.setAllowCustomRequests(allowRequest);
configuration.setUpgradeTimeout(upgradeTimeOut);
configuration.setPingTimeout(pingTimeOut);
configuration.setPingInterval(pingSpace);
configuration.setMaxHttpContentLength(contextLength);
configuration.setMaxFramePayloadLength(payloadLength);
return new SocketIOServer(configuration);
}
//开启socketIO注解
// @Bean
// public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketIOServer){
// return new SpringAnnotationScanner(socketIOServer);
// }
}
1)定义消息和房间实体bean
a. 定义消息相关实体类 ===>MessageBean.java
@Data //lombok注解,可自动生成getter、setter、构造方法等
public class MessageBean implements Serializable {
private static final long serialVersionUID = 1L;
//房间的标识
private String roomName;
//用户标识
public String userName;
public String token;
//消息内容
public String message;
//操作类型
public String option;
}
b.定义房间相关实体类 ===>
@Data
public class RoomBean implements Serializable {
private static final long serialVersionUID = 1L;
//房间的标识
private String roomName;
//用户标识
private String userId;
private String userName;
//动作类型 用户进入/退出房间
private String option;
}
2)定义事件常量
public class Config {
public static final String CHATEVENT = "chatevent";
public static final String ROOMEVENT = "roomevent";
public static final String TARGETEVENT = "target";
//系统消息
public static final String SYSTEM_ROOM_MESSAGE = "system-message";
//房间消息
public static final String SYSTEM_MESSAGE = "system-room-message";
}
3) 编写事件监听器
a.连接事件监听器
@Slf4j
public class AppConnectListener implements ConnectListener {
@Autowired
private SocketIOServer server;
public AppConnectListener (SocketIOServer server){
this.server = server;
}
@Override
public void onConnect(SocketIOClient client) {
log.info("{}进入连接...",client.getSessionId());
}
}
b.断开连接监听事件
@Slf4j
public class AppDisconnectListener implements DisconnectListener {
@Autowired
private SocketIOServer server;
public AppDisconnectListener (SocketIOServer server){
this.server = server;
}
@Override
public void onDisconnect(SocketIOClient client) {
log.info("{} 断开连接.", client.getSessionId());
}
}
c.进入房间监听器
@Component
@Slf4j
public class RoomListener implements DataListener<RoomBean> {
@Autowired
private SocketIOServer server;
public RoomListener(SocketIOServer server){
this.server = server;
}
@Override
public void onData(SocketIOClient client, RoomBean data, AckRequest ackSender) throws Exception {
String roomName = data.getRoomName();
BroadcastOperations bo = server.getRoomOperations(roomName);
String option = data.getOption();
if("join".equals(option)){
//客户端加入房间
client.joinRoom(data.getRoomName());
//发送进入房间消息到所有在线的客户端
bo.sendEvent(Config.ROOMEVENT,data.getToken().concat("进入房间"));
}else{
server.getClient(client.getSessionId()).sendEvent(Config.TARGETEVENT, "走错了,小老弟~");
}
}
}
d. 发送消息监听器
@Component
@Slf4j
public class SendMessageListener implements DataListener<MessageBean> {
@Autowired
private SocketIOServer server;
public SendMessageListener (SocketIOServer server){
this.server = server;
}
@Override
public void onData(SocketIOClient client, MessageBean data, AckRequest ackSender) throws Exception {
String roomName = data.getRoomName();
String option = data.getOption();
BroadcastOperations bo = server.getRoomOperations(roomName);
if ("send".equals(option)) {
bo.sendEvent(Config.TARGETEVENT, data);
} else {
bo.sendEvent(Config.TARGETEVENT,"这是什么操作???");
}
}
}
4)编写socketServer启动类,当springboot项目启动的时候,启动socketServer
@Component
@Order(1)
@Slf4j
public class Server implements CommandLineRunner {
private final SocketIOServer server;
@Autowired
public Server(SocketIOServer socketIOServer) {
this.server = socketIOServer;
}
public void init() {
// Configuration config = new Configuration();
// config.setPort(prot);
// final SocketIOServer server = new SocketIOServer(config);
// 连接监听器
server.addConnectListener(new AppConnectListener(server));
// 断开连接监听器
server.addDisconnectListener(new AppDisconnectListener(server));
// 消息发送监听器
server.addEventListener(Config.CHATEVENT, MessageBean.class, new SendMessageListener(server));
server.addEventListener(Config.ROOMEVENT, RoomBean.class, new RoomListener(server));
// 服务启动
log.info("socket服务准备启动");
server.startAsync();
log.info("socket启动完成");
}
@Override
public void run(String... args) throws Exception {
this.init();
}
}
三、 Socket相关代码的前端页面代码的实现
客户端1和客户端2的代码本质上是一样的,因为本例的代码的用户信息是在页面写死的,所以有两套代码
- 客户端1
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.22/vue.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.slim.js"></script>
</head>
<title>User1</title>
<body class="luck-draw-input-box">
<div id="luck_login" class="luck-draw-input">
<div>
<input id="uid" name="uid" type="text" placeholder="请输入消息" class="name" v-model="msg">
<a href="javascript:;" class="draw-btn" v-on:click="subMsg">发送</a>
</div>
<div v-for="(item,index) in content" style="display: flex;">
<p :key="index" style="margin-right: 10px;">{{item.userName}}:</p>
<p :key="index">{{item}}</p>
</div>
</div>
<script>
var mv = new Vue({
el: '#luck_login',
data: {
username: '',
socket:'',
msg:'',
content:[]
},
created(){
//生命周期钩子,在实例创建完成后被立即调用
this.join()
},
methods: {
//加入房间
join(event) {
let self = this;
//开始连接server
self.socket = io('ws://192.168.1.190:10099', {transports: ['websocket']});
//debugger
self.socket.on('connect', function (data) {
self.socket.emit('roomevent', {// roomDataListener
roomName: "001",
userId: "001",
userName:"AAA",
option: "join"
});
});
//点对点的消息
self.socket.on('target', function (data) {
console.log(data);
mv.content.push(data);
});
},
//发送消息
subMsg(){
this.socket.emit('chatevent',{
roomName: "001",
userName:"AAA",
token: "001",
message:this.msg,
option:'send'
})
}
}
})
</script>
</body>
</html>
2.客户端2
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.22/vue.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.slim.js"></script>
</head>
<title>User2</title>
<body class="luck-draw-input-box">
<div id="luck_login" class="luck-draw-input">
<div>
<input id="uid" name="uid" type="text" placeholder="请输入消息" class="name" v-model="msg">
<a href="javascript:;" class="draw-btn" v-on:click="subMsg">发送</a>
</div>
<div v-for="(item,index) in content" style="display: flex;">
<p :key="index" style="margin-right: 10px;">{{item.userName}}:</p>
<p :key="index">{{item}}</p>
</div>
</div>
<script>
var mv = new Vue({
el:'#luck_login',
data:{
uid:'',
username:'',
socket:'',
msg:'',
content:[]
},
created(){
//生命周期钩子,在实例创建完成后被立即调用
this.join()
},
methods:{
join(event){
let self = this;
//开始连接server
self.socket = io('http://192.168.1.190:10099',{transports:['websocket']});
//debugger
self.socket.on('connect', function(data){
self.socket.emit('roomevent', {//roomDataListener
roomName: "001",
userId: "002",
userName:"BBB",
option: "join"
});
});
//点对点的消息
self.socket.on('target', function(data) {
console.log(data);
mv.content.push(data);
});
self.socket.on('chatevent', function(data) {
console.log("chatevent");
console.log(data);
if(data){
mv.content.push(data);
}
});
},
subMsg(){
this.socket.emit('chatevent',{
roomName: "001",
userName:"BBB",
token: "002",
message:this.msg,
option:'send'
})
}
}
})
</script>
</body>
</html>