精华内容
下载资源
问答
  • WebSocket客户端和服务端实例源码

    千次下载 热门讨论 2015-08-14 13:30:01
    WebSocket客户端和服务端实例源码 WebSocket ws实例 HTML5 用java实现的服务端 Websocket与服务器的正常通信 众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器接收请求后进行处理并返回...
  • 主要为大家详细介绍了WebSocket实现简单客服聊天系统,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 一、websocket协议简介 WebSocket是为解决客户端与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。 二...

    <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">一、websocket协议简介</span>

    WebSocket是为解决客户端与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。


    二、php使用的一些websocket函数

    resource socket_create ( int $domain , int $type , int $protocol )

    bool socket_set_option ( resource $socket , int $level , int $optname , mixed $optval )

    bool socket_bind ( resource $socket , string $address [, int $port = 0 ] )

    bool socket_listen ( resource $socket [, int $backlog = 0 ] )

    以上四个方法都是非阻塞的,而下面的几个方法都是阻塞的或者是有timeout的

    int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )

    resource socket_accept ( resource $socket )

    int socket_recv ( resource $socket , string &$buf , int $len , int $flags )

    string socket_read ( resource $socket , int $length [, int $type = PHP_BINARY_READ ] )

    后面会对socket_recv()和socket_read()进行介绍


    三、php服务端代码分析

    <?php
    /**
     * Created by PhpStorm.
     * User: changshuiwang
     * Date: 2016/9/5
     * Time: 14:35
     */
    error_reporting(~E_WARNING & ~E_NOTICE);
    set_time_limit(0);
    class Wserver{
        public $address;
        public $port;
        public $master;  <span style="color: rgb(255, 0, 0);">// 连接 server 的 client</span>
        public $sockets = array(); <span style="color:#ff0000;">// 不同状态的 socket 管理</span>
        //public $handshake = false; <span style="color:#ff0000;">// 判断是否握手</span>
        public $request = array(); <span style="color:#ff0000;">//过来的请求</span>
        public $response = array();<span style="color:#ff0000;">//服务人员</span>
        public $client = array();   <span style="color:#ff0000;">//正在进行的会话</span>
        public $wenhou = "你好,有什么可以帮助你的么?"; <span style="color:#ff0000;">//建立请求的问候语</span>
        public $refuse = "对不起,请稍等,暂时无空闲客服人员。"; <span style="color:#ff0000;">//没有空客服人员的回话</span>
        //public $tag=false; //默认为false,标志位,是否有空客服人员
    
        function __construct($address, $port)
        {
            $this->address=$address;
            $this->port=$port;
            // 建立一个 socket 套接字
            $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
            or die("socket_create() failed");
            socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)
            or die("socket_option() failed");
            socket_bind($this->master, $this->address, $this->port)
            or die("socket_bind() failed");
            socket_listen($this->master)
            or die("socket_listen() failed");
            $this->sockets[] = $this->master;
            // debug
            echo("Master socket  : " . $this->master . "\n");
    
            //socket_set_nonblock($this->master);<span style="color:#ff0000;">//让进程非阻塞,主要是socket_accept(),因为这里没有让master参与到通信中,所以可以不加这一句</span>
        }
        function start(){
            while(true) {
                $changed=$this->sockets;<span style="color:#ff0000;">//让当前的socket列表加入到监听中</span>
                $write = NULL;
                $except = NULL;
                $num=socket_select($changed, $write, $except, 0);
                if($num==0){
                    continue;
                }
                <span style="color:#ff0000;">//$changed是引用变量,调用之后$changed是状态改变的数组,如果来新连接$changed里面会有$this->master</span>
    
                if(in_array($this->master,$changed)){
                    <span style="color:#ff0000;">//来新连接了</span>
                    $socket_new = socket_accept($this->master);<span style="color:#ff0000;">//返回值是一个resource</span>
    
                    //socket_set_nonblock($socket_new);<span style="color:#ff0000;">//不能让master以外的socket为非阻塞的,因为会涉及到读socket数据</span>
    
                    <span style="color:#ff0000;">//通过socket获取数据执行handshake</span>
                    @$header = socket_read($socket_new, 1024);
                    $this->perform_handshaking($header, $socket_new, $this->address, $this->port);
    
    
                    @socket_getpeername($socket_new, $ip);
                    $msg=$this->diff($ip);<span style="color:#ff0000;">//这里是根据ip来区分是客户</span> 
                    switch ($msg){
                        case 'client':
                            $tag=$this->addClient($socket_new);
                            $message=$tag=="client"?$this->wenhou:$this->refuse;//发送的消息
                            $response = $this->mask(json_encode(array('type'=>'system', 'message'=> $message)));
                            $this->send_message([$socket_new],$response);
                            break;
                        default:
                            //$message="有新请求";
                            $this->addResponse($socket_new);
                            $index=array_search($socket_new,$this->response);
                            $this->changeStatus($index);
                            $message="客服人员已来!";
                            $response = $this->mask(json_encode(array('type'=>'system', 'message'=> $message)));
                            $this->send_message([$this->client["$index"],$response]);
                            break;
                    }
    
                    <span style="color:#ff0000;">//从改变的socket数组里面删除</span>
                    $found_socket = array_search($this->master, $changed);
                    unset($changed[$found_socket]);
                    echo $msg." connect\n";
                }
    
                <span style="color:#ff0000;">//未加入会话的请求的处理,比如未加入会话但发来消息,或者是在等待过程中断开</span>
                foreach ($this->request as $request){
                    if(in_array($request,$changed)){
    
                    }
                }
    
    <span style="color:#ff0000;">            //如果是断开连接,socket会先发送一个消息,socket_read()返回false但是socket_recv()会读取到数据,
                //所以foreach会执行两次,指的是两次while然后两次foreach
                //第一次socket_recv会读取到数据,第二次socket_recv()读取不到数据,就会直接走下面的判断语句
                //不能直接使用socket_read()因为会在读到换行时候结束</span>
                foreach ($changed as $changed_socket){
    
                    <span style="color:#ff0000;">//如果有client数据发送过来</span>
                    $tag=false;
    
                    while(socket_recv($changed_socket, $buf, 1024, 0) >= 1)<span style="color:#ff0000;">//这里就要限制每次发送数据最多为1024,</span>
                    {
                        //var_dump("wh");
                        $tag=true;
                        //解码发送过来的数据
                        $received_text = $this->unmask($buf);
                        //编码需要发出的数据
                        $response_text = $this->mask(json_encode(array('type'=>'usermsg', 'message'=>$received_text)));
                        if($response_text=="\000"){<span style="color:#ff0000;">//对应的是断开连接的,如果是断开连接,socket_recv()会收到一串字符</span>
                            break;
                        }
                        if(in_array($changed_socket,$this->client)){
                            $index=array_search($changed_socket,$this->client);
                            $this->send_message([$this->response["$index"]],$response_text);
                        }else{
                            $index=array_search($changed_socket,$this->response);
                            $this->send_message([$this->client["$index"]],$response_text);
                        }
                        break;
                        //跳出循坏,因为socket_recv()会阻塞,但是如果socket断开了之后,socket_recv()就不会阻塞了
                    }
    
                    <span style="color:#ff0000;">//已经读了数据的跳过下面的断开连接判断,不然会阻塞</span>
                    if($tag==true){
                        continue;
                    }
    
                    <span style="color:#ff0000;">//检查offline的client,断开连接</span>
    
                    @$buf = socket_read($changed_socket, 1024, PHP_NORMAL_READ);
                    if ($buf === false) {<span style="color:#ff0000;">//没有接受到数据的时候,但是这个socket又有变化,也就是断开连接</span>
                        $found_socket = array_search($changed_socket, $this->sockets);<span style="color:#ff0000;">//返回下标</span>
                        socket_getpeername($changed_socket, $ip);
                        unset($this->sockets[$found_socket]);<span style="color:#ff0000;">//必须要手动删除</span>
    
                        $msg=$this->diff($ip);
                        $response = $this->mask(json_encode(array('type'=>'system', 'message'=>'断开连接')));
                        switch ($msg){
                            case "client":
                                $index=array_search($changed_socket,$this->client);
                                $this->client["$index"]="";
                                if($this->changeStatus($index)){
                                    $this->send_message([$this->response["$index"]],$response);<span style="color:#ff0000;">//向客服发送断开</span>
                                    $response = $this->mask(json_encode(array('type'=>'system', 'message'=>$this->wenhou)));
                                    $this->send_message([$this->client["$index"]],$response);<span style="color:#ff0000;">//新客户的问候语</span>
                                }
                                break;
                            default:
                                $index=array_search($changed_socket,$this->response);
                                unset($this->response["$index"]);<span style="color:#ff0000;">//释放客服的数组元素</span>
                                $this->send_message([$this->client["$index"]],$response);
                                $index=array_search($this->client["$index"],$this->sockets);
                                unset($this->sockets["$index"]);<span style="color:#ff0000;">//删掉监听的socket</span>
                                break;
                        }
    
                    }
                }
            }
        }
    
        function send_message($clients,$msg)
        {
            foreach($clients as $changed_socket)
            {
                socket_write($changed_socket,$msg,strlen($msg));
            }
            return true;
        }
    
    
        //解码数据
        function unmask($text) {
            $length = ord($text[1]) & 127;
            if($length == 126) {
                $masks = substr($text, 4, 4);
                $data = substr($text, 8);
            }
            elseif($length == 127) {
                $masks = substr($text, 10, 4);
                $data = substr($text, 14);
            }
            else {
                $masks = substr($text, 2, 4);
                $data = substr($text, 6);
            }
            $text = "";
            for ($i = 0; $i < strlen($data); ++$i) {
                $text .= $data[$i] ^ $masks[$i%4];
            }
            return $text;
        }
    
        //编码数据
        function mask($text)
        {
            $b1 = 0x80 | (0x1 & 0x0f);
            $length = strlen($text);
    
            if($length <= 125)
                $header = pack('CC', $b1, $length);
            elseif($length > 125 && $length < 65536)
                $header = pack('CCn', $b1, 126, $length);
            elseif($length >= 65536)
                $header = pack('CCNN', $b1, 127, $length);
            return $header.$text;
        }
    
        //握手的逻辑
        function perform_handshaking($receved_header,$client_conn, $host, $port)
        {
            $headers = array();
            $lines = preg_split("/\r\n/", $receved_header);
            foreach($lines as $line)
            {
                $line = chop($line);
                if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
                {
                    $headers[$matches[1]] = $matches[2];
                }
            }
    
            $secKey = $headers['Sec-WebSocket-Key'];
            $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
            $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
                "Upgrade: websocket\r\n" .
                "Connection: Upgrade\r\n" .
                "WebSocket-Origin: $host\r\n" .
                "WebSocket-Location: ws://$host:$port/demo/shout.php\r\n".
                "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
            socket_write($client_conn,$upgrade,strlen($upgrade));
        }
    
        /**
         * 查询空客服,如果没有就返回false
         * @return bool|int|string
         */
        function searchEmptyResponse(){
            foreach ($this->response as $k=>$v){
                if(empty($this->response[$k])){
                    return $k;
                }else{
                    return false;
                }
            }
        }
    
        /**
         * 加入到正在会话中
         * @param $socket
         */
        function addClient($socket){
            if(empty($this->response)||count($this->response)==count($this->client)){
                //满了
                $this->request[]=$socket;
                return "request";
            }else{
                $index=$this->searchEmptyResponse();
                $this->client[$index]=$socket;//加入正在会话中
                $this->sockets[]=$socket;//加入到监控数组
                return "client";
            }
        }
    
        /**
         * 加入到客服中
         * @param $socket
         */
        function addResponse($socket){
            $this->response[]=$socket;
            $this->sockets[]=$socket;
        }
    
        /**
         * 区分是客服还是请求
         * @param $socket
         * @param $ip
         */
        public function diff($ip){
            if($ip=="xxxxxx"){
                //客服
                return "response";
            }else{
                return "client";
            }
    
        }
    
        /**
         * 将原先等候的加入到正在会话中
         * @param $loc
         * @return bool
         */
        private function changeStatus($loc){
            if(empty($this->request)){
                //等待队列为空
                return false;
            }else{
    <span style="white-space:pre">	</span>    foreach ($this->request as $k=>$v){
              $index=$k;
              break;
            }
            $this->client["$loc"]=$this->request["$index"];
            $this->sockets[]=$this->request["$index"];
            unset($this->request["$index"]);
            return true;            
            }
        }
    
        //private function
    }
    
    $server=new Wserver('xxxxxx','10000');
    $server->start();
    
    


    四、客户端代码


    <?php
    /**
     * Created by PhpStorm.
     * User: changshuiwang
     * Date: 2016/9/5
     * Time: 14:46
     */
    ?>
    <!DOCTYPE html>
    <script src="jquery-1.6.2.min.js"></script>
    <script>
        $(function(){
            var wsServer = 'ws://xxxx:xxxx';
            var ws = new WebSocket(wsServer);
            //alert("22");
            ws.onopen = function(e){
                //alert("连接成功");
            }
            ws.onclose = function(e){
                alert("连接关闭");
            }
            ws.onerror = function (e) {
                //alert("连接错误");
            }
            ws.onmessage = function (evt) {
                //alert(evt);
                //alert("22");
                var data=JSON.parse(evt.data);
                alert(data.message);
                //var message=JSON.stringify(evt.data);
                //alert(message);
                //发送字符串,服务器端只需要unmask就可以了,如果是json串,后端就要json_decode
            };
            $(":button").click(function(){
                var message=$("#message").val();
                console.log(ws.send(message));
            })
        })
    </script>
    <body>
        <div style="width:200px;height:600px;border-color:#0000FF;border: 2px solid;margin-bottom:20px;">
            11
        </div>
        <form action="#">
            <textarea id="message" name="message" style="width:200px;height:50px;resize:none;">
            </textarea>
            <input type="button" name="send" value="发送">
        </form>
    </body>
    


    五、一点总结

    其实这只是一个小demo,不过写到这里就发现,完全没有写下去的必要了,php用来监听socket是用的socket_select(),对应的底层实现应该就是select机制,现在都用的epoll机制了。关于select()和epoll(),大家看看这篇我读过的最好的epoll讲解

    展开全文
  • 0.56 websocket的在线客服系统 ## WebSocket介绍 WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:1.WebSocket是一种双向通信协议,在建立连接后...
  • 在线客服系统源代码,客户端使用 websocket实现,服务器端使用C#实现(服务器端源码暂无开放,如果需要请联系:boohiya@ubadis.com),可扩展性强根据自己的需求灵活扩展。 演示地址 : 1.客户入口 ...

    html5在线客服源码 websocket c#实现,可扩展 im 即时通讯

    在线客服系统源代码,客户端使用 websocket实现,服务器端使用C#实现,完整的源代码。可扩展性强根据自己的需求灵活扩展。 

    演示地址 :

    1.客户入口

    http://47.92.79.165/

    2.客服入口

    http://47.92.79.165/

     

    1. 启动服务器  chat/Server.exe
    2. 设置监听端口号,点击启动按钮,只要端口不冲突,服务器启动成功。

     

    1. 开始聊天,本项目主要为在线客服模式,根据自己的需求可以进行修改,可扩展性强。
    2. 客户端的配置,打开js/config.js,修改ip(服务器ip地址或者域名) 和port(端口)。
    3. 在线客服包含两个角色,客户(h.html)和客服(f.html)
    4. 参数说明:
    1. 客户页面 chat.html?id=yonghu1&&toid=kefu1  ,id是发送方,toid接收方,都支持字符串。
    2. 客服页面chatkefu.html?id=kefu1&&toid=yonghu1 ,id是发送方,toid接收方,都支持字符串。可以处理多个客户的问题。如下图:可以选择对应的客户进行聊天。

     

     

     

     

     

    7.数据传输协议:json格式

    {"from":{"user_id":"","name":"","custom":"","type":""},"to":{"user_id":"","name":"","custom":"","type":""},"data":"","type":1}

    from 发送方 ,to接收方;type消息类型 值10000 是自定义消息 详细请看chattype.cs文件 ,data 数据

    用户模型:user_id 用户编号 ,name 名称,custom 自定义属性 type类型

    获取源码

    展开全文
  • 1. f.html 客服端 2. h.html 客户端 3. 打开这两个文件进行聊天 根据自己的需求可以扩展,说明文档比较全
  • 本项目里面的demo基本上都是基于Java,基于Vue,数据库MySql。 一,运行demo 下载代码后,先配置好yml文件:数据库和微信号appid,secret(其他包下面有数据库)。...基于WebSocket和微信API,实现自定义微信客服
  • webSocket 实现 客服聊天逻辑

    千次阅读 2018-10-17 11:38:06
    文章目录写在前面代码前端客服端用户端后端各事件处理类socket连接管理类测试类效果图 写在前面 代码基本都是网上百度的,自己根据业务需要,进行了一些修改。 前端页面不擅长调试,基本逻辑完成了,可以用 tab ...

    写在前面

    	代码基本都是网上百度的,自己根据业务需要,进行了一些修改。
    	前端页面不擅长调试,基本逻辑完成了,可以用 tab 选项卡来展示不同的聊天人员
    	给有需要的人。大神轻喷。
    

    代码

    前端

    客服端

    <!DOCTYPE html>
    <html>
    <head>
        <title>Java后端WebSocket的Tomcat实现</title>
          <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport' />
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <h1>客服界面</h1>
        内容:<input id="text" type="text"/> 用户:<input id="user" type="text"/>
        <button onclick="send()">发送消息</button>
        <hr/>
        <button onclick="closeWebSocket()">关闭WebSocket连接</button>
        <hr/>
        <div id="message"></div>
    </body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script type="text/javascript">
        var websocket = null;
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8887");
        }
        else {
            alert('当前浏览器 Not support websocket')
        }
    
        //连接发生错误的回调方法
        websocket.onerror = function () {
            setMessageInnerHTML("WebSocket连接发生错误");
        };
    
        //连接成功建立的回调方法
        websocket.onopen = function () {
            // 连接成功,注册自己
            websocket.send("register|admin");
        }
    
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
    
        //连接关闭的回调方法
        websocket.onclose = function () {
            setMessageInnerHTML("WebSocket连接关闭");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            closeWebSocket();
        }
    
        //将消息显示在网页上
        function setMessageInnerHTML(innerHTML) {
            var contents =  innerHTML.split("|");
            if($("#"+contents[1]).length > 0){
                $("#"+contents[1]).append("<font color=red>"+contents[1]+":"+contents[2]+"<br/></font>")
            }else{
                document.getElementById('message').innerHTML += '<div id='+contents[1]+' style="display:inline-block;border:1px solid red;width:200px;height:200px" >'+"<font color=red>"+ contents[1]+":"+contents[2] + '<br/></font>'+'</div> ';
            }
        }
    
        //关闭WebSocket连接
        function closeWebSocket() {
            websocket.close();
        }
    
        //发送消息
        function send() {
            var message = document.getElementById('text').value;
            var user = document.getElementById('user').value;
            $("#"+user).append("<font color=green>"+"我:"+message+"<br/></font>")
            websocket.send("chat|"+user+"|"+message);
        }
    </script>
    </html>
    

    用户端

    <!DOCTYPE html>
    <html>
    <head>
        <title>Java后端WebSocket的Tomcat实现</title>
          <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport' />
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <h1>用户界面</h1>
        内容:<input id="text" type="text"/>
        <button onclick="send()">发送消息</button>
        <hr/>
        <button onclick="closeWebSocket()">关闭WebSocket连接</button>
        <hr/>
        <div id="message"></div>
    </body>
    
    <script type="text/javascript">
        var websocket = null;
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8887");
        }
        else {
            alert('当前浏览器 Not support websocket')
        }
    
        //连接发生错误的回调方法
        websocket.onerror = function () {
            setMessageInnerHTML("WebSocket连接发生错误");
        };
    
        //连接成功建立的回调方法
        websocket.onopen = function () {
            // 连接成功,注册自己
            websocket.send("register|"+new Date().getTime());
        }
    
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
    
        //连接关闭的回调方法
        websocket.onclose = function () {
            setMessageInnerHTML("WebSocket连接关闭");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            closeWebSocket();
        }
    
        //将消息显示在网页上
        function setMessageInnerHTML(innerHTML) {
            var contents = innerHTML.split("|");
            document.getElementById('message').innerHTML +="<font color=red>"+ "客服:"+ contents[2] + '<br/></font>';
        }
    
        //关闭WebSocket连接
        function closeWebSocket() {
            websocket.close();
        }
    
        //发送消息
        function send() {
            var message = document.getElementById('text').value;
            document.getElementById('message').innerHTML +="<font color=green>"+ "我:" + message + '<br/></font>';
            websocket.send("chat|admin|"+message);
        }
    </script>
    </html>
    

    后端

    各事件处理类

    package WebSocket;
    
    import org.java_websocket.WebSocket;
    import org.java_websocket.handshake.ClientHandshake;
    import org.java_websocket.server.WebSocketServer;
    
    import java.net.InetSocketAddress;
    import java.net.UnknownHostException;
    
    /**
     * @author kangxuan
     * @date 2018/3/13 18:32
     * @description
     */
    public class WebSocketEntity extends WebSocketServer {
    
        public WebSocketEntity(int port) {
            super(new InetSocketAddress(port));
        }
    
        public WebSocketEntity(InetSocketAddress address) {
            super(address);
        }
    
    
        @Override
        public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
            System.out.println();
            System.out.println("==============================打开连接===============================");
        }
    
    
    
        @Override
        public void onClose(WebSocket webSocket, int i, String s, boolean b) {
            System.out.println();
            System.out.println("================================关闭连接==============================");
        }
    
        @Override
        public void onMessage(WebSocket webSocket, String s) {
            System.out.println();
            System.out.println("==============================接收消息================================");
            System.out.println("接受到的消息:"+s);
    
            InetSocketAddress remoteSocketAddress = webSocket.getRemoteSocketAddress();
            int port = remoteSocketAddress.getPort();
            String[] split = s.split("\\|");
            if (s.startsWith("register")){ // 注册
    
                WebSocketPool.addConn(split[1],webSocket);
            }else if (s.startsWith("chat")){// 聊天
                WebSocket conn = WebSocketPool.getConn(split[1]);
                // 根据连接获取用户
    
                conn.send("response|"+WebSocketPool.getUserBySocket(webSocket)+"|"+split[2]);
            }
    
            if ("@heart".equals(s)){
                System.out.println("-------------接受到心跳包"
                );
            }
        }
    
        @Override
        public void onError(WebSocket webSocket, Exception e) {
            System.out.println();
            System.out.println("==============================错误==================================");
        }
    }
    
    

    socket连接管理类

    package WebSocket;
    
    import java.net.InetSocketAddress;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.java_websocket.WebSocket;
    
    /**
     * @author kangxuan
     * @date 2018/3/13 18:27
     * @description
     */
    public class WebSocketPool {
        private static Map<String, WebSocket> map = new HashMap<>();
        private static Map<String,String> webSocketUserMap = new HashMap<>();
    
        /**
         * 存入socket连接
         */
        public static void addConn(String name, WebSocket conn) {
            map.put(name,conn);
            webSocketUserMap.put(getKey(conn),name);
        }
    
        /**
         * 获取 socket连接对应的用户
         */
        public static String getUserBySocket(WebSocket webSocket){
            return webSocketUserMap.get(getKey(webSocket));
        }
    
        /**
         * 获取 webSocket唯一连接
         * @param webSocket
         * @return
         */
        private static String getKey(WebSocket webSocket){
            InetSocketAddress remoteSocketAddress = webSocket.getRemoteSocketAddress();
            return remoteSocketAddress.getAddress().getHostAddress()+":"+remoteSocketAddress.getPort();
        };
    
        /**
         * 取出socket连接
         */
        public static WebSocket getConn(String name) {
            WebSocket webSocket = map.get(name);
            return webSocket;
        }
    
        /**
         * 给所有用户发信息
         */
        public static void sendToAll(WebSocket webSocket,String msg){
            for (Map.Entry<String, WebSocket> stringWebSocketEntry : map.entrySet()) {
                String key = stringWebSocketEntry.getKey();
                WebSocket value = stringWebSocketEntry.getValue();
                int port = webSocket.getRemoteSocketAddress().getPort();
                if (!String.valueOf(port).equals(key)){
                    value.send(port+":"+msg);
                }
    
            }
        }
    }
    
    

    测试类

    package WebSocket;
    
    import org.java_websocket.WebSocket;
    import org.java_websocket.WebSocketImpl;
    
    import java.util.Scanner;
    
    /**
     * @author kangxuan
     * @date 2018/3/13 18:26
     * @description
     */
    public class WebSocketTest {
        public static void main(String[] args) {
            WebSocketImpl.DEBUG = false;
            int port = 8887; // 端口
            WebSocketEntity s = new WebSocketEntity(port);
            s.start();
        }
    }
    
    

    效果图

    在这里插入图片描述

    源码

    其实我粘贴出来的基本就是源码了,只差了一个websocket的jar包,我把源码放到百度盘了,有需要的下载吧!
    链接:https://pan.baidu.com/s/11SajF8hlNIfo3KPA_-xZIA
    提取码:iyld
    复制这段内容后打开百度网盘手机App,操作更方便哦

    展开全文
  • 该文基于开源项目分析,总结了IM相关的一些知识点,如何实现,以及针对客服业务需要补充的几个点。开源系统使用netty+websocket/socket搭建IM系统,前端实现了jsp和layui,服务端内容较完整,前端可根据自己实际情况...

    该文基于开源项目分析,总结了IM相关的一些知识点,如何实现,以及针对客服业务需要补充的几个点。

    开源系统使用netty+websocket/socket搭建IM系统,前端实现了jsp和layui,服务端内容较完整,前端可根据自己实际情况搭建。

    感谢开源项目的贡献。地址:

    https://gitee.com/qiqiim/qiqiim-server

    IM服务

    1.网络协议

    传输层

    tcp

    面向连接的、可靠的、基于字节流的传输层通信协议,keepalive 机制、ack机制保障连接和消息的可靠性。

    应用层

    websocket

    在TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。适用于IM即时通讯。

    http

    短连接,业务操作类接口。

    2.数据传输格式

    protobuf,适用于高并发场景下的消息传输

    使用场景:

    用户A发送消息时,前端通过protobuf序列化消息,将其send到服务端,服务端接收到后反序列化消息,处理完成后再次序列化消息,发送给客服B,接收到消息时同样也要反序列化消息展示。

    ps:客服聊天系统中用到的消息类型为绑定、心跳、普通消息,不同场景下发送的消息类型不同。

    项目中用到的消息格式:

    消息包 Message.proto,其中content为下面的消息内容:

    syntax = "proto3";

    package com.black.services.customerIm.common.model.proto;

    option java_outer_classname="MessageProto";

    message Model {

    string version = 1;//接口版本号

    string deviceId = 2;//设备uuid

    uint32 cmd = 3;//请求接口命令字 1绑定 2心跳 3上线 4下线 5消息

    string sender = 4;//发送人

    string receiver = 5;//接收人

    string groupId =6;//用户组编号(暂时可忽略)

    uint32 msgtype = 7;//请求1,应答2,通知3,响应4 format

    uint32 flag = 8;//1 rsa加密 2aes加密

    string platform = 9;//mobile-ios mobile-android pc-windows pc-mac

    string platformVersion = 10;//客户端版本号

    string token = 11;//客户端凭证

    string appKey = 12;//客户端key

    string timeStamp = 13;//时间戳

    string sign = 14;//签名

    bytes content = 15;//请求数据

    }

    消息内容 MessageBody.proto:

    syntax = "proto3";

    package com.black.services.customerIm.common.model.proto;

    option java_outer_classname="MessageBodyProto";

    message MessageBody {

    string title = 1; //标题

    string content = 2;//内容

    string time = 3;//发送时间

    uint32 type = 4;//0 文字 1 文件

    string extend = 5;//扩展字段

    }

    开发可以自定义proto格式(上面.proto那种格式),然后通过protoc命令生成对应的java文件,protoc安装方式:http://google.github.io/proto-lens/installing-protoc.html

    3.连接可靠性

    实现心跳保活

    websocket受到nginx缺省为60秒的proxy_read_timeout的影响,超过时间没有发送任何消息,连接会自动断开。

    解决办法:服务端在连接没有消息传输后,到达一定时间后发送心跳包,客户端收到心跳包回一个响应包,如果心跳发送一定时间后还未收到响应,则关闭连接。

    netty使用IdleStateHandler处理,设置readIdleTime(读超时时间)和writeIdleTime(写超时时间),当读超时触发后发送心跳包到客户端(浏览器),客户端(浏览器)收到心跳包后回复一个心跳回应包(需要前端监听类型为心跳包的消息,收到后发送心跳回应);如果服务端心跳请求发出后一定时间内未收到回复,可断开连接。

    服务端超时触发的代码:

    /**

    * 超时触发此方法

    *

    * @param ctx

    * @param o

    * @throws Exception

    */

    @Override

    public void userEventTriggered(ChannelHandlerContext ctx, Object o) throws Exception {

    // 服务端发个心跳包,客户端要回一个才行(需要前端实现)

    if (o instanceof IdleStateEvent && ((IdleStateEvent) o).state().equals(IdleState.WRITER_IDLE)) {

    if (StringUtils.isNotEmpty(sessionId)) {

    MessageProto.Model.Builder builder = MessageProto.Model.newBuilder();

    builder.setCmd(NettyConstants.CmdType.HEARTBEAT);//心跳包

    builder.setMsgtype(NettyConstants.ProtobufType.SEND);

    ctx.channel().writeAndFlush(builder);

    }

    }

    //如果心跳请求发出70秒内没收到响应,则关闭连接

    if (o instanceof IdleStateEvent && ((IdleStateEvent) o).state().equals(IdleState.READER_IDLE)) {

    //服务端收到上一次的心跳响应后会设置这个响应时间

    Long lastTime = (Long) ctx.channel().attr(NettyConstants.SessionConfig.SERVER_SESSION_HEARBEAT).get();

    if (lastTime == null || ((System.currentTimeMillis() - lastTime) / 1000 >= 70)) {

    connertor.close(ctx);

    }

    }

    }

    前端心跳响应:

    socket.onmessage = function(event) {

    //后端发送的是二进制帧,protobuf反序列化

    var msg = proto.Model.deserializeBinary(event.data);

    //心跳消息

    if(msg.getCmd()==2){//对应服务端的NettyConstants.CmdType.HEARTBEAT

    //发送心跳回应

    var message1 = new proto.Model();

    message1.setCmd(2);

    message1.setMsgtype(4);

    socket.send(message1.serializeBinary());

    }else {

    //...

    }

    };

    断线重连

    当网络情况不稳定,或者用户从移动网切换到无线网等场景下,长连接会断开。需要前端监听websocket的onclose事件,当连接断开后重新创建连接。

    socket.onclose = function(event) {

    //重新创建websocket连接

    };

    4.消息可靠性

    1.基于TCP,传输层已经保证了消息可靠性

    2.应用层消息可靠性,实现Ack消息机制(待补充)

    5.安全

    ssl,http协议升级为https,对应的ws协议升级为wss

    注意:当升级ssl后,前端通过"ws://"开头的url创建不了连接,需要修改成wss;并且nginx增加对应配置。

    6.负载均衡

    nginx

    nginx应用层负载,支持websocket,原因是websocket在创建长连接之前,会通过一次http握手升级。

    lvs(待研究)

    抗负载能力强,工作在网络四层,仅作流量分发,几乎可以对所有应用做负载均衡。

    7.netty服务

    服务端由netty搭建:

    1.基于nio,支持高并发,可维持大数量的长连接

    2.本身支持websocket协议,自带websocket的处理器,方便开发

    im聊天实现方式:

    8f4d754647a2

    image.png

    用户/客服和服务端之间的连接是netty中的channel,所有聊天的消息写入到channel中,当A给B发送消息后,ChannelInboundHandler从A和netty服务端连接的channel中读取到数据,然后解析消息获取消息的接收者B,再将消息写入B和服务端连接的channel。反之亦然。

    8.数据库

    1.mysql消息持久化

    2.消息较多,需要考虑分表分库

    9.缓存

    用户进入聊天页面时,是可以和机器人或者人工客服聊天的;默认是机器人聊天,当用户输入“人工”时,切换成人工客服聊天,此时需要在缓存中保存用户的会话状态,来区别用户发送的消息是触达机器人还是客服。

    考虑到集群部署,可选择在redis中维护会话状态以及客服的在线状态。

    |

    业务实现

    1.app端开始聊天

    1.用户进入聊天页面,new WebSocket,创建和服务端端的长连接。

    2.前端监听连接到成功事件,并给连接的服务端发送一条消息,该消息包括用户的信息,消息类型是“绑定”

    3.服务端判断此用户的会话状态,如果处于人工客服中,将缓存中的会话id取出,通过会话id查询出消息历史,发送给前端;如果不处于人工客服,给用户发送欢迎语。

    4.用户发送消息,消息类型是“普通”,服务端接收到消息,判断用户会话状态。

    a.用户不处于人工会话状态,且用户没有发送“人工”二字,此时调用机器人服务,将消息发送给机器人,得到的结果写到用户的channel中,结果发送给前端。

    b.用户不处于人工会话状态,但用户发送“人工”二字,通过redis中客服在线状态,获取空闲客服,和用户之间创建绑定关系,关系存入redis中。(需要考虑队列排队,等待空闲客服的场景)

    c.用户处于人工会话状态,直接将消息发送给绑定的客服。

    8f4d754647a2

    image.png

    关闭会话状态

    分为两种,客服主动关闭,会话超时关闭。

    客服主动关闭

    1.客服主动发起关闭会话请求,该请求协议为http/https。前端设置聊天框无法输入,消息无法点击发送。

    2.在redis中删除用户和客服绑定关系,删除当前客服的聊天用户列表中的对应用户。

    3.给用户推送一条满意度调查消息。(如果不希望重复发送满意度调查,可以维护满意度调查的发送次数)

    4.用户可选择填写满意度,满意度调查为http/https接口,后续场景有如下情况:

    a.离开聊天页面,会断开连接,心跳响应超时服务端会将用户从redis的登录人员集中删除。

    b.留在当前页面,继续聊天则会路由到机器人回复。

    8f4d754647a2

    image.png

    超时关闭

    设置超时关闭时长,系统自动删除用户会话状态

    定时遍历客服聊天用户集合,获取到用户最近一次聊天的时间戳(可以将用户最近一次聊天的时间戳放入redis的有序集合中,设置member为userId,时间戳为score值),当超过超时时间后,关闭会话状态,流程和客服主动关闭相同。

    消息推送

    对接push厂商通道,在非聊天页面进行消息推送。

    总结

    针对于项目实际业务场景,还是有很多地方需要完善。比如所有客服繁忙时,用户需要在队列中排队等待;比如如何实现应用层的ack机制保证消息不丢失。

    该文需要补充的地方还有很多,才能完成真实的业务场景。欢迎大家查缺补漏。

    展开全文
  • 微信小程序即时通讯源码示例,基于websocket通讯,代码提供测试账号和web测试工具。
  • 访客窗口多达5种配色方案,同时支持移动和PC, 中英文双语自由切换, 对外贸易业务更方便; 智能机器人自动回复功能, 即可设置客服系统为无人值守状态; 客服人员使用电脑或移动设备浏览器均可登录提供服务; 客服人员...
  • WebSocket协议

    2020-12-28 22:18:04
    WebSocket协议简介WebSocket是html5规范新引入的功能,用于解决浏览器与后台服务器双向通讯的问题,使用WebSocket技术,后台可以随时向前端推送消息,以保证前后台状态统一,在传统的无状态HTTP协议中,这是“无法...
  • package controllers import ( "github.com/astaxie/beego/orm" "github.com/gorilla/websocket" "imservice/models" "log" ...) ... 客服 */ type Service struct { WebsiteName string `json:"websi...
  • springboot+websocket实现服务端、客户端

    万次阅读 多人点赞 2019-01-12 12:54:43
    一、引言 小编最近一直在使用springboot框架...websocket主要功能就是实现网络通讯,比如说最经典的客服聊天窗口、您有新的消息通知,或者是项目与项目之间的通讯,都可以采用websocket来实现。 二、websocket介...
  • 1、新增客服web多达18种访客信息提示音可选 2、新增客服web访客多窗口和单窗口模式切换 3、新增客服web访客列表中关闭离线访客功能 4、新增访客上传文件开放或禁用设置功能 5、新增访客web发送截图提示按钮 ...
  • Android webSocket应用Demo,推送、即时通信、心跳重连。可实现简单聊天、服务端推送消息到客服端等功能。(java-webSocket
  • 在学习安卓的过程中,有一个是关于做石头剪刀布游戏的左右,我设法想实现人对人对战,正好想尝试一下websocket编程,本篇先写的是websocket客户端。 实现现象: 首先添加库配置文件。 implementation '...
  • 在线客服系统源代码,客户端使用 websocket实现,服务器端使用C#实现(服务器端源码暂无开放,如果需要请联系:boohiya@ubadis.com ),可扩展性强根据自己的需求灵活扩展。 演示地址 : ...
  • socket通信即时服务端源码 客服端服务端
  • node+websocket 简易多人聊天室

    千次阅读 2020-08-06 18:02:11
    websocket和node一起搭建一个简单聊天室,全部源码已经上传到github 具体实现 环境搭建 创建一个文件夹,建立相对应的css目录,js目录(其他可以先不用管) 再根目录下执行命令,安装socket.io npm install socket...
  • go-fly是一个基于Golang语言和MySQL实现的WEB在线客服系统。 主要技术栈 gin + jwt-go + websocket + go.uuid + gorm + cobra ...客服端可以编辑自动回复内容了 命令行参数中新增了关闭服务的功能如:./go-fly stop
  • 2. 访客窗口多达5种配色方案,同时支持移动和PC, 中英文双语自由切换, 对外贸易业务更方便;3. 智能机器人自动回复功能, 即可设置客服系统为无人值守状态;4. 客服人员使用电脑或移动设备浏览器均...
  • winform客户端SuperWebSocket通信Demo

    热门讨论 2015-01-06 21:57:00
    c#编写的winform多客户端SuperWebSocket框架的双向通信的Demo源码,实现连接、收到消息、关闭事件。适合初学者参考。
  • 在线客服系统源代码,客户端使用 websocket实现,服务器端使用C#实现(服务器端源码暂无开放,如果需要请联系:boohiya@ubadis.com),可扩展性强根据自己的需求灵活扩展。 演示地址 : 1.客户入口 ...
  • CentOS7源码安装node.js和webSocket

    千次阅读 2017-11-17 11:04:00
    CentOS7源码安装node.js和webSocket 学习参考:http://ourjs.com/detail/529ca5950cb6498814000005   如果iptables是开启的,要先加入80端口和socket监听端口(可以自己设置) 官网:https://nodejs.org 下载...
  • 借鉴文章所用websocket 第三方是:nv-websocket-client:2.2,而这里所用的是:Java-WebSocket:1.3.0核心代码如下:public abstract class MyWebSocketService extends Service implements IWebSocket {private static ...
  • 我们知道websocket协议与http协议有些不同,websocket有自带的Session,但是跟http的Session是不一样的,而我们做有些web项目的时候,又需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权...
  • WeLive5是一个企业级的在线客服系统, 程序小巧使用简单, 主要特点: 1. PHP开发, 基于WebSocket通讯技术, 具有请求与推送全双工功效, 极速高效; 2. 访客窗口多达5种配色方案,同时支持移动和PC, 中英文双语自由...
  • 使用C语言实现一个websocket

    千次阅读 热门讨论 2019-08-07 15:38:12
    最近自己心血来潮想学习一下C语言,感觉C语言是...奈何网上搜索了很多资料关于C语言来实现websocket的寥寥无几,更多的是泛泛而谈亦或是提供的源码运行环境复杂更或者是根本运行不起来。一怒之下本人放弃了~~~~ ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 546
精华内容 218
关键字:

websocket客服端源码