socket与WebSocket协议

本文整理了对Socket与WebSocket协议的理解,基于WebSocket聊天室的实现及实现原理,Workerman与swoole的区别。

什么是socket

Socket套接字是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

socket与WebSocket协议_第1张图片
socket与WebSocket协议_第2张图片
实现websocket服务端的流程原理

可以通俗理解为:Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。创建 Socket 连接的时候,可以指定传输层协议,可以是 TCP 或者 UDP,当用 TCP 连接,该Socket就是个TCP连接,反之。

什么是WebSocket协议

HTTP协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。

WebSocket协议它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种

两者都是基于TCP/IP协议之上。所以HTTP连接也是建立在Socket连接之上。在实际的网络栈中,Socket连接的确是HTTP连接的一部分。但是从HTTP协议看,它的连接一般是指它本身的那部分。

socket与WebSocket协议_第3张图片

在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

实现应用的样例如下:

1、swoole实现聊天室:

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

2、PHP实现聊天室:

webq前端页面客户端需要一个客户端连接与服务端交互,通常加载js提供的接口连接服务端。如下建立websocket连接和交互,小马拙见:除了握手的处理,其基本上也就是双向读写接收的处理(接收消息和发送消息对应的读写函数)。

var ws = new WebSocket("ws://localhost:9998/echo"); //参数是个url:协议://域名(或者ip):端口/路径,webscoket支持ws和wss对应着http和https

ws.onmessage = function (evt) { var received_msg = evt.data; alert("数据已接收..."); }; //这个回调函数用于接收服务端消息

ws.send("发送数据");//这个函数用于向服务端发送数据  

服务端要想建立websocket连接,服务端需要回应客户端的握手消息。与客户端进行数据传输的时候要遵从websocket协议消息格式。建立一个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); //这个函数很重要 //前三个参数时传入的是数组的引用,会依次从传入的数组中选择出可读的,可写的,异常的socket,我们只需要选择出可读的socket //最后一个参数tv_sec很重要 //第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合(socket数组)中某个文件描 //述符发生变化为止; //第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无 //变化返回0,有变化返回一个正值; //第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了, //否则在超时后不管怎样一定返回,返回值同上述。 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数组中找出user if ($bytes==0){ //没有数据 关闭连接 $this->disconnect($socket); }else{ //没有握手就先握手 if (!$this->users[$key]['handshake']){ $this->handshake($key,$buffer); }else{ //握手后 //解析消息 websocket协议有自己的消息格式 //解码 编码过程固定的 $msg=$this->msg_decode($buffer); echo $msg; //编码后发送回去 $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数组中找出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)); //按照协议组合信息进行返回 $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)); //对已经握手的client做标志 $this->users[$k]['handshake']=true; return true; } //编码 把消息打包成websocket协议支持的格式 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数据帧 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数组。一个代表连接池,一个代表用户(其中一个用户对应着一个自己的连接)。

socket与WebSocket协议_第4张图片
socket与WebSocket协议_第5张图片
socket与WebSocket协议_第6张图片

Workerman框架

Workerman是一款纯PHP开发的开源高性能的PHP socket 服务框架

Workerman不是重复造轮子,它不是一个MVC框架,而是一个更底层更通用的socket服务框架,你可以用它开发tcp代理、梯子代理、做游戏服务器、邮件服务器、ftp服务器、甚至开发一个php版本的redis、php版本的数据库、php版本的nginx、php版本的php-fpm等等。Workerman可以说是PHP领域的一次创新,让开发者彻底摆脱了PHP只能做WEB的束缚。

实际上Workerman类似一个PHP版本的nginx,核心也是多进程+Epoll+非阻塞IO。Workerman每个进程能维持上万并发连接。由于本身常住内存,不依赖Apache、nginx、php-fpm这些容器,拥有超高的性能。同时支持TCP、UDP、UNIXSOCKET,支持长连接,支持Websocket、HTTP、WSS、HTTPS等通讯协以及各种自定义协议。拥有定时器、异步socket客户端、异步Mysql、异步Redis、异步Http、异步消息队列等众多高性能组件。

Workerman与swoole的区别:

socket与WebSocket协议_第7张图片

彩蛋时间:

socket与WebSocket协议_第8张图片

你可能感兴趣的:(socket与WebSocket协议)