欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用websocket构建实时通知程序或者web聊天程序

程序员文章站 2022-05-20 09:37:25
...

先搞清楚几个概念 


1、WebSocket是什么?

WebScoket是一种让客户端和服务器之间能进行双向实时通信的技术。它是HTML最新标准HTML5的一个协议规范,本质上是个基于TCP的协议,它通过HTTP/HTTPS协议发送一条特殊的请求进行握手后创建了一个TCP连接,此后浏览器/客户端和服务器之间便可以通过此连接来进行双向实时通信。

使用websocket构建实时通知程序或者web聊天程序

2、为什么要用WebSocket?

1)一直以来,HTTP协议是无状态、单向通信的,即客户端请求一次,服务器回复一次。如果想让服务器消息及时下发到客户端,需要采用类似于轮询的机制,即客户端定时频繁的向服务器发出请求,这样效率很低,而且HTTP数据包头本身的字节量较大,浪费了大量带宽和服务器资源;

2)为提高效率,出现了AJAX/Comet技术,它实现了双向通信且节省了一定带宽,但仍然需要发出请求,本质上仍然是轮询;

3)新一代HTML标准HTML5推出了WebSocket技术,它使客户端和服务器之间能通过HTTP协议建立TCP连接,之后便可以随时随地进行双向通信,且交换的数据包头信息量很小;

使用websocket构建实时通知程序或者web聊天程序

3、如何使用WebSocket?

在支持WebSocket的浏览器中,创建Socket之后,通过onopen、onmessage、onclose、onerror四个事件的实现来处理Socket的响应;

4、WebSocket与HTTP、TCP的关系

WebSocket和HTTP都属于应用层协议,且都是基于TCP的,它们的send函数最终也是通过TCP系统接口来做数据传输。那么WebSocket和HTTP的关系呢?WebSocket在建立握手连接时,数据是通过HTTP协议传输的,但是在连接建立后,真正的数据传输阶段则不需要HTTP协议的参与。中间重叠的部分是表示升级到websocket协议,它们之间的关系如下图:

使用websocket构建实时通知程序或者web聊天程序

5、什么情况下使用WebSocket?

如果游戏需要同时支持手机端、Web端,那毫无疑问应该使用WebSocket,现在各个平台都提供了相应的WebSocket实现。如果游戏不需要支持Web端,且对实时性要求比较高,如多人射击、MMORPG之类,那么使用TCP/UDP结合的原生Socket会比较好。

6、SocketIO

WebSocket是HTML5最新提出的规范,虽然主流浏览器都已经支持,但仍然可能有不兼容的情况,为了兼容所有浏览器,给程序员提供一致的编程体验,SocketIO将WebSocket、AJAX和其它的通信方式全部封装成了统一的通信接口,也就是说,我们在使用SocketIO时,不用担心兼容问题,底层会自动选用最佳的通信方式。因此说,WebSocket是SocketIO的一个子集。
使用websocket构建实时通知程序或者web聊天程序

首先用composer程序中加入workman依赖

如果你对composer不熟悉,可以移步这里, https://blog.csdn.net/robinhunan/article/details/106377501 文章详细介绍了composer的配置,使用教程。

composer require  walkor/workerman

 

Server端程序,编辑server端程序ws.php 

<?php
/**
 * websocket server程序,监听端口19988
 */
use Workerman\Worker;
require_once __DIR__.'/vendor/autoload.php';

//初始化一个worker容器,监听19988端口
$worker = new Worker('websocket://0.0.0.0:19988');

/**
 * 这里进程数必须设置为1,否则会报端口占用错误
 * (php7 可以设置进程数大于1,前提是$inner_worker->reusePort=true)
 */
$worker->count = 1;

//$worker进程启动后创建一个text Worker,以便打开一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
    //开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
    $inner_text_worker = new Worker('text://0.0.0.0:19989');
    $inner_text_worker->onMessage=function($connection,$buffer)
    {
        //$data数组格式,里面有uid,表示向那个uid的页面推送数据
        $data = json_decode($buffer,true);
        $uid = $data['uid']; 

        //通过workerman,向uid的页面推送数据
        $ret = sendMessageByUid($uid,$buffer);

        //返回推送结果
        $connection->send($ret?'ok':'fail');
    };
    $inner_text_worker->listen();
};

//新增一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();

//当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection,$data)
{
    global $worker;
    $date = date('Y-m-d H:i:s',time());
    file_put_contents('./workerman.log',$date.' :  '.$data.PHP_EOL,FILE_APPEND );
    $data = json_decode($data,true);

    if(!isset($connection->uid))
    {
        //正式环境需要根据$data里面的信息验证用户身份,演示用直接根据客户端第一次发来的uid直接绑定了用户
        $connection->uid = $data['uid']; 

        //保存uid到connection映射,这样可以方便的通过uid查找connection,实现针对特定的uid推送数据
        $worker->uidConnections[$connection->uid] = $connection;
        return;
    }
};

//当有客户端连接断开时,删除映射
$worker->onClose = function($connection)
{
    global $worker;
    if(isset($connection->uid))
    {
        //连接断开时,删除映射
        unset($worker->uidConnections[$connection->uid]);
    }
};

//向所有验证的用户推送数据
function broadcast($message)
{
    global $worker;
    foreach($worker->uidConnections as $connection)
    {
        $connection->send($message);
    }
}

//针对uid推送数据
function sendMessageByUid($uid,$message)
{
    global $worker;
    if(isset($worker->uidConnections[$uid]))
    {
        $connection = $worker->uidConnections[$uid];
        $connection->send($message);
        return true;
    }
    return false;
}

//运行所有worker
Worker::runAll();

    启动websocket的server程序 

使用websocket构建实时通知程序或者web聊天程序

     浏览器端程序或者客户端程序 client.html ,通过浏览器,建议是chrome或者firefox浏览器访问 http://localhost/client.html, 并打开控制台,我这里是简化了流程,正常的话,还是需要登录的,可以在下面的send消息里面,加上用户信息,server端判断是否正确,再绑定,为了简化理解,我就去掉了websocket登录部分。

<script>
ws = new WebSocket("ws://127.0.0.1:19988/");
ws.onopen = function() {
    console.log("连接成功");
    ws.send('{"uid":"yubing"}'); //这里放登录信息
    console.log("给服务器发送uid信息:yubing");
};
ws.onmessage = function(e) {
    console.log("收到服务端的消息:" + e.data);
};
</script>

打开控制台后,可以发现浏览器已经连接上websoket服务器,并且给服务器发送了一个消息。

使用websocket构建实时通知程序或者web聊天程序 

第三方客户端,直接调用tcp发送消息,下面的例子client.php 演示了通过php直接发送推送消息,执行php client.php ,浏览器对应的上个用户我这里设置的是yubing,就能收到消息了。

<?php
//建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:19989',$errno,$errmsg,1);

//推送的数据,包含uid字段,表示是给这个uid推送
$data = array('uid'=>'yubing','status' => 'success','msg' => date('Y-m-d H:i:s'));

//发送数据,注意19989端口是text协议的端口,text协议需要在数据末尾加上换行符
fwrite($client,json_encode($data)."\n");

//读取推送结果
echo fread($client,8192);

 切换到浏览器,会发现浏览器已经收到了,php 客户端发送过来的消息。

使用websocket构建实时通知程序或者web聊天程序

 

原理

一般我们开发的WebSocket服务程序使用ws协议,明文的。但是怎样让它安全的通过互联网传输呢?这时候可以通过nginx在客户端和服务端直接做一个转发了, 客户端通过wss访问,然后nginx和服务端通过ws协议通信。如下图所示:

使用websocket构建实时通知程序或者web聊天程序

 nginx代理websocket服务配置文件

upstream websocket1{
    ip_hash;
    server localhost:19988 weight=50 fail_timeout=10s;
    server localhost:29988 weight=50 fail_timeout=10s;
}
 
server
{
	listen 80;
	listen 443;
	#listen [::]:80;
	server_name test.com.cn;
	index index.html index.htm index.php;
	root  /data/www/web/public;

	charset utf-8;

	ssl on;
	ssl_certificate /usr/local/nginx/conf/cert/test.com.cn.crt;
	ssl_certificate_key /usr/local/nginx/conf/cert/test.com.cn.key;
	ssl_session_timeout 5m;
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
	ssl_prefer_server_ciphers on;



	location /wss
	{
		proxy_pass http://websocket1;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header X-real-ip $remote_addr;
		proxy_set_header HOST $host;
		proxy_set_header X-Forwarded-For $remote_addr;
	}
}

这时候客户端通过wss://test.com.cn/wss,就可以加密连接了。

<script>
ws = new WebSocket("wss://test.com.cn/wss");
ws.onopen = function() {
}
</script>