这两天花时间看了一下websocket,自己也跟着动手做了一个毕竟简单的实现 记录一下:

websocket分为 客户端 和 服务端 两部分  

客户端代码 

var ws = new WebSocket('ws://127.0.0.1:7777'); // 访问的是本地的服务器

		function sendMsg() {
			let _file = document.querySelector('input[type=file]').files[0];
			let text = document.getElementById('txtinput').value;
			
			if (ws.readyState == 1) {
				// 处理图片为base64格式
				if (_file) {
					let reader = new FileReader();
					reader.readAsDataURL(_file); // 转化为二进制流
					reader.onload = () => {
						ws.send(reader.result);
					}
				} else {
					ws.send(text);
				}
			}
		}

		// 连接服务器
		ws.onopen = function (e) {
			console.log('connect');
		}

		// 接收服务器的消息
		ws.onmessage = function(e) {
			if (e.data.indexOf('base64,') > 0) {
				let img = new Image();
				img.src = e.data;
				document.getElementById('area').appendChild(img);
			} else {
				let text = document.createElement('div');
				text.innerHTML = e.data;

				document.getElementById('area').appendChild(text);
			}
		}

		ws.onclose = e => {
			console.log('lost connect');
			ws = 0;
		}
		
		ws.onerror = e => {
			console.log('error');
			console.log(e);
		}

  客户端第一次连接服务端的时候会有一个握手的步骤 客服端发送WebSocket-Key给服务端,服务端拿到之后 又加密返给客户端 双方都通过后 websocket连接建立成功

winform 通过socket传输图片 javascript_客户端

 

 

服务端代码(PHP实现)

class SocketController
{
    public $master;
    public $sockets;
    private $len = 1024;

    public function __construct($ip, $port)
    {
        // 创建一个IPV4的套接字
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1); // 设置接收所有数据
        // 绑定IP 端口
        socket_bind($this->master, $ip, $port);
        //开启监听
        socket_listen($this->master);
//        socket_set_nonblock($this->master);
        $this->sockets['master'] = $this->master;
        echo 'socket create' . PHP_EOL;
    }



    // 接收消息
    public function run()
    {
        while (true)
        {
            $changes = $this->sockets;
            $write = $exception = [];
        // 没处理异常的情况  关于第四个参数 null 表示等待客户端发消息或有新连接再往下走 会阻塞在这里 非null的情况没做尝试
            socket_select($changes, $write, $exception, null);

            foreach ($changes as $k => $master)
            {
                // 说明有新的客户端进来
                if ($master == $this->master)
                {
                    $client = socket_accept($master);

                    //获取客户端发送内容
                    $buffer = trim(socket_read($client,1024));
                    $this->hand($client, $buffer); // 握手

                    $key = uniqid();
                    echo 'new connect ' . $key . PHP_EOL;
                    $this->sockets[$key] = $client;
                }
                else // 说明客户端发了信息过来
                {
                    $content = '';
                    $contentLength = 0;
                    echo 'begin get content ' . $k . PHP_EOL;
                    // 用do while 是因为socket_recv 会阻塞程序 消息接收完了也卡在这不往下走
                    do
                    {
                        $len = socket_recv($master, $subContent, $this->len, 0);
                        $content .= $subContent;
                        $contentLength += $len;

                    }while($len == $this->len);
 
                    if ($contentLength > 0)
                    {
                        // 把消息 给到其他连接
                        $this->sendMsg($content);
                    } else {
                        $this->unlinkSocket($k); // 关闭socket连接
                    }
                }
            }
        }
    }


    // 发送消息
    public function sendMsg($content)
    {
        $fixedContent = self::encode($content);

        foreach ($this->sockets as $k => $socket)
        {
            if ($k == 'master') continue;
            socket_write($socket, $fixedContent, strlen($fixedContent));
        }
        echo 'send msg' . PHP_EOL;

    }

    // 关闭套接字
    public function unlinkSocket($connect)
    {
        echo 'lost connect' . $connect . PHP_EOL;
        @socket_shutdown($this->sockets[$connect]);
        socket_close($this->sockets[$connect]);
        unset($this->sockets[$connect]);
    }

    // 握手
    public function hand($client, $buffer)
    {
        $buf  = substr($buffer, strpos($buffer,'Sec-WebSocket-Key:') + 18);
        $key  = trim(substr($buf, 0, strpos($buf, "\r\n")));
        
        $newKey = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));

        //按照协议组合信息进行返回
        $newMessage = "HTTP/1.1 101 Switching Protocols\r\n";
        $newMessage .= "Upgrade: websocket\r\n";
        $newMessage .= "Sec-WebSocket-Version: 13\r\n";
        $newMessage .= "Connection: Upgrade\r\n";
        $newMessage .= "Sec-WebSocket-Accept: " . $newKey . "\r\n\r\n";

        socket_write($client, $newMessage, strlen($newMessage));
    }

    /**
     *  很好的解释了$masks $data为什么这样取值
     * @param $buffer
     * @return string
     */
    public function encode($buffer)
    {
        // 解析成文案
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;
        if ($len === 126)
        {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127)
        {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        }
        else
        {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++)
        {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        // end

        // 这段的意思是 把数据分割成125个字节的长度大小 大于125个字节的 就分段返回
//        $a = str_split($decoded, 125);
//        if (count($a) == 1)
//        {
//            return "\x81" . chr(strlen($a[0])) . $a[0];
//        }
//        $ns = "";
//        foreach ($a as $o)
//        {
//            $ns .= "\x81" . chr(strlen($o)) . $o;
//        }
//        return $ns;
        // end

        // 这个就不分段了 
        $frame = array();
        $frame[0] = '81';
        $len = strlen($decoded);
        if ($len < 126)
        {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        }
        elseif ($len < 65025)
        {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        }
        else
        {
            $s=dechex($len);
            $frame[1]= '7f' . str_repeat('0',16 - strlen($s)) . $s;
        }
        $msg = '';
        for ($i = 0; $i < $len; $i++)
        {
            $msg .= dechex(ord($decoded{$i}));
        }
        $frame[2] = $msg;
        $data = implode('', $frame);
        return pack("H*", $data);

    }
}

$sc = new SocketController('127.0.0.1', 7777);

$sc->run();

  刚写完服务端代码的时候并没有发送过来的数据做处理,以为是发送过来一个 ‘hello world’ 服务端拿到的就应该是一个明文的字符串 一眼就能看到懂那种,后来打印了之后才发现事情并没有这么简单,看了    这个老哥的文章才知道数据在这个过程中是怎么传递的 牵扯到位运算及字符操作函数 对这方面也了解不深就不赘述了

 

上面已经基本上算是实现了聊天室的功能