1. Foreword
There is a simple chat room in the company’s game. After learning about it, I realized that it was made by node+websocket. I thought about using PHP to make a simple chat room. So I collected various information, read documents, looked for examples, and wrote a simple chat room myself.
http connections are divided into short connections and long connections. Short connections can generally be implemented using ajax, and long connections are websockets. Short connections are relatively simple to implement, but consume too many resources. Websocket is efficient but has some issues with compatibility. websocket is a resource of html5
If you want to know more about the principle of websocket long connection, please see https://www.zhihu.com/question/20215561.
This article mainly introduces the implementation steps of a simple websocket chat room. In-depth knowledge points of specific parts will be given links or trouble readers to collect information by themselves.
2. Front-end
Implementing websocket on the front end is very simple and straightforward
//Connect websocket
var ws = new WebSocket("ws://127.0.0.1:8000");
//When successfully connected to websoc
ws.onopen = function(){}
//Successfully obtained the message output by the server
ws.onmessage = function(e){}
//When there is a connection error
ws.onerror = function(){}
//Send data to the server
ws.send();
3.Backstage
The difficulty of websocket is mainly in the background
3.1websocket connection process
Websocket communication diagram This is a simple communication diagram between client and server. What PHP mainly does is to accept the encryption key and return it to complete the socket creation and handshake operation
The picture below is a detailed flow chart of websocket processing on the server side
3.2 Code Practice
The process done by the server is roughly as follows:
①. Suspend a socket process waiting for connection
②. After there is a socket connection, traverse the socket array
③. If there is no handshake, perform the handshake operation. If there is a handshake, the data will be parsed and written into the buffer for output
The following is a sample code (I wrote a class, so the code is segmented according to functions). The bottom of the text gives the github address and some pitfalls I encountered
1. First create the socket
<span style="color: #008000;">//</span><span style="color: #008000;">建立套接字</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> createSocket(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">创建一个套接字</span> <span style="color: #800080;">$socket</span>= socket_create(AF_INET, SOCK_STREAM,<span style="color: #000000;"> SOL_TCP); </span><span style="color: #008000;">//</span><span style="color: #008000;">设置套接字选项</span> socket_set_option(<span style="color: #800080;">$socket</span>, SOL_SOCKET, SO_REUSEADDR, 1<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">绑定IP地址和端口</span> socket_bind(<span style="color: #800080;">$socket</span>,<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">监听套接字</span> socket_listen(<span style="color: #800080;">$socket</span><span style="color: #000000;">); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$socket</span><span style="color: #000000;">; }</span>
2. Put the socket into the array
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> __construct(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">建立套接字</span> <span style="color: #800080;">$this</span>->soc=<span style="color: #800080;">$this</span>->createSocket(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->socs=<span style="color: #0000ff;">array</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">soc); }</span>
3. The suspended process traverses the socket array, and the main operations are completed here
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> run(){ </span><span style="color: #008000;">//</span><span style="color: #008000;">挂起进程</span> <span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){ </span><span style="color: #800080;">$arr</span>=<span style="color: #800080;">$this</span>-><span style="color: #000000;">socs; </span><span style="color: #800080;">$write</span>=<span style="color: #800080;">$except</span>=<span style="color: #0000ff;">NULL</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">接收套接字数字 监听他们的状态</span> socket_select(<span style="color: #800080;">$arr</span>,<span style="color: #800080;">$write</span>,<span style="color: #800080;">$except</span>, <span style="color: #0000ff;">NULL</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历套接字数组</span> <span style="color: #0000ff;">foreach</span>(<span style="color: #800080;">$arr</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$k</span>=><span style="color: #800080;">$v</span><span style="color: #000000;">){ </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是新建立的套接字返回一个有效的 套接字资源</span> <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->soc == <span style="color: #800080;">$v</span><span style="color: #000000;">){ </span><span style="color: #800080;">$client</span>=socket_accept(<span style="color: #800080;">$this</span>-><span style="color: #000000;">soc); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$client</span> <0<span style="color: #000000;">){ </span><span style="color: #0000ff;">echo</span> "socket_accept() failed"<span style="color: #000000;">; }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> array_push($this->socs,$client); // unset($this[]); //将有效的套接字资源放到套接字数组 <span style="color: #800080;">$this</span>->socs[]=<span style="color: #800080;">$client</span><span style="color: #000000;">; } }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{ </span><span style="color: #008000;">//</span><span style="color: #008000;">从已连接的socket接收数据 返回的是从socket中接收的字节数</span> <span style="color: #800080;">$byte</span>=socket_recv(<span style="color: #800080;">$v</span>, <span style="color: #800080;">$buff</span>,20480, 0<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">如果接收的字节是0</span> <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$byte</span><7<span style="color: #000000;">) </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">判断有没有握手没有握手则进行握手,如果握手了 则进行处理</span> <span style="color: #0000ff;">if</span>(!<span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$client</span><span style="color: #000000;">]){ </span><span style="color: #008000;">//</span><span style="color: #008000;">进行握手操作</span> <span style="color: #800080;">$this</span>->hands(<span style="color: #800080;">$client</span>,<span style="color: #800080;">$buff</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">); }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{ </span><span style="color: #008000;">//</span><span style="color: #008000;">处理数据操作</span> <span style="color: #800080;">$mess</span>=<span style="color: #800080;">$this</span>->decodeData(<span style="color: #800080;">$buff</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">发送数据</span> <span style="color: #800080;">$this</span>->send(<span style="color: #800080;">$mess</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">); } } } } }</span>
4. Handshake. The process is to receive the websocket content, obtain the key from Sec-WebSocket-Key: and write it into the buffer through the encryption algorithm. The client will verify it (automatic verification does not require us to handle it)
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> hands(<span style="color: #800080;">$client</span>,<span style="color: #800080;">$buff</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">提取websocket传的key并进行加密 (这是固定的握手机制获取Sec-WebSocket-Key:里面的key)</span> <span style="color: #800080;">$buf</span> = <span style="color: #008080;">substr</span>(<span style="color: #800080;">$buff</span>,<span style="color: #008080;">strpos</span>(<span style="color: #800080;">$buff</span>,'Sec-WebSocket-Key:')+18<span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">去除换行空格字符</span> <span style="color: #800080;">$key</span> = <span style="color: #008080;">trim</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$buf</span>,0,<span style="color: #008080;">strpos</span>(<span style="color: #800080;">$buf</span>,"\r\n"<span style="color: #000000;">))); </span><span style="color: #008000;">//</span><span style="color: #008000;">固定的加密算法</span> <span style="color: #800080;">$new_key</span> = <span style="color: #008080;">base64_encode</span>(<span style="color: #008080;">sha1</span>(<span style="color: #800080;">$key</span>."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",<span style="color: #0000ff;">true</span><span style="color: #000000;">)); </span><span style="color: #800080;">$new_message</span> = "HTTP/1.1 101 Switching Protocols\r\n"<span style="color: #000000;">; </span><span style="color: #800080;">$new_message</span> .= "Upgrade: websocket\r\n"<span style="color: #000000;">; </span><span style="color: #800080;">$new_message</span> .= "Sec-WebSocket-Version: 13\r\n"<span style="color: #000000;">; </span><span style="color: #800080;">$new_message</span> .= "Connection: Upgrade\r\n"<span style="color: #000000;">; </span><span style="color: #800080;">$new_message</span> .= "Sec-WebSocket-Accept: " . <span style="color: #800080;">$new_key</span> . "\r\n\r\n"<span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">将套接字写入缓冲区</span> socket_write(<span style="color: #800080;">$v</span>,<span style="color: #800080;">$new_message</span>,<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$new_message</span><span style="color: #000000;">)); </span><span style="color: #008000;">//</span><span style="color: #008000;"> socket_write(socket,$upgrade.chr(0), strlen($upgrade.chr(0))); //标记此套接字握手成功</span> <span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$client</span>]=<span style="color: #0000ff;">true</span><span style="color: #000000;">; }</span>
5. Parse the client data (I did not encrypt it here, you can encrypt it yourself if necessary)
<span style="color: #008000;">//</span><span style="color: #008000;">解析数据</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> decodeData(<span style="color: #800080;">$buff</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">$buff 解析数据帧</span> <span style="color: #800080;">$mask</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">(); </span><span style="color: #800080;">$data</span> = ''<span style="color: #000000;">; </span><span style="color: #800080;">$msg</span> = <span style="color: #008080;">unpack</span>('H*',<span style="color: #800080;">$buff</span>); <span style="color: #008000;">//</span><span style="color: #008000;">用unpack函数从二进制将数据解码</span> <span style="color: #800080;">$head</span> = <span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],0,2<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (<span style="color: #008080;">hexdec</span>(<span style="color: #800080;">$head</span>{1}) === 8<span style="color: #000000;">) { </span><span style="color: #800080;">$data</span> = <span style="color: #0000ff;">false</span><span style="color: #000000;">; }</span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (<span style="color: #008080;">hexdec</span>(<span style="color: #800080;">$head</span>{1}) === 1<span style="color: #000000;">){ </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],4,2<span style="color: #000000;">)); </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],6,2<span style="color: #000000;">)); </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],8,2<span style="color: #000000;">)); </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],10,2<span style="color: #000000;">)); </span><span style="color: #008000;">//</span><span style="color: #008000;">遇到的问题 刚连接的时候就发送数据 显示 state connecting</span> <span style="color: #800080;">$s</span> = 12<span style="color: #000000;">; </span><span style="color: #800080;">$e</span> = <span style="color: #008080;">strlen</span>(<span style="color: #800080;">$msg</span>[1])-2<span style="color: #000000;">; </span><span style="color: #800080;">$n</span> = 0<span style="color: #000000;">; </span><span style="color: #0000ff;">for</span> (<span style="color: #800080;">$i</span>=<span style="color: #800080;">$s</span>; <span style="color: #800080;">$i</span><= <span style="color: #800080;">$e</span>; <span style="color: #800080;">$i</span>+= 2<span style="color: #000000;">) { </span><span style="color: #800080;">$data</span> .= <span style="color: #008080;">chr</span>(<span style="color: #800080;">$mask</span>[<span style="color: #800080;">$n</span>%4]^<span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],<span style="color: #800080;">$i</span>,2<span style="color: #000000;">))); </span><span style="color: #800080;">$n</span>++<span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">发送数据到客户端 //如果长度大于125 将数据分块</span> <span style="color: #800080;">$block</span>=<span style="color: #008080;">str_split</span>(<span style="color: #800080;">$data</span>,125<span style="color: #000000;">); </span><span style="color: #800080;">$mess</span>=<span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'mess'=><span style="color: #800080;">$block</span>[0],<span style="color: #000000;"> ); </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$mess</span><span style="color: #000000;">; }</span>
6. Write the socket to the buffer
<span style="color: #008000;">//</span><span style="color: #008000;">发送数据</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> send(<span style="color: #800080;">$mess</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历套接字数组 成功握手的 进行数据群发</span> <span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$this</span>->socs <span style="color: #0000ff;">as</span> <span style="color: #800080;">$keys</span> => <span style="color: #800080;">$values</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">用系统分配的套接字资源id作为用户昵称</span> <span style="color: #800080;">$mess</span>['name']="Tourist's socket:{<span style="color: #800080;">$v</span>}"<span style="color: #000000;">; </span><span style="color: #800080;">$str</span>=json_encode(<span style="color: #800080;">$mess</span><span style="color: #000000;">); </span><span style="color: #800080;">$writes</span> ="\x81".<span style="color: #008080;">chr</span>(<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$str</span>)).<span style="color: #800080;">$str</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;"> ob_flush(); // flush(); // sleep(3);</span> <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$values</span><span style="color: #000000;">]) socket_write(</span><span style="color: #800080;">$values</span>,<span style="color: #800080;">$writes</span>,<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$writes</span><span style="color: #000000;">)); } }</span>
7、运行方法
github地址git@github.com:rsaLive/websocket.git
①最好在控制台运行server.php
转到server.php脚本目录(可以先php -v 看下有没有配置php如果没有Linux配置下bash windows 配置下path)
php -f server.php
如果有错误会提示
②通过服务器访问html文件
8、踩过的坑,打开调试工作方便查看错误
①server.php 挂起的进程中可以打印输出的,如果出现问题可以在代码中加入打印来调试
可以在各个判断里面做标记在控制台查看代码运行在哪个区间
不过每次修改完代码之后需要重新运行脚本 php server.php
②
如果出现这种错误可能是
1、在与服务器初始套接字的时候发送数据 (在第一次与服务器验证握手的时候不能发送内容)
2、如果已经验证过了但是客户端没有发送或者发送的消息为空也会出现这样的情况
所以要检验已连接的套接字的数据
③可能浏览器不支持或者服务端没有开启socket开始之前最好验证下
<span style="color: #000000;">if (window.WebSocket){ console.log("This browser supports WebSocket!"); } else { console.log("This browser does not support WebSocket."); }</span>
如有不正欢迎指出