1. Vorwort
Es gibt einen einfachen Chatroom im Firmenspiel. Als ich davon erfuhr, wurde mir klar, dass er von Node Websocket erstellt wurde. Ich dachte darüber nach, einen einfachen Chatroom zu erstellen. Also habe ich verschiedene Informationen gesammelt, Dokumente gelesen, nach Beispielen gesucht und selbst einen einfachen Chatroom geschrieben.
HTTP-Verbindungen werden in kurze und lange Verbindungen unterteilt. Kurze Verbindungen können im Allgemeinen mit Ajax implementiert werden, lange Verbindungen sind Websockets. Kurze Verbindungen sind relativ einfach zu implementieren, verbrauchen jedoch zu viele Ressourcen. Websocket ist effizient, weist jedoch einige Kompatibilitätsprobleme auf. Websocket ist eine Ressource von HTML5
Wenn Sie mehr über das Prinzip der Websocket-Langverbindung erfahren möchten, besuchen Sie bitte https://www.zhihu.com/question/20215561.
In diesem Artikel werden hauptsächlich die Implementierungsschritte des einfachen Websocket-Chatrooms vorgestellt. Detaillierte Wissenspunkte zu bestimmten Teilen werden bereitgestellt, oder es wird den Lesern erleichtert, Informationen selbst zu sammeln.
2. Frontend
Die Implementierung von Websocket im Frontend ist sehr einfach und unkompliziert
//Websocket verbinden
var ws = new WebSocket("ws://127.0.0.1:8000");
//Bei erfolgreicher Verbindung mit websoc
ws.onopen = function(){}
//Die Nachrichtenausgabe vom Server wurde erfolgreich abgerufen
ws.onmessage = function(e){}
//Wenn ein Verbindungsfehler auftritt
ws.onerror = function(){}
//Daten an den Server senden
ws.send();
3. Backstage
Die Schwierigkeit von Websocket liegt hauptsächlich im Hintergrund
3.1Websocket-Verbindungsprozess
Websocket-Kommunikationsdiagramm Dies ist ein einfaches Kommunikationsdiagramm zwischen dem Client und dem Server. Die Hauptaufgabe von PHP besteht darin, den Verschlüsselungsschlüssel zu akzeptieren und zurückzugeben, um die Socket-Erstellung und den Handshake-Vorgang abzuschließen
Das Bild unten ist ein detailliertes Flussdiagramm des Serververarbeitungs-Websockets
3.2 Code-Praxis
Der vom Server durchgeführte Prozess sieht ungefähr wie folgt aus:
①. Unterbrechen Sie einen Socket-Prozess, der auf eine Verbindung wartet
② Durchlaufen Sie das Socket-Array, nachdem eine Socket-Verbindung besteht
③. Wenn kein Handshake erfolgt, führen Sie einen Handshake-Vorgang durch. Wenn ein Handshake vorliegt, werden die Daten analysiert und zur Ausgabe in den Puffer geschrieben
Das Folgende ist ein Beispielcode (ich habe eine Klasse geschrieben, daher ist der Code nach Funktionen segmentiert. Am Ende des Textes sind die Github-Adresse und einige Fallstricke angegeben, auf die ich gestoßen bin
).1. Erstellen Sie zunächst den 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>
<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. Der angehaltene Prozess durchläuft das Socket-Array und die Hauptoperationen werden hier abgeschlossen
<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. Der Prozess besteht darin, den Websocket-Inhalt zu empfangen, den Schlüssel von Sec-WebSocket-Key zu erhalten und ihn über den Verschlüsselungsalgorithmus in den Puffer zu schreiben (für die automatische Überprüfung ist keine Verarbeitung durch uns erforderlich). )
<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>
<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. Schreiben Sie den Socket in den Puffer
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>
如有不正欢迎指出