今天学习了CoCos Creator、WebSocket聊天室
参考文章:https://blog.csdn.net/u012987441/article/details/106863545/
别人用的node.js做服务器,这里我用了基于java的netty做服务器
若你非后端人员,无spring boot及netty基础,可以用我写好的服务器文件,服务端部分可以略过。
链接:https://pan.baidu.com/s/1beO61aCmhBx6wltnveQFKA
提取码:jyad
创建一个Spring Boot项目
引入依赖:
io.netty
netty-all
4.1.66.Final
org.projectlombok
lombok
1.18.20
package com.cocos.netty.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
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;
public class WebSocketServer {
private int port;
public WebSocketServer(int port){
this.port = port;
}
public void run(){
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128) //最大客户端连接数128
.childOption(ChannelOption.SO_KEEPALIVE,true) //客户端保持活动连接
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new WebSocketServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
WebSocketServerInitializer
package com.cocos.netty.server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WebSocketServerInitializer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//http的解编码器
ch.pipeline().addLast("Codec",new HttpServerCodec());
//该处理器是处理大数据传输的,用以维护管理大文件传输过程中时复杂的状态
ch.pipeline().addLast("ChunkedWrite",new ChunkedWriteHandler());
//将HTTP消息的多个部分合成一条完整的HTTP消息,接收累计器,将多个段聚合,保证http请求完整性,用于处理大量数据
ch.pipeline().addLast("Aggregator",new HttpObjectAggregator(64 * 1024));
ch.pipeline().addLast("Protocol",new WebSocketServerProtocolHandler("/ws"));
ch.pipeline().addLast("MyHandler",new WebSocketServerHandler());
}
}
WebSocketServerHandler
package com.cocos.netty.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
public class WebSocketServerHandler extends SimpleChannelInboundHandler {
//定义一个channel组,管理所有channel (GlobalEventExecutor.INSTANCE 是一个全局的事件执行器,是一个单例)
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//处理时间
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
channelGroup.forEach(channel -> {
if(ctx.channel() != channel){
channel.writeAndFlush(new TextWebSocketFrame("[客户端]" + sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + ":" + msg.text()));
}else {
channel.writeAndFlush(new TextWebSocketFrame("[我]" + sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + ":" + msg.text()));
}
});
System.out.println(ctx.channel().id().asShortText() + ":" + msg.text());
}
//新的客户端连接
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id 表示唯一的值,(有LongText()和ShortText()两种),LongText 是唯一的值;ShortText()不是唯一的
channelGroup.writeAndFlush(sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + " 加入了!");
channelGroup.add(ctx.channel());
super.handlerAdded(ctx);
}
//客户端活动
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + " 已上线!");
super.channelActive(ctx);
}
//客户端不活动
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + " 断线了!");
channelGroup.remove(ctx.channel());
super.channelInactive(ctx);
}
//断开连接
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
channelGroup.writeAndFlush(sdf.format(new Date()) + " " + ctx.channel().id().asShortText() + " 离开了!");
System.out.println(ctx.channel().id().asShortText()+ " 离开了!");
super.handlerRemoved(ctx);
}
//客户端异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("发生异常了 " + cause.getMessage());
ctx.close();
super.exceptionCaught(ctx, cause);
}
}
NettyCocosApplication
package com.cocos.netty;
import com.cocos.netty.server.WebSocketServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NettyCocosApplication {
public static void main(String[] args) {
SpringApplication.run(NettyCocosApplication.class, args);
WebSocketServer webSocketServer = new WebSocketServer(8080);
webSocketServer.run();
}
}
测试,网页hello.html
Title
启动Netty项目,打开网页
此处进行发送测试
将项目打包,pom添加:
org.springframework.boot
spring-boot-maven-plugin
版本:2.4.6,TypeScript
细节处:
scrollViwe组件删掉bar和item
添加Widget组件
NetMoudle.ts
const {ccclass, property} = cc._decorator;
@ccclass
export default class GameNet extends cc.Component {
private static Instance: GameNet = null;
private ws: WebSocket = null;
private constructor(){
super();
}
public static getInstance(): GameNet {
if (GameNet.Instance === null) {
GameNet.Instance = new GameNet();
}
return GameNet.Instance;
}
/**
* 初始化并连接WebSocket服务端
* @param callback 回调函数
* @param target 绑定this
* @returns
*/
public init(callback: Function, target: any): void {
if (this.ws !== null) return;
this.ws = new WebSocket('ws://localhost:8080/ws');
this.ws.onopen = (event: MessageEvent)=>{
callback.call(target, "登录成功");
}
this.ws.onmessage = (event: MessageEvent)=>{
callback.call(target, event.data);
}
this.ws.onerror = (event: MessageEvent)=>{
console.log("WebSocket error:" + event);
this.ws.close();
}
this.ws.onclose = ()=>{
if (this.ws.readyState === WebSocket.CLOSED) {
console.log("WebSocket already close");
}
console.log("WebSocket close");
}
}
public send(data: any): void {
if (this.ws !== null && this.ws.readyState !== WebSocket.OPEN) return;
this.ws.send(data);
}
}
GameManager.ts
const {ccclass, property} = cc._decorator;
import GameNet from "./NetModule";
@ccclass
export default class GameManager extends cc.Component {
@property(cc.ScrollView)
scrollView: cc.ScrollView = null;
@property(cc.EditBox)
editBox: cc.EditBox = null;
@property(cc.Button)
login_btn: cc.Button = null;
@property(cc.Button)
send_btn: cc.Button = null;
posY: number = 0;
start () {
this.login_btn.node.on(cc.Node.EventType.TOUCH_END, this.login, this);
this.send_btn.node.on(cc.Node.EventType.TOUCH_END, this.sendMsg, this);
}
private login(){
GameNet.getInstance().init(this.getMsg, this);
}
private sendMsg(){
let data: string = this.editBox.string;
if (data === null) return;
GameNet.getInstance().send(data.toString());
}
private getMsg(data: any){
let label: cc.Label = (new cc.Node).addComponent(cc.Label);
label.string = data;
label.fontSize = 20;
label.node.anchorX = 0;
label.node.anchorY = 1;
label.node.x = -this.scrollView.content.width * 0.5 + 10;
label.node.y = this.posY;
this.scrollView.content.addChild(label.node);
this.posY -= label.node.height;
if (Math.abs(this.posY) > this.scrollView.content.height) {
this.scrollView.content.height = Math.abs(this.posY);
this.scrollView.scrollToBottom();
}
this.editBox.string = '';
}
}
本地文件找到spring boot 项目的target文件夹,
按住shift并右击打开dos命令窗口,运行写好的项目
若要上传到服务器,可以更改spring boot 项目、ts代码的ip地址为服务器ip
java -jar 文件名.jar
java -jar netty-cocos-0.0.1-SNAPSHOT.jar
个人学习记录,参考链接已放上。