当前位置:   article > 正文

Springboot 基于netty-socketio 实现简单的在线聊天_socketio 实现 在线聊天功能

socketio 实现 在线聊天功能
一、构建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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
二、 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

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;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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";
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

​ 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());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

​ 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, "走错了,小老弟~");
        }

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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,"这是什么操作???");
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
三、 Socket相关代码的前端页面代码的实现

客户端1和客户端2的代码本质上是一样的,因为本例的代码的用户信息是在页面写死的,所以有两套代码

  1. 客户端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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/973899
推荐阅读
相关标签
  

闽ICP备14008679号