前言
使用springboot整合netty。
一、配置文件
<dependencies>
<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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.meeting</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
server:
port: 8005
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/meeting?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8&
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mybatis-mapper/*.xml
type-aliases-package: com.meeting.entity
netty:
server:
port: 8006
二、实体类
@Component
public class ChatChannelGroup {
private final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
private final Map<Long, Channel> ID_CHANNEL_REF = new HashMap<>();
private final Map<Channel, Long> CHANNEL_ID_REF = new HashMap<>();
public void addChannel(Long id, Channel channel) {
LOCK.writeLock().lock();
try {
ID_CHANNEL_REF.put(id, channel);
CHANNEL_ID_REF.put(channel, id);
} finally {
LOCK.writeLock().unlock();
}
}
public void removeChannel(Long id, Channel channel) {
LOCK.writeLock().lock();
try {
ID_CHANNEL_REF.remove(id);
CHANNEL_ID_REF.remove(channel);
} finally {
LOCK.writeLock().unlock();
}
}
public void print() {
LOCK.readLock().lock();
try {
ID_CHANNEL_REF.forEach((key, value) -> {
System.out.println(key + ": " + value);
});
System.out.println("print over");
} finally {
LOCK.readLock().unlock();
}
}
public Channel getChannelById(Long id) {
LOCK.readLock().lock();
try {
return ID_CHANNEL_REF.get(id);
} finally {
LOCK.readLock().unlock();
}
}
public Long getIdByChannel(Channel channel) {
LOCK.readLock().lock();
try {
return CHANNEL_ID_REF.get(channel);
} finally {
LOCK.readLock().unlock();
}
}
}
public class MessageVO {
/**
* message的id
*/
private Long id;
/**
* 接收方的id
*/
private Long toId;
/**
* 消息信息
*/
private String message;
/**
* 回复好友请求,布尔
*/
private boolean agree;
/**
* 获取历史记录,与num一起使用,表示从倒数第start条开始,一直读nun条
*/
private Integer start;
/**
* 获取历史记录,与start一起使用,表示从倒数第start条开始,一直读nun条
*/
private Integer num;
/**
* 消息类型
* 从客户端发来的消息类型,参照MessageType
* 返回给客户端的消息类型
* type = 1,聊天消息
* type = 2,好友请求
* type = 3,回复请求
*/
private Integer type;
/**
* 日期
*/
private long date;
public MessageVO() {}
/**
* todo type应该可以不要了
*/
public MessageVO(MessageDO message, int type) {
this.id = message.getId();
this.toId = message.getToId();
this.message = message.getMessage();
this.type = type;
this.date = message.getDate();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getToId() {
return toId;
}
public void setToId(Long toId) {
this.toId = toId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isAgree() {
return agree;
}
public void setAgree(boolean agree) {
this.agree = agree;
}
public Integer getStart() {
return start;
}
public void setStart(Integer start) {
this.start = start;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
@Override
public String toString() {
return "MessageVO{" +
"id=" + id +
", toId=" + toId +
", message='" + message + '\'' +
", agree=" + agree +
", start=" + start +
", num=" + num +
", type=" + type +
", date=" + date +
'}';
}
}
public class MessageDO {
/**
* 消息id
*/
private long id;
/**
* 发送方id
*/
private long fromId;
/**
* 接收方Id
*/
private long toId;
/**
* 消息
*/
private String message;
/**
* 时间
*/
private long date;
/**
* status=0,消息未签收
* status=1,消息已签收
* status=2,好友请求,且没有被回复
* status=3,好友请求,已经被回复
*/
private int status;
public MessageDO() {}
public MessageDO(long id, long fromId, long toId, String message, long date, int status) {
this.id = id;
this.fromId = fromId;
this.toId = toId;
this.message = message;
this.date = date;
this.status = status;
}
/**
* 根据MessageVo对象构造MessageDo
*/
public MessageDO(MessageVO messageVO, long fromId) {
this.id = messageVO.getId() == null ? 0 : messageVO.getId();
this.fromId = fromId;
this.toId = messageVO.getToId();
this.message = messageVO.getMessage();
// 当前时间
this.date = System.currentTimeMillis();
// 未签收的消息
this.status = 0;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getFromId() {
return fromId;
}
public void setFromId(long fromId) {
this.fromId = fromId;
}
public long getToId() {
return toId;
}
public void setToId(long toId) {
this.toId = toId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("id", this.id);
map.put("message", this.message);
map.put("date", this.date);
map.put("toId", this.toId);
map.put("fromId", this.fromId);
return map;
}
@Override
public String toString() {
return "MessageDO{" +
"id=" + id +
", fromId=" + fromId +
", toId=" + toId +
", message='" + message + '\'' +
", date=" + date +
", status=" + status +
'}';
}
}
public enum MessageType {
/**
* 聊天消息
*/
CHAT(1, "聊天消息"),
/**
* 消息签收
*/
SIGNED(2, "消息签收"),
/**
* 请求添加好友
*/
REQUEST(3, "请求添加好友"),
/**
* 回复好友请求
*/
REPLY(4, "回复好友请求"),
/**
* 获取好友信息
*/
PULL_FRIENDS(5, "获取好友信息"),
/**
* 获取好友请求
*/
PULL_REQUESTS(6, "获取好友请求"),
/**
* 获取未签收消息
*/
PULL_UNSIGNED_MESSAGE(7, "获取未签收消息"),
/**
* 获取历史消息记录
*/
PULL_HISTORY_MESSAGE(8, " 获取历史消息记录");
private final int type;
private final String text;
MessageType(int type, String text) {
this.type = type;
this.text = text;
}
public int getType() {
return type;
}
public String getText() {
return text;
}
}
public class Friend {
private Long uid;
private Long friendId;
public Friend() {}
public Friend(Long uid, Long friendId) {
this.uid = uid;
this.friendId = friendId;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public Long getFriendId() {
return friendId;
}
public void setFriendId(Long friendId) {
this.friendId = friendId;
}
@Override
public String toString() {
return "Friend{" +
"uid=" + uid +
", friendId=" + friendId +
'}';
}
}
public class ResponseData {
public final static ResponseData ID_NOT_FOUND = new ResponseData(false, "MISSING_MESSAGE_ID");
public final static ResponseData MESSAGE_TOO_LONG = new ResponseData(false, "MESSAGE_TOO_LONG");
public final static ResponseData USER_ID_NOT_FOUND = new ResponseData(false, "MISSING_USER_ID");
public final static ResponseData MESSAGE_NOT_EXIST = new ResponseData(false, "MESSAGE_NOT_EXIST");
public final static ResponseData HAVE_ALREADY_REQUESTED = new ResponseData(true, "HAVE_ALREADY_REQUESTED");
public final static ResponseData ILLEGAL_MESSAGE_FORMAT = new ResponseData(false, "ILLEGAL_MESSAGE_FORMAT");
public final static ResponseData TYPE_NOT_ALLOWED = new ResponseData(false, "TYPE_NOT_ALLOWED");
public final static ResponseData IS_ALREADY_FRIEND = new ResponseData(true, "IS_ALREADY_FRIEND");
public final static ResponseData SERVER_PROBLEM = new ResponseData(false, "SERVER_PROBLEM");
public final static ResponseData UNAUTHORIZED = new ResponseData(false, "UNAUTHORIZED");
public final static ResponseData BAD_REQUEST = new ResponseData(false, "BAD_REQUEST");
private boolean success;
private String type;
private final Map<String, Object> data = new HashMap<>();
public ResponseData() {}
public ResponseData(boolean success, String type) {
this.success = success;
this.type = type;
}
public static ResponseData ok(String message) {
return new ResponseData(true, message);
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Map<String, Object> getData() {
return data;
}
@Override
public String toString() {
return "ResponseData{" +
"success=" + success +
", type='" + type + '\'' +
", data=" + data +
'}';
}
}
/**
* 容纳ResponseData的容器
* toSender 回复给sender的响应
* toReceiver 回复给receiver的响应
*/
public class ResponseDataContainer {
/**
* 回复给sender的响应
*/
private ResponseData toSender;
/**
* 回复给receiver的响应
*/
private ResponseData toReceiver;
public ResponseDataContainer() {}
public ResponseDataContainer(ResponseData toSender, ResponseData toReceiver) {
this.toSender = toSender;
this.toReceiver = toReceiver;
}
public ResponseData getToSender() {
return toSender;
}
public void setToSender(ResponseData toSender) {
this.toSender = toSender;
}
public ResponseData getToReceiver() {
return toReceiver;
}
public void setToReceiver(ResponseData toReceiver) {
this.toReceiver = toReceiver;
}
@Override
public String toString() {
return "ResponseDataContainer{" +
"toSender=" + toSender +
", toReceiver=" + toReceiver +
'}';
}
}
public enum ResponseType {
/**
* 给消息发送方的成功响应
*/
MESSAGE_SENDER_OK("MESSAGE_SENDER_OK"),
/**
* 给消息接受方的成功响应
*/
MESSAGE_RECEIVER_OK("MESSAGE_RECEIVER_OK"),
/**
* 消息签收成功的响应
*/
SIGN_OK("SIGN_OK"),
/**
* 请求未签收消息的响应
*/
UNSIGNED_MESSAGE("UNSIGNED_MESSAGE_OK"),
/**
* 请求历史消息的响应
*/
HISTORY_MESSAGE("HISTORY_MESSAGE"),
/**
* 请求好友列表的响应
*/
FRIEND("FRIEND"),
/**
* 发送好友请求成功的响应
*/
REQUEST_SENDER_OK("REQUEST_SENDER_OK"),
/**
* 收到好友请求的响应
*/
REQUEST_RECEIVER_OK("REQUEST_RECEIVER_OK"),
/**
* 收到好友回复的消息
*/
REPLY_SENDER_OK("REPLY_SENDER_OK"),
/**
* 回复好友请求成功的消息
*/
REPLY_RECEIVER_OK("REPLY_RECEIVER_OK"),
/**
* 请求未回复的好友请求
*/
REQUESTS_TO_BE_REPLIED("REQUESTS_TO_BE_REPLIED");
private String type;
private ResponseType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
三、NettyServer.class
@Component
public class NettyServer {
@Value("${netty.server.port}")
private int port;
private void startServer() {
//服务端需要2个线程组 boss处理客户端连接 work进行客服端连接之后的处理
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
//服务器 配置
bootstrap.group(boss,work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
// HttpServerCodec:将请求和应答消息解码为HTTP消息
.addLast("http-codec",new HttpServerCodec())
// HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
.addLast("aggregator",new HttpObjectAggregator(65536))
// ChunkedWriteHandler:向客户端发送HTML5文件
.addLast("http-chunked",new ChunkedWriteHandler())
.addLast("websocket", new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10))
// 进行设置心跳检测
// .addLast("idle", new IdleStateHandler(60,30,60*30, TimeUnit.SECONDS))
// 配置通道处理来进行业务处理
.addLast("handler", new WebSocketHandler());
}
})
.option(ChannelOption.SO_BACKLOG,1024)
.childOption(ChannelOption.SO_KEEPALIVE,true);
//绑定端口 开启事件驱动
Channel channel = bootstrap.bind(port).sync().channel();
channel.closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
@PostConstruct
public void init() {
// 需要开启一个新的线程来启动netty server服务器
new Thread(this::startServer).start();
}
}
四
其它部分放在下一篇博客中。