CoCos Creator、WebSocket聊天室


前言

今天学习了CoCos Creator、WebSocket聊天室
参考文章:https://blog.csdn.net/u012987441/article/details/106863545/


一、服务端 Spring Boot、Netty

别人用的node.js做服务器,这里我用了基于java的netty做服务器
若你非后端人员,无spring boot及netty基础,可以用我写好的服务器文件,服务端部分可以略过。

链接:https://pan.baidu.com/s/1beO61aCmhBx6wltnveQFKA 
提取码:jyad

创建一个Spring Boot项目
引入依赖:

		<!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.66.Final</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

项目结构:
在这里插入图片描述
WebSocketServer

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<SocketChannel> {
    @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<TextWebSocketFrame> {
    //定义一个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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持webSocket编程
    if(window.WebSocket){
        socket = new WebSocket("ws://localhost:8080/ws");

        //相当于channelRead0, ev 收到服务器会送的消息
        socket.onmessage = function (ev){
            var rt = document.getElementById("responseText")
            rt.value = rt.value + "\n" + ev.data;
        }

        //相当于连接开启(感知到连接开启)
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText")
            rt.value = "连接开启了..."
        }

        //相当于连接关闭(感知到连接关闭)
        socket.onclose = function (ev){
            var rt = document.getElementById("responseText")
            rt.value= rt.value + "\n" + "连接关闭了...";
        }
    }else {
        alert("当前浏览器不支持webSocket编程")
    }

    function send(message){
        //判断WebSocket是否创建好
        if(!window.WebSocket){
            return;
        }
        if(socket.readyState == WebSocket.OPEN){
            //通过socket发送消息
            socket.send(message);
        }else{
            alert("连接没有开启")
        }
    }
</script>

    <form onsubmit="return false">
        <textarea name="message" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="发送消息" onclick="send(this.form.message.value)">

        <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value = ''">
    </form>
</body>
</html>

启动Netty项目,打开网页
在这里插入图片描述
此处进行发送测试
在这里插入图片描述
将项目打包,pom添加:

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

在这里插入图片描述

二、客户端 CoCos Creator 2.4.6、WebSocket

版本:2.4.6,TypeScript

1.界面

在这里插入图片描述
细节处:
scrollViwe组件删掉bar和item
添加Widget组件
在这里插入图片描述
在这里插入图片描述

2.代码

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

运行 cocos

多开两个窗口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

写在最后

个人学习记录,参考链接已放上。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值