如何用phpstudy搭建网站(如何用php制作网页)

目录

前言websocket简介与http的关系握手数据传输PHP实现websocket服务器文件描述符创建服务器socket服务器逻辑客户端创建客户端页面功能用户名异步处理总结

如何用phpstudy搭建网站(如何用php制作网页)

前言

最近好不容易“挤出”了一些时间,完善了很久以前做的websocket“请求-返回”服务器,用js改进了客户端功能,把过程和思路分享给大家,并且还普及了它。我们先来看看websocket相关的知识。当然,现在讨论websocket的文章也有很多。我会跳过一些理论性的东西,给出参考文章供大家选择阅读。

正文开始之前,先贴一张聊天室的效果图(请勿关注CSS渣页):

websocket

简介

WebSocket不是一项技术,而是一个全新的协议。它使用TCP的Socket(套接字),并为网络应用定义了一个新的重要能力:全双工传输以及客户端和服务器之间的双向通信。继Javaapplet、XMLHttpRequest、AdobeFlash、ActiveXObject以及各种Comet技术之后,服务器推送客户端消息是一种新的趋势。

与http的关系

从网络分层来看,websocket和http协议都是应用层协议。它们都是基于tcp传输层的。但websocket在建立连接时,借用了http的101开关协议来实现协议转换(升级)。是的,从HTTP协议切换到WebSocket通信协议。这个动作协议称为“握手”;

握手成功后,websocket使用自己协议指定的方法进行通信,与http无关。

握手

这是我自己的浏览器发送的典型握手http标头:

服务器收到握手请求后,提取请求头中的“Sec-WebSocket-Key”字段,恢复出固定字符串‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’,然后进行sha1加密,最后转换为base64编码在“Sec-WebSocket-Accept”字段中作为密钥返回给客户端。客户端匹配此密钥后,连接建立并完成握手;

数据传输

websocket有自己指定的数据传输格式,称为Frame。下图是一个数据帧的结构,单位是bit:

0123

+-+-+-+-+--------+-+------------------------+--------------------------------+

|F|R|R|R|操作码|M|有效负载长度|扩展有效负载长度|

|我|S|S|S|(4)|A|(7)|(16/64)|

|N|V|V|V||S||(如果有效负载长度==126/127)|

||1|2|3||K|||

+-+-+-+-+--------+-+-------------+----------------+

|如果有效负载len==127,则继续扩展有效负载长度|

+----------------+----------------------------------+

||屏蔽键,如果MASK设置为1|

+--------------------------------++---------------------------------+

|屏蔽键(续)|有效负载数据|

+-----------------------------------------------+

:有效负载数据(续).

+---------------------------------+

|有效负载数据续.|

+---------------------------------------------------------------+

每个字段的含义是什么?如果您有兴趣,可以阅读这篇文章TheWebSocketProtocol5.Dataframe。我感觉自己对二进制运算不太灵活,所以没有挑战自己写一个解析数据的算法。下面的数据帧解析和封装都使用在线算法。

不过,在我编写支付网关的工作中,经常会用到数据的十六进制运算。这必须认真研究和总结。嗯,先写下来吧。

PHP实现websocket服务器

如果PHP实现了websocket,主要使用PHP的socket函数库:

PHP的socket函数库与C语言的socket函数非常相似。我之前读过APUE,所以我认为它很容易理解。看完PHP手册中的socket函数,我想大家也能对PHP中的socket编程有一定的了解了。

下面是对代码中使用的函数的简单注释。

文件描述符

您可能会对突然提到“文件描述符”感到有点惊讶。

但作为服务器,需要存储并识别已连接的socket。每个套接字代表一个用户。如何关联和查询用户信息与socket的对应关系是一个问题。这里应用了一个关于文件描述符的小技巧。

我们知道Linux是“一切都是文件”,而C语言中的socket实现就是一个“文件描述符”。这个文件描述符一般是一个int值,按照文件打开的顺序递增,从0开始递增(当然系统有限)。每个socket对应一个文件,读写socket对对应的文件进行操作,因此读写功能也可以像文件系统一样应用。

Tips:在Linux中,标准输入对应文件描述符0;标准输出对应文件描述符1;标准错误对应文件描述符2;所以我们可以使用012来重定向输入和输出。

那么类似C套接字的PHP套接字自然继承了这一点,它创建的套接字也是intvalue45这样的资源类型。我们可以使用(int)或intval()函数将套接字转换为唯一的套接字ID,这样就可以用一个‘类索引数组’来存储socket资源和对应的用户信息;

结果相似:

?$connected_sockets=array((int)$socket=array('resource'=$socket,'name'=$name,'ip'=$ip,'port'=$port,))

创建服务器socket

下面是创建服务器套接字的一段代码:

?//创建TCP套接字。该函数的可选值官方文档中有详细写,这里不再赘述。$this-master=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);//设置IP和端口复用,重启服务器后即可复用该端口;socket_set_option($this-master,SOL_SOCKET,SO_REUSEADDR,1);//将IP和端口绑定到服务器socket;socket_bind($this-master,$host,$port);//listen函数将主动连接socket变成已连接socket,这样这个socket就可以被其他socket访问,从而实现服务器功能。以下参数是要处理的自定义套接字的最大数量。当并发量较高时,该值可以设置得较大,不过也受系统环境的影响。socket_listen($this-master,self:LISTEN_SOCKET_NUM);这样我们就得到了一个服务器socket。当客户端连接到此套接字时,它将把状态更改为可读。取决于服务器后续的处理逻辑。

服务器逻辑

这里重点关注socket_select($read,$write,$except,$tv_sec[,$tv_usec):

select函数使用传统的select模型。可读、可写和异常套接字将分别放入$socket、$write和$except数组中,然后返回状态发生变化的套接字数量。如果发生错误,该函数将返回false。

需要注意的是,最后两个时间参数单位不同,可以一起使用来表示socket_select的阻塞时长。当为0时,该函数立即返回,可用于轮询机制。当它为NULL时,函数将永远阻塞。这里我们将$tv_sec参数设置为null并让它阻塞,直到可操作的套接字返回。

以下是服务端的主要逻辑:

?$write=$except=NULL;$sockets=array_column($this-sockets,'资源');//获取所有套接字资源$read_num=socket_select($sockets,$write,$except,NULL);foreach($socketsas$socket){//如果服务器socket可读,则处理连接逻辑;if($socket==$this-master){socket_accept($this-master);//socket_accept()accept请求连接到“监听套接字(如我们的服务器套接字)”和客户端套接字,出错时返回false;self:connect($client);continue;}//如果可读的是其他连接的socket,则读取其数据并处理响应逻辑}else{//函数socket_recv()从socket接受长度为len字节的数据,并将其保存在$buffer中。$bytes=@socket_recv($socket,$buffer,2048,0);if($bytes9){//当客户端突然中断时,服务器会收到一条8字节长度的消息(由于其数据帧机制,我们认为是客户端异常中断消息的8字节消息),服务器处理离线逻辑,并将其封装为消息并广播$recv_msg=$this-disconnect($socket);}else{//Ifthis客户端尚未握手,执行握手逻辑if(!$this-sockets[(int)$socket['handshake'){self:handShake($socket,$buffer);continue;}else{$recv_msg=self:parse($buffer);}}//广播消息$this-broadcast($msg);}}}这只是服务器处理消息的基本代码。日志和异常处理就跳过了,还有一些数据帧的解析和封装方法。不一定非得看艾,有兴趣的话可以在github上支持一下我的源码~~

另外,为了方便服务端和客户端交互,我自己定义了json类型的消息格式,如下所示:

?$msg=['type'=$msg_type,//有普通消息、在线消息、离线消息、服务器消息'from'=$msg_resource,//消息来源'content'=$msg_content,//消息内容'user_list'=$uname_list,//方便同步当前在线人数和姓名;

客户端

创建客户端

在前端,我们使用js调用Websocket方法,轻松创建websocket连接。服务器会帮我们完成连接和握手操作。js使用事件机制来处理浏览器和服务器之间的交互:

?//创建Websocket连接varws=newWebSocket('');//Websocket创建成功事件ws.onopen=function(){};//Websocket收到消息事件ws.onmessage=function(e){varmsg=JSON.parse(e.data);}//websocket错误事件ws.onerror=function(){};发送消息也很简单,直接调用ws.send(msg)方法即可。

页面功能

页面部分主要是为了方便用户使用。这里在消息框textarea中添加了键盘监听事件,当用户按下回车键时直接发送消息;

?functionrecognize(event){varkey_num=event.keyCode;if(13==key_num){send();}else{returnfalse;}}还有一个用户打开客户端时生成的默认唯一用户名;

然后是一些数据的分析和构建以及客户端页面的更新。这里我就不详细说了。如果有兴趣,可以阅读源码。

用户名异步处理

这里不得不提一下用户登录时判断用户名的一个小问题,本来想在客户端创建连接后直接将用户名发送到服务器,但是控制台报“websocketisstill”连接“正在或已关闭”错误消息。

未捕获DOMException:无法在“WebSocket”上执行“发送”:仍处于CONNECTING状态。

考虑到连接可能还没处理完,我实现了sleep方法,等待一秒才发送用户名,但是错误依然存在。

后来突然想到了js的单线程阻塞机制,意识到使用sleep一直阻塞是没有用的。利用好js的事件机制才是正路:所以我在服务器端添加了逻辑。握手成功后,向客户端发送握手就已经成功了。信息;客户端首先将用户名存储在全局变量中,然后在收到服务器握手成功的提醒消息后发送用户名,从而第一时间成功更新用户名。

总结

聊天室扩展方向

简单的聊天室就完成了。当然,需要给予它光明的未来和希望。我希望有人能意识到这一点。

页面美化(信息加色等)服务器识别‘@’字符,只向某个socket写入数据,实现聊天室多进程私聊(使用redis等缓存数据库实现资源共享)消息记录数据库持久化(日志log还是分析不方便)以上就是如何使用PHPwebsocket实现网页实时聊天的详细内容。更多关于使用PHPwebsocket实现网页实时聊天的内容,请关注服务器之家其他相关文章!