使用Websocket实现消息推送(上)

Spring Boot 使用 WebSocket 实现消息推送 及 WebSocket原理 WebSocket 是什么原理,为什么可以实现持久连接? 一、WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容 阅读详情

联系客服功能在项目中很难避免,一般有下面三种实现方式:

  • 使用http的get方式轮询
  • 接入第三方IM系统
  • 自己的IM系统
    • 基于socket
    • 基于websocket

第一种方式,最low的,实现简单,但是浪费用户流量;第二种方式,接入简单,功能强大,但是可能需要一定的成本(比如付费);第三种方式,需要一定的开发成本(服务器托管费用忽略)。对于第三种情况的 socket,实现IM的文字加音视频聊天,做过的话你可能也会直接懵逼。但是,简单的文字聊天还好,不过你还是需要去定义一些协议来实现这样一个功能。如果我们使用websocket,那事情就变的简单很多。

WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范,WebSocketAPI被W3C定为标准。

WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端直接向客户端推送数据而不需要客户端进行请求,在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并允许数据进行双向传送。

目前常见的浏览器如 Chrome、IE、Firefox、Safari、Opera 等都支持 WebSocket,同时需要服务端程序支持 WebSocket。

来自维基百科 https://zh.wikipedia.org/wiki/WebSocket

websocket只是一种协议,类似http,因此与语言无关。这里我使用java来做服务端,同时提供android和html的客户端,通过一个简单的demo来介绍websocket的使用,接下来的一篇会对websocket进行分析。

服务端

这里我使用 Java-WebSocket 这个库来实现 websocket server。需要的jar包位于项目的 /Java-WebSocket/dist/java-websocket.jar 位置。效果如下:

server

代码如下:

public class SocketServer extends WebSocketServer {
    private static final int PORT = 2333;

    public static void main(String[] args) {
        SocketServer server = new SocketServer(PORT);
        server.start();

        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            int port = server.getPort();
            print(String.format("服务已启动: %s:%d", ip, port));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        InputStreamReader in = new InputStreamReader(System.in);
        BufferedReader reader = new BufferedReader(in);

        while (true) {
            try {
                String msg = reader.readLine();
                server.broadcastMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public SocketServer(int port) {
        super(new InetSocketAddress(port));
    }

    public SocketServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
        String address = webSocket.getRemoteSocketAddress().getAddress().getHostAddress();
        String message = String.format("(%s) <加入>", address);
        broadcastMessage(message);
        print(message);
    }

    @Override
    public void onClose(WebSocket webSocket, int code, String reason, boolean remote) {
        String address = webSocket.getRemoteSocketAddress().getAddress().getHostAddress();
        String message = String.format("(%s) <离开>", address);
        broadcastMessage(message);
        print(message);
    }

    @Override
    public void onMessage(WebSocket webSocket, String msg) {
        String address = webSocket.getRemoteSocketAddress().getAddress().getHostAddress();
        String message = String.format("(%s) %s", address, msg);
        broadcastMessage(message);
        print(message);
    }

    @Override
    public void onError(WebSocket webSocket, Exception e) {
        if (null != webSocket) {
            if (!webSocket.isClosed()) {
                webSocket.close(0);
            }
        }
        e.printStackTrace();
    }

    /**
     * 广播收到消息
     *
     * @param msg
     */
    private void broadcastMessage(String msg) {
        Collection<WebSocket> connections = connections();
        synchronized (connections) {
            for (WebSocket client : connections) {
                client.send(msg);
            }
        }
    }

    private static void print(String msg) {
        System.out.println(String.format("[%d] %s", System.currentTimeMillis(), msg));
    }
}

客户端

  • Android

    这里也是用java-websocket这个库,效果如下:

    android

    核心代码:

    public class WebSocketActivity extends BaseActivity {
    private static final int STATUS_CLOSE = 0;
    private static final int STATUS_CONNECT = 1;
    private static final int STATUS_MESSAGE = 2;
    
    @Bind(R.id.etIP)
    EditText etIP;
    @Bind(R.id.etPort)
    EditText etPort;
    @Bind(R.id.tvStatus)
    TextView tvStatus;
    @Bind(R.id.tvMsg)
    TextView tvMsg;
    @Bind(R.id.rgVersion)
    RadioGroup rgVersion;
    @Bind(R.id.etMessage)
    EditText etMessage;
    @Bind(R.id.svContent)
    ScrollView svContent;
    @Bind(R.id.viewMain)
    View viewMain;
    
    @Bind(R.id.btConnect)
    Button btConnect;
    @Bind(R.id.btDisconnect)
    Button btDisconnect;
    @Bind(R.id.btSend)
    Button btSend;
    
    @OnClick({R.id.btConnect, R.id.btDisconnect, R.id.btSend})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btConnect:
                connectToServer();
                break;
            case R.id.btDisconnect:
                if (null != mClient) {
                    mClient.close();
                }
                break;
            case R.id.btSend:
                if (null != mClient) {
                    String msg = etMessage.getText().toString();
                    if (!TextUtils.isEmpty(msg)) {
                        try {
                            mClient.send(msg);
                        } catch (NotYetConnectedException e) {
                            e.printStackTrace();
                            return;
                        }
                        etMessage.setText("");
                    }
                }
                break;
    
            default:
                break;
        }
    }
    
    private Client mClient;
    private Handler mHandle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            String message = String.format("[%d] %s\n", System.currentTimeMillis(), msg.obj.toString());
            tvMsg.append(message);
    
            switch (msg.what) {
                case STATUS_CONNECT:
                    btConnect.setEnabled(false);
                    btDisconnect.setEnabled(true);
                    btSend.setEnabled(true);
                    break;
                case STATUS_CLOSE:
                    btConnect.setEnabled(true);
                    btDisconnect.setEnabled(false);
                    btSend.setEnabled(false);
                    break;
                case STATUS_MESSAGE:
                    // TODO: 16/8/24
                    break;
                default:
                    break;
            }
            svContent.postDelayed(new Runnable() {
                @Override
                public void run() {
                    svContent.fullScroll(View.FOCUS_DOWN);
                }
            }, 100);
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_socket);
        ButterKnife.bind(this);
    
    
        System.setProperty("java.net.preferIPv6Addresses", "false");
        System.setProperty("java.net.preferIPv4Stack", "true");
    }
    
    private void connectToServer() {
        String ip = etIP.getText().toString();
        String port = etPort.getText().toString();
        if (TextUtils.isEmpty(ip) || TextUtils.isEmpty(port)) {
            Snackbar.make(viewMain, "IP and Port 不能为空", Snackbar.LENGTH_LONG).show();
            return;
        }
        String address = String.format("ws://%s:%s", ip, port);
        Draft draft = null;
        switch (rgVersion.getCheckedRadioButtonId()) {
            case R.id.rbDraft10:
                draft = new Draft_10();
                break;
            case R.id.rbDraft17:
                draft = new Draft_17();
                break;
            case R.id.rbDraft75:
                draft = new Draft_75();
                break;
            case R.id.rbDraft76:
                draft = new Draft_76();
                break;
    
            default:
                draft = new Draft_17();
                break;
        }
        try {
            URI uri = new URI(address);
            mClient = new Client(uri, draft);
            mClient.connect();
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return;
        }
    
        tvStatus.setText(address);
    }
    
    private class Client extends WebSocketClient {
    
        public Client(URI serverURI) {
            super(serverURI);
        }
    
        public Client(URI serverUri, Draft draft) {
            super(serverUri, draft);
        }
    
        @Override
        public void onOpen(ServerHandshake handShakeData) {
            Message msg = new Message();
            msg.what = STATUS_CONNECT;
            msg.obj = String.format("[Welcome:%s]", getURI());
            mHandle.sendMessage(msg);
        }
    
        @Override
        public void onMessage(String message) {
            Message msg = new Message();
            msg.what = STATUS_MESSAGE;
            msg.obj = message;
            mHandle.sendMessage(msg);
        }
    
        @Override
        public void onClose(int code, String reason, boolean remote) {
            Message msg = new Message();
            msg.what = STATUS_CLOSE;
            msg.obj = String.format("[Bye:%s]", getURI());
            mHandle.sendMessage(msg);
        }
    
        @Override
        public void onError(Exception ex) {
            ex.printStackTrace();
        }
    }
    

}
```

  • Html

    H5使用了这个库:sstephenson/prototype,效果如下:

    html5

    核心代码如下:

    <script type="text/javascript">
        document.observe("dom:loaded", function () {
            function log(text) {
                var value = (new Date).getTime();
                value += ": ";
                value += text.escapeHTML();
                value += $("log").innerHTML;
                $("log").innerHTML = value;
            }
    
            if (!window.WebSocket) {
                alert("浏览器不支持,换一个吧");
            }
    
            var ws;
    
            $("connectForm").observe("submit", function (e) {
                e.stop();
                ws = new WebSocket($F("uri"));
                ws.onopen = function () {
                    log("连接到服务器\n");
                }
                ws.onmessage = function (e) {
                    log("服务器信息: " + e.data + "\n");
                }
                ws.onclose = function () {
                    log("断开服务器连接\n");
    
                    $("uri", "connect").invoke("enable");
                    $("close").disable();
                    ws = null;
                }
    
                $("uri", "connect").invoke("disable");
                $("close").enable();
            });
            $("close").observe("click", function (e) {
                e.stop();
                if (ws) {
                    ws.close();
                    ws = null;
                }
            });
            $("sendForm").observe("submit", function (e) {
                e.stop();
                if (ws) {
                    var message = $("message");
                    ws.send(message.value);
                    message.value = "";
                    message.focus();
                }
            });
    
        });
    </script>
    

所有的代码都可以到github查看:ttdevs。下一篇将通过对websocket协议的分析,来对其做进一步的了解。

参考:

  1. https://github.com/sstephenson/prototype
  2. https://github.com/TooTallNate/Java-WebSocket/
  3. http://www.cnblogs.com/wlfcolin/p/5193583.html
Django使用websocket实现实时消息推送和聊天 websocket简介 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 在 WebSocket API 中,浏览器和服务... 阅读详情

相关推荐

使用WebSocket实现Android消息推送

在上述代码中,我们继承了WebSocketClient类,并重写了其中的几个方法。在onMessage方法中,可以处理收到的服务器推送消息;在onError方法中,可以处理连接过程中发生的错误。在Android应用中使用WebSocket可以实现消息推送功能,使得应用能够接收实时的推送消息,提升用户体验。在上述代码中,我们创建了一个WebSocketClient对象,并传入服务器的URL。最后,当我们不再需要连接时,可以调用WebSocketClient对象的close()方法来断开连接并释放资源。

HackMasterX的博客 538

Spring Boot WebSocket 客户端_springboot创建websocket客户端

外链图片转存中…(img-MoMKOEeI-1712914457688)]

2401_84254364的博客 1965

使用 WebSocket 实现实时消息推送

虽然 WebSocket 已经是个“老将”了,但它在实时通信领域的地位依然不可撼动。通过上面的示例,我们可以看到使用 Java 和 Spring Boot 实现 WebSocket 消息推送是多么简单和高效。

wjianwei666的专栏 888

JAVA做为客户端连接Websocket之wss

博主对长链接并不是特别熟练,但是什么代码是能正式用的,什么代码上不了生产只是个写着玩的demo 还是一眼能分辨出来的,代码主要参考 git@github.com:yimiancheng/netty-study.git ,写的很不错,代码优化空间很少了, 线程池博主是推荐用new ThreadPoolExecutor()创建,避免OOM的问题,除此之外应该是准生产环境级别代码了。断开连接会进入channelInactive方法(本文中未重写该方法, 在channelInactive方法中重连也是没问题的)

GeeLoong`s Blog 6821

Git下载安装及环境配置,解决安装包下载慢问题(详细版)

Git是我们平时开发都要用到的项目管理工具,虽然有网页版的Git网站,但是在本地安装Git后,可以直接使用命令语句来进行项目的上传与克隆。还是非常方便的。今天就来介绍下Git的下载。1、首先在浏览器中搜索Git,进入到Git的官方下载界面或者直接复制下面这个链接。 2、点击download进入下载界面,选择自己需要平台的文件 3、下载标准版就可以4、但是会发现下载速度很慢,就找个镜像版 5、点击 https://registry.npmmirror.com/binary.html?path=git-for-

mengxiang_的博客 6万+

基于PTB-XL数据集的深度学习心电信号分类

心电信号的分析与处理是心血管疾病诊断的重要手段。该领域的主要工作领域是分类,它越来越多地得到基于机器学习的算法的支持。在这项工作中,开发了一个深度神经网络来自动分类初级心电信号。这项研究是在PTB-XL数据库中包含的数据上进行的。提出了三种神经网络架构:第一种基于卷积网络,第二种基于SincNet,第三种基于卷积网络,

weixin_55121617的博客 7008

WebSocket协议实时通信技术原理

HTML5 WebSocket协议实时通讯机制 Ajax长轮询过程中,客户端通过频繁地向服务器发送HTTP请求的方式与服务器保持这一种虚拟的连接,此连接的方式属于循环连接而不属于长连接。 相对于HTTP协议这一非持久连接的特点来说,为避免HTTP轮询的滥用,2011年,由IETF(互联网工程任务组)制定并规范了WebSocket通信协议。 WebSocket通信协议是HTML5支持浏览器与服务器进行多路复用全双工(Full-Duplex)通信的技术,是一个持久化协议,允许服务器主动发送信息给客户端。客户

u012802070的专栏 728

一文读懂无人机AI识别算法,原理、技术、应用

本文详细介绍了无人机识别算法的工作原理、技术优势以及在多个行业的应用前景,展现了其在提升安全监控、资源管理和应急响应中的关键作用。尽管无人机识别算法在多个领域展现出巨大潜力,但仍面临技术挑战,如算法的准确性、数据的实时处理能力以及对复杂环境的适应性。4. 消防与城市管理: 无人机识别算法在消防和城市管理中的应用,为相关部门提供了及时的信息支持,提升了应急处置能力和城市管理水平。1. 公安监控: 无人机识别算法在公安领域的应用,可以实时识别犯罪嫌疑人和违法行为,有效提升城市治安管理的智能化水平。

foiadrone的博客 1849

java socket spring,如何正确实现spring-websocket java客户端

I am working on a Spring WebSocket Stomp Client for my WebSocket Server and I am getting conflicting information.I have found 2 ways to get it to work and without going into too much detail I was wond...

weixin_35448535的博客 382

PADS安装教程(超详细)

声明:个人实践 一、工具准备 pads9.5完整破解版百度网盘提取码【wjx9】 链接:https://pan.baidu.com/s/1pIP6BJO435ssmF7Hjwyz8A 提取码:wjx9 安装环境:win10 64位 二、开始解压安装 关闭杀毒,exe以管理员身份运行 点击【skip】,不然又得licese 点击【agree】 点击【modify】 设置安装的工具、路径及工程路径 设置完成后,开始【install】 在之后注册。 将Crack复制到PADS安装目录

qq_42057393的博客 5万+

电容式触摸抗干扰设计:工业环境下的实践方案

针对工业环境中电容式touch器件易受干扰的问题,分享实用的硬件布局与软件滤波方案,提升touch响应稳定性和可靠性。

weixin_35755562的博客 845

websocket的学习及坑

最近项目上需要优化,原来的方法是通过http请求来获取数据,但是客户觉得速度不够快,一番思索后,决定用websocket实时传输数据。 准备知识:websocket是什么,为什么要用websocket,如何使用websocket。 WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通...

lgbisha的博客 1732

异步处理优化:多线程线程池与消息队列的选择与应用

本文讨论了两种常见的异步处理方式:多线程线程池和消息队列(MQ)。通过将非核心逻辑异步化,可以提升系统的性能和响应速度。多线程线程池适用于本地并发任务,适合需要快速响应的场景,但无法处理跨服务任务。消息队列则适用于分布式系统,能够解耦服务并保证任务的可靠性和顺序性,适合处理高并发和高可靠性要求的任务。文章提供了实际代码示例,帮助开发者根据业务需求选择合适的异步处理方式。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 170万+

Django使用Channels实现WebSocket消息推送功能

django的channels配置,解决channels不生效问题,后端实时消息推送

weixin_43901010的博客 2909

深入理解 DDR5 Bank Group 与并行调度机制

Bank Group架构和并行调度机制,是DDR5性能跃升的核心秘密!理解它们才能写出高效调度程序,设计出性能强劲又稳定的DDR5系统。🎯记住:Bank Group不是简单的分组,而是高速并行的“魔法钥匙”,巧用它,你的DDR5设计将爆发惊人性能!

weixin_43199439的博客 318

乐企增值税及附加税费(一般纳税人适用)申报表填报能力说明文档V3.009

申报表填报,是指企业在自有业务系统中将算税结果数据按预填规则要求加工汇总后,进行申报表填报的过程。申报表填报前对纳税申报所需的前提条件进行校验。校验通过后,应用前序算税结果数据及预填规则进行纳税申报表预填,生成申报表数据。申报表数据通过申报业务规则校验后,经纳税人确认即完成申报表填报业务。

记录点滴 347

Spring Boot使用WebSocket实现后台消息推送

Spring Boot使用WebSocket实现后台消息推送 为什么需要WebSocket? 已经有了HTTP协议,为什么还需要另一个协议?这是因为HTTP协议有一个缺陷:通信只能由客户端发起。 当客户端想知道某个订单是否已经完成支付,使用HTTP协议只能由客户端向服务器发出请求,服务器返回查询结果,可以知道HTTP协议做不到服务器主动向客户端推送信息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息

kaven 2279

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

使用golang实现websocket消息推送

golang 推送 webSocket ws rabbitmq gin docker

a34ErxV的博客 3111

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

在Spring Boot框架下使用WebSocket实现消息推送

Spring Boot的学习持续进行中。前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的支持(使用Spring Boot开发Web项目(二)之添加HTTPS支持),在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket。 什么是WebSocket

江南一点雨的专栏 11万+

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

使用php实现websocket实时消息推送的方法整理

php实现websocket实时消息推送建立websocket服务端建立client客户端 建立websocket服务端 liaotianServer.php <?php /** * Created by qipa250 * Date: 2019/08/05 * Time: 14:33 */ class SocketService { private $address = '...

徊忆羽菲 2676

使用WebSocket并在Java和Vue中实现后台消息推送

这两个方法可以在Vue组件的模板中绑定到按钮或其他事件上,以实现相应的功能。在上面的例子中,我们使用Java EE WebSocket API创建了一个WebSocket服务器。以上是大致的实现步骤,具体的代码实现以及使用的库可能会有所不同,具体根据项目需求和开发环境进行调整。在上述代码中,首先在Vue组件中导入WebSocket库和其他需要使用的库。回调方法中,我们打印出WebSocket错误的详细信息。回调方法中,我们打印出WebSocket关闭的原因。回调方法中,我们打印出接收到的消息,并使用。

qq_31532979的贺公子之数据科学与艺术博客,致力于科技向善,拥抱开源,要用技术的影响力来领导团队,而不是威权和职位! 1514

php websocket消息推送,怎么在php中使用websocket实现实时消息推送功能

怎么在php中使用websocket实现实时消息推送功能发布时间:2021-02-15 10:43:40来源:亿速云阅读:62作者:Leah怎么在php中使用websocket实现实时消息推送功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。SocketService.php...

weixin_42299560的博客 419

使用spring boot+WebSocket 实现定时消息推送(基于注解)

作者:清风徐来水波不清https://www.jianshu.com/p/62132f605669前言前面文章也有 websocket 相关的文章,为什...

java思维导图 3480

【SpringBoot学习】43、SpringBoot 使用 Uniapp 集成 Websocket 实现消息推送

文章目录SpringBoot 使用 Uniapp 集成 Websocket 实现消息推送1、SpringBoot 配置(1)依赖配置(2)Websocket 配置(3)测试控制层(4)服务端业务主动发送消息2、Uniapp 配置微信公众号 SpringBoot 使用 Uniapp 集成 Websocket 实现消息推送 1、SpringBoot 配置 (1)依赖配置 导入依赖,一样的 stater,这里就不多概述了 <dependency> <gro

Tellsea 3964

使用spring boot +WebSocket实现(后台主动)消息推送

前言:使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!主要是tomcat的兼容与支持。有个需求:APP用户产生某个操作,需要让后台管理系统部分人员感知(表现为一个页面消息)。最早版本是后台管理系统轮训,每隔一段时间轮训一次,由于消息重要,每隔几秒就查一次。这样做明显很不雅!会消耗大量资...

zhangdehua678的博客 14万+
上一篇: Android通过Intent传递数据
下一篇: Mac下Homebrew 安装 Mysql
ttdevs
博客等级 码龄15年 388粉丝 91原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值