socket與WebSocket協(xié)議

本文整理了對Socket與WebSocket協(xié)議的理解,基于WebSocket聊天室的實現(xiàn)及實現(xiàn)原理,Workerman與swoole的區(qū)別。

什么是socket

Socket套接字是應用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。

實現(xiàn)websocket服務端的流程原理

可以通俗理解為:Socket是對TCP/IP協(xié)議的封裝,Socket本身并不是協(xié)議,而是一個調(diào)用接口(API),通過Socket,我們才能使用TCP/IP協(xié)議。創(chuàng)建 Socket 連接的時候,可以指定傳輸層協(xié)議,可以是 TCP 或者 UDP,當用 TCP 連接,該Socket就是個TCP連接,反之。

什么是WebSocket協(xié)議

HTTP協(xié)議有一個缺陷:通信只能由客戶端發(fā)起,做不到服務器主動向客戶端推送信息。

WebSocket協(xié)議它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發(fā)送信息,是真正的雙向平等對話,屬于服務器推送技術的一種

兩者都是基于TCP/IP協(xié)議之上。所以HTTP連接也是建立在Socket連接之上。在實際的網(wǎng)絡棧中,Socket連接的確是HTTP連接的一部分。但是從HTTP協(xié)議看,它的連接一般是指它本身的那部分。

在WebSocket中,只需要服務器和瀏覽器通過HTTP協(xié)議進行一個握手的動作,然后單獨建立一條TCP的通信通道進行數(shù)據(jù)的傳送。


實現(xiàn)應用的樣例如下:

1、swoole實現(xiàn)聊天室:

$this->server = new swoole_websocket_server(self::HOST, self::PART);//swoole起一個服務...

2、PHP實現(xiàn)聊天室:

webq前端頁面客戶端需要一個客戶端連接與服務端交互,通常加載js提供的接口連接服務端。如下建立websocket連接和交互,小馬拙見:除了握手的處理,其基本上也就是雙向讀寫接收的處理(接收消息和發(fā)送消息對應的讀寫函數(shù))。

var ws = new WebSocket("ws://localhost:9998/echo"); //參數(shù)是個url:協(xié)議://域名(或者ip):端口/路徑,webscoket支持ws和wss對應著http和https

ws.onmessage = function (evt) {?var received_msg = evt.data;?alert("數(shù)據(jù)已接收...");?}; //這個回調(diào)函數(shù)用于接收服務端消息

ws.send("發(fā)送數(shù)據(jù)");//這個函數(shù)用于向服務端發(fā)送數(shù)據(jù)??

服務端要想建立websocket連接,服務端需要回應客戶端的握手消息。與客戶端進行數(shù)據(jù)傳輸?shù)臅r候要遵從websocket協(xié)議消息格式。建立一個server.php文件。代碼的整體思路參考上面的原理圖。

class WebSocketServer{ private $sockets;//所有socket連接池包括服務端socket private $users;//所有連接用戶 private $server;//服務端 socket public function __construct($ip,$port){ $this->server=socket_create(AF_INET,SOCK_STREAM,0); $this->sockets=array($this->server); $this->users=array(); socket_bind($this->server,$ip,$port); socket_listen($this->server,3); echo "[*]Listening:".$ip.":".$port."\n"; } public function run(){ $write=NULL; $except=NULL; while (true){ $active_sockets=$this->sockets; socket_select($active_sockets,$write,$except,NULL); //這個函數(shù)很重要 //前三個參數(shù)時傳入的是數(shù)組的引用,會依次從傳入的數(shù)組中選擇出可讀的,可寫的,異常的socket,我們只需要選擇出可讀的socket //最后一個參數(shù)tv_sec很重要 //第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置于阻塞狀態(tài),一定等到監(jiān)視文件描述符集合(socket數(shù)組)中某個文件描 //述符發(fā)生變化為止; //第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數(shù),不管文件描述符是否有變化,都立刻返回繼續(xù)執(zhí)行,文件無 //變化返回0,有變化返回一個正值; //第三,timeout的值大于0,這就是等待的超時時間,即 select在timeout時間內(nèi)阻塞,超時時間之內(nèi)有事件到來就返回了, //否則在超時后不管怎樣一定返回,返回值同上述。 foreach ($active_sockets as $socket){ if ($socket==$this->server){ //服務端 socket可讀說明有新用戶連接 $user=socket_accept($this->server); $key=uniqid(); $this->sockets[]=$user; $this->users[$key]=array( 'socket'=>$user, 'handshake'=>false //是否完成websocket握手 ); }else{ //用戶socket可讀 $buffer=''; $bytes=socket_recv($socket,$buffer,1024,0); $key=$this->find_user_by_socket($socket); //通過socket在users數(shù)組中找出user if ($bytes==0){ //沒有數(shù)據(jù) 關閉連接 $this->disconnect($socket); }else{ //沒有握手就先握手 if (!$this->users[$key]['handshake']){ $this->handshake($key,$buffer); }else{ //握手后 //解析消息 websocket協(xié)議有自己的消息格式 //解碼 編碼過程固定的 $msg=$this->msg_decode($buffer); echo $msg; //編碼后發(fā)送回去 $res_msg=$this->msg_encode($msg); socket_write($socket,$res_msg,strlen($res_msg)); } } } } } } //解除連接 private function disconnect($socket){ $key=$this->find_user_by_socket($socket); unset($this->users[$key]); foreach ($this->sockets as $k=>$v){ if ($v==$socket) unset($this->sockets[$k]); } socket_shutdown($socket); socket_close($socket); } //通過socket在users數(shù)組中找出user private function find_user_by_socket($socket){ foreach ($this->users as $key=>$user){ if ($user['socket']==$socket){ return $key; } } return -1; } private function handshake($k,$buffer){ //截取Sec-WebSocket-Key的值并加密 $buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18); $key = trim(substr($buf,0,strpos($buf,"\r\n"))); $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); //按照協(xié)議組合信息進行返回 $new_message = "HTTP/1.1 101 Switching Protocols\r\n"; $new_message .= "Upgrade: websocket\r\n"; $new_message .= "Sec-WebSocket-Version: 13\r\n"; $new_message .= "Connection: Upgrade\r\n"; $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; socket_write($this->users[$k]['socket'],$new_message,strlen($new_message)); //對已經(jīng)握手的client做標志 $this->users[$k]['handshake']=true; return true; } //編碼 把消息打包成websocket協(xié)議支持的格式 private function msg_encode( $buffer ){ $len = strlen($buffer); if ($len <= 125) { return "\x81" . chr($len) . $buffer; } else if ($len <= 65535) { return "\x81" . chr(126) . pack("n", $len) . $buffer; } else { return "\x81" . char(127) . pack("xxxxN", $len) . $buffer; } } //解碼 解析websocket數(shù)據(jù)幀 private function msg_decode( $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]; } return $decoded; }}$ws=new WebSocketServer('127.0.0.1',1234);$ws->run();

主要關注$sockets和$users數(shù)組。一個代表連接池,一個代表用戶(其中一個用戶對應著一個自己的連接)。

Workerman框架

Workerman是一款純PHP開發(fā)的開源高性能的PHP socket 服務框架。

Workerman不是重復造輪子,它不是一個MVC框架,而是一個更底層更通用的socket服務框架,你可以用它開發(fā)tcp代理、梯子代理、做游戲服務器、郵件服務器、ftp服務器、甚至開發(fā)一個php版本的redis、php版本的數(shù)據(jù)庫、php版本的nginx、php版本的php-fpm等等。Workerman可以說是PHP領域的一次創(chuàng)新,讓開發(fā)者徹底擺脫了PHP只能做WEB的束縛。

實際上Workerman類似一個PHP版本的nginx,核心也是多進程+Epoll+非阻塞IO。Workerman每個進程能維持上萬并發(fā)連接。由于本身常住內(nèi)存,不依賴Apache、nginx、php-fpm這些容器,擁有超高的性能。同時支持TCP、UDP、UNIXSOCKET,支持長連接,支持Websocket、HTTP、WSS、HTTPS等通訊協(xié)以及各種自定義協(xié)議。擁有定時器、異步socket客戶端、異步Mysql、異步Redis、異步Http、異步消息隊列等眾多高性能組件。

Workerman與swoole的區(qū)別

彩蛋時間:

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容