系列文章目录
前言
最近公司有个项目设计到IM和物联网,因为时间的关系直接使用的第三方。但是我想做一下技术的积累,自己实现一个IM服务器,主要实现的功能有:消息的发送和云端存储,离线消息,已读回执,集群部署,用户授权,强制下线,关键字过滤等。最后加入音视频聊天。
从今天开始,我将记录下实现这些功能的过程,并把代码分享给大家。希望大家能不吝赐教。
一、技术选型
- SpringBoot 2.2.5.RELEASE
- Netty 5.0.0.Alpha2
- Redis
- Mybaits
- MySql 5.5.7
目前想到的就这些,做的过程中再补充吧。
二、简单的实现一个IM服务端
1.创建工程
maven pom.xml 文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.xa87.im</groupId>
<artifactId>Xa87-IM</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<springboot.version>2.2.5.RELEASE</springboot.version>
<netty.version>5.0.0.Alpha2</netty.version>
</properties>
<modules>
<module>SimpleServerDemo</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- alibaba JSON类库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.14</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>Central</id>
<name>Central Repository</name>
<url>https://repo1.maven.org/maven2/</url>
</repository>
<repository>
<id>spring-release</id>
<name>Spring Release</name>
<url>https://repo.spring.io/libs-release</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
2.创建一个Demo Module
pom.xml 文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Xa87-IM</artifactId>
<groupId>cn.xa87.im</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SimpleServerDemo</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
</dependencies>
</project>
3.配置文件
application.xml 文件内容:
xa87-im:
port: 18877
Xa87ImConfig.java:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author Mr.Guo
* @date 2021/3/5 上午11:46
*/
@Component
@ConfigurationProperties(prefix = "xa87-im")
public class Xa87ImConfig {
private int port;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
4.创建Netty服务
SocketMsgHandler.java:
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author Mr.Guo
* @date 2021/3/4 下午5:03
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class SocketMsgHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("收到信息:{}", msg);
ctx.writeAndFlush("hi, Client!");
}
}
SocketServerInitializer.java:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Mr.Guo
* @date 2021/3/4 下午4:53
*/
@Component
public class SocketServerInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private SocketMsgHandler socketMsgHandler;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加对于读写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//对httpMessage进行聚合
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
//自定义handler
pipeline.addLast(socketMsgHandler);
}
}
SocketServerListener.java:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* @author Mr.Guo
* @date 2021/3/5 下午1:47
*/
@Slf4j
@Component
public class SocketServerListener {
/**
* 创建bootstrap
*/
ServerBootstrap serverBootstrap = new ServerBootstrap();
/**
* BOSS
*/
EventLoopGroup boss = new NioEventLoopGroup();
/**
* Worker
*/
EventLoopGroup work = new NioEventLoopGroup();
@Resource
private Xa87ImConfig imConfig;
@Autowired
private SocketServerInitializer socketServerInitializer;
/**
* 开启服务线程
*/
public void start() {
// 从配置文件中(application.yml)获取服务端监听端口号
serverBootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO));
try {
serverBootstrap.childHandler(socketServerInitializer);
log.info("netty服务器在[{}]端口启动监听", imConfig.getPort());
ChannelFuture f = serverBootstrap.bind(imConfig.getPort()).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.info("[出现异常] 释放资源");
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
/**
* 关闭服务器方法
*/
@PreDestroy
public void close() {
log.info("关闭服务器....");
//优雅退出
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
5.启动服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Mr.Guo
* @date 2021/3/4 下午4:31
*/
@SpringBootApplication
public class App implements CommandLineRunner {
@Autowired
private SocketServerListener socketServerListener;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Override
public void run(String... args) throws Exception {
socketServerListener.start();
}
}
三、运行结果