netty-socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架。项目地址为:https://github.com/mrniko/netty-socketio
最主要:服务端的主要工作,就是添加各种事件的监听,然后在监听中,做出相应的处理。
代码说明:
1.本代码不能完全copy然后执行,只是放出了关键代码,需要对socketio有一定的理解;
2.有聊天emoji表情的处理功能;
3.有文件的上传功能:我这里把文件分为图片和非图片文件,因为图片文件要实时展示出来的。
引入maven依赖:
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.11</version>
</dependency>
新建ServerRunner类实现ApplicationListener确保服务器在spring启动完成后启动,添加@Service标签确保spring可以扫描到。
@Service("ServerRunner")
public class ServerRunner implements ApplicationListener<ContextRefreshedEvent>{
private static final Logger logger = LoggerFactory.getLogger(ServerRunner.class);
@Value("${server.host}")
private String host ;
@Value("${server.port}")
private Integer port;
@Value("${fileUrl}")
private String fileUrl;
private EmojiConverter emojiConverter = EmojiConverter.getInstance();
//会话集合
private static final ConcurrentSkipListMap<String, ClientInfo> webSocketMap = new ConcurrentSkipListMap<>();
//静态变量,用来记录当前在线连接数。(原子类、线程安全)
private static AtomicInteger onlineCount = new AtomicInteger(0);
private SocketIOServer server;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//端口
int WSS_PORT=port;
//服务器ip
String WSS_HOST=host;
System.out.println("WSS_PORT"+WSS_PORT+"**************************"+WSS_HOST);
//避免重复启动
if( server== null){
Configuration config = new Configuration();
//服务器ip
config.setHostname(WSS_HOST);
config.setPort(WSS_PORT);
config.setMaxFramePayloadLength(1024 * 1024);//防止上传文件超过默认值报错
config.setMaxHttpContentLength(1024 * 1024);
//该处进行身份验证
config.setAuthorizationListener(handshakeData -> {return true;});
//异常
config.setExceptionListener(new ExceptionListenerAdapter(){
@Override
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
logger.debug("错误:"+e.getMessage());
ctx.close();
return true;
}
});
server = new SocketIOServer(config);
//添加链接事件监听
server.addConnectListener(new ConnectListener() {
@Override
public void onConnect(SocketIOClient client) {
logger.debug("socket 建立新连接");
...
}
});
//添加销毁链接事件监听
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.debug("socket 断开连接");
...
}
});
//添加发送消息事件监听
server.addEventListener("messageEvent", Message.class, new DataListener<Message>(){
@Override
public void onData(SocketIOClient client, Message data, AckRequest ackSender) {
SendData sendData = new SendData();//发送的消息
try{
Message chat = new Message();//存入数据库
Integer userId = data.getUserId();
String filePath = "/"+userId+"/";
String fileRandomName = null;
String format =null;
ClientInfo si = webSocketMap.get(userId);
if (si != null) {
String result = null;
byte[] fileByte = data.getFileByte();//接收客户端传来的文件字节数组
String fileName = data.getFileName();//接收客户的传来的文件名称
Integer msgType = data.getMsgType();//接收客户的传来的文件类型
Integer materialId = null;
if(msgType==1){//1文字
result = emojiConverter.toAlias(data.getMsgContent());//对emoji表情进行转入,否则存入mysql数据库将报错
}else if(msgType==2||msgType==3){//2图片和3文件
if(fileByte!=null){
//这里保存文件
format = fileName.substring(fileName.lastIndexOf("."));
fileRandomName = System.currentTimeMillis()+format;
FileUtil.getFile(fileByte, fileUrl+filePath, fileRandomName);//工具方法:将文件字节数组转换成文件保存到本地
//保存到Materail,自己创建的实体类
Material material = new Material();
material.setFileName(fileName);
material.setFilePath(filePath+fileRandomName);
material.setFormat(format);
ReturnInfo info = materialService.createMaterial(material);//保存文件信息到数据库
if(!info.getSuccess()){
logger.debug("保存文件失败:"+info.getMessage());
}else {
materialId = (Integer)info.getData();
}
}
}
chat.setCreater(userId);
chat.setMsgContent(result);
chat.setMsgType(msgType);
chat.setMaterialId(materialId);
//这里保存聊天记录
Integer sendStatus = 2;//发送留言的状态,2未发送
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String msgDate = simpleDateFormat.format(new Date());
sendData.setCreateDate(new Date());
sendData.setMsgContent(result);
sendData.setCreater(userId);
sendData.setMsgType(msgType);
if(fileByte!=null){
sendData.setFormat(format);
sendData.setFileName(fileName);
sendData.setFileByte(fileByte);
sendData.setFilePath(filePath+fileRandomName);
}
List<Integer> userIds = data.getUserIds();//客户端传来的要转发给其他的用户的Id
if(userIds!=null){
for(Integer targetClientId : userIds){
if(targetClientId!=userId){
ClientInfo clientInfo = webSocketMap.get(targetClientId);
if (clientInfo != null && clientInfo.isOnline()){//当前在线
UUID target = new UUID(clientInfo.getMostSignificantBits(), clientInfo.getLeastSignificantBits());
// 向目标会话发送信息
if(server.getClient(target)!=null){
server.getClient(target).sendEvent("messageEvent", sendData);
}
sendStatus=1;//发送留言成功
}else{//不在线
sendStatus=2;//未发送
}
//对留言发送成功或失败的处理
}
}
}
}
}catch(Exception e){
e.printStackTrace();
logger.debug("发送留言异常:"+e.getMessage());
sendData.setSuccess(false);
sendData.setMsgContent("发送留言异常");
}
// 向当前会话发送信息
client.sendEvent("messageEvent", sendData);
}
});
}
}
}
public class Message {
private List<Integer> userIds;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createDate;
private Integer msgType;
private Integer materialId;
private String msgContent;
private String createrFullname;
private String filePath;
private Integer userId;//在接收聊天消息时用到,等于creater字段
private byte[] fileByte;
private String fileName;
private String format;
//getter and setter
}
public class SendData extends Message {
private Boolean success;
private String msgDate;
private String message;
//getter and setter
}
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileUtil {
/**
* 根据byte数组,生成文件
*/
public static void getFile(byte[] bfile, String filePath,String fileName) {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = null;
createFolderIfNotExists(filePath);
try {
file = new File(filePath+"\\"+fileName);
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(bfile);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public static void createFolderIfNotExists(String dirName)
throws SecurityException {
File theDir = new File(dirName);
mkDir(theDir);
}
public static void mkDir(File file) {
if (file.getParentFile().exists()) {
file.mkdir();
} else {
mkDir(file.getParentFile());
file.mkdir();
}
}
}
emoji表情报错参考:https://blog.csdn.net/qq_23888451/article/details/84638365
另外,该功能是在这篇文章上,做了些改进。