如何用PHP websocket实现网页实时聊天

如何用PHP websocket实现网页实时聊天

Websocket作为HTML5中的一个新特性,一直备受人们关注,因为它真的很酷,打破了http“请求-响应”的常规思维,实现了服务器主动向客户端推送消息。本文介绍了如何利用PHP和JS应用websocket实现一个网页实时聊天室。

目录

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

前言

最近,我很难“挤出”websocket“请求即服务”服务器,这是我很久以前做的。我用js改进了客户端功能,把过程和想法分享给大家。顺便还普及了websocket相关知识。当然现在讨论websocket的文章很多,我就跳过一些理论上的东西。我会给出参考文章供你选择阅读。

在开始正文之前,贴一张聊天室的效果图(请不要在意CSS渣的页面):

websocket

简介

WebSocket不是一种技术,而是一种全新的协议。它使用TCP Socket为网络应用定义了一个新的重要的能力:客户端和服务器之间的双全时传输和双向通信。继Java小程序、XMLHttpRequest、Adobe Flash、ActiveXObject、各种Comet技术之后,服务器推送客户端消息成为新趋势。

与http的关系

在网络分层方面,websocket和http协议都是应用层的协议,都是基于tcp传输层。但websocket建立连接时,借用http的101 switch协议实现协议升级,从HTTP协议切换到WebSocket通信协议。这个动作协议被称为“握手”。

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

握手

以下是我自己浏览器发的典型握手http头:

收到握手请求后,服务器从请求头中提取“Sec-WebSocket-Key”字段,恢复一个固定字符串“258 ea 5 a 5-e914-47da-95ca-C5 ab 0 DC 85 b 11”,然后用sha1加密,最后将其转换为base64代码,并将其作为带有“Sec-WebSocket-Accept”字段的密钥返回给

数据传输

Websocket有自己的数据传输格式,称为帧。下图显示了数据帧的结构,单位为比特:

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

- - - - - - - -

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

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

|N|V|V|V| |S| |(如果有效载荷len==126/127) |

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

- - - - - - - - - - - - - - - - - - - - - -

|有效载荷长度延长,如果有效载荷长度==127 |

- - - - - - - - - - - - - - - -

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

- -

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

- - - - - - - - - - - - - - - -

:有效负载数据续.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

|有效载荷数据(续).|

-

每个字段是什么意思?如果你有兴趣,你可以阅读这篇文章。数据帧觉得二进制运算不是很灵活,就不挑战自己写算法解析数据了。下面的数据帧解析和封装都是在线算法。

但是,在工作中,我仍然经常使用支付网关中数据的小数运算。这一点必须认真研究和总结。嗯,先写下来。

PHP 实现 websocket 服务器

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

PHP的socket函数库和C语言的socket函数非常相似。我之前翻了一个APUE,所以我觉得挺好理解的。看php手册里的socket函数。我想你也可以对PHP的socket编程有一定的了解。

下面将简单评论代码中使用的函数。

文件描述符

大家突然提到‘文件描述符’可能会有点奇怪。

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

我们知道linux是‘一切都是文件’,C语言的socket实现是一个个‘文件描述符’。这个文件描述符一般是一个int值,按照打开文件的升序排列,从0开始一直递增(当然系统是有限的)。每个套接字对应一个文件,读写套接字是操作对应的文件,所以读写功能也可以像文件系统一样应用。

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

那么类似C socket的PHP socket自然继承了这一点,它创建的socket也是int值为4 5的资源类型。我们可以用(int)或intval()函数将socket转换成唯一的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_bind($this-master,$host,$ port);

listen函数将活动的连接windows套接字更改为已连接的windows套接字,使该套接字可以被其他套接字访问,从而实现服务器功能。后一个参数是要处理的自定义套接字的最大数量。在高并发的情况下,这个值可以设置得更大,尽管它也受到系统环境的约束。

socket_listen($this-master,self:LISTEN _ SOCKET _ NUM);

这样,我们就得到一个服务器套接字。当客户端连接到此套接字时,它会将其状态更改为可读。这取决于服务器接下来的处理逻辑。

服务器逻辑

这里我们重点关注socket _ select ($ read,$ write,$ except,$ tv _ sec [,$ tv _ usec]):

选择函数使用传统的选择模型。可读、可写和异常的套接字会放入$ socket、$ write和$ except数组中,然后返回状态改变的套接字数量。如果出现错误,该函数将返回false。

请注意,最后两个时间参数(只是单位不同)可以一起用来指示socket_select阻塞的持续时间。当它为0时,该函数立即返回,可用于轮询机制。当它为空时,函数将一直阻塞。这里,我们将$tv_sec参数设置为null,并让它阻塞,直到一个可操作的套接字返回。

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

$ write=$ except=NULL

$ sockets=array _ column($ this-sockets, resource );//获取所有套接字资源

$ read _ num=socket _ select($ sockets,$write,$except,NULL);

foreach ($sockets as $socket) {

//如果可读服务器套接字,处理连接逻辑;

if ($socket==$this-master) {

socket _ accept($ this-master);

//socket_accept()接受连接请求“正在监听的套接字(像我们的服务器套接字)”和一个客户端套接字,错误返回false

self:connect($ client);

继续;

}

//如果其他连接的套接字是可读的,则读取它们的数据并处理响应逻辑。

}否则{

//函数socket_recv()从套接字接受长度为len字节的数据,并保存在$buffer中。

$bytes=@socket_recv($socket,$buffer,2048,0);

if(字节9) {

//当客户端突然中断时,服务器会收到一个8字节的消息(由于其数据帧机制,该8字节消息被认为是客户端的异常中断消息),服务器会对离线逻辑进行处理,封装成消息进行广播。

$ recv _ msg=$ this-disconnect($ socket);

}否则{

//如果这个客户端还没有握手,执行握手逻辑。

如果(!$ this-sockets[(int)$ socket][ handshake ]){

self:握手($socket,$ buffer);

继续;

}否则{

$ recv _ msg=self:parse($ buffer);

}

}

//广播消息

$ this-broadcast($ msg);

}

}

}

这里只是服务器处理消息的基本代码。日志记录和异常处理被跳过,有一些解析和封装数据帧的方法。你可能不爱他们。有兴趣可以去github支持我的源码~ ~

另外,为了方便服务器和客户端的交互,我定义了自己的json类型的消息格式,看起来像:

$msg=[

Type=$msg_type,//有一般消息,在线和离线消息,服务器消息。

From=$msg_resource,//source

Content=$msg_content,//消息内容

User_list=$uname_list,//方便同步当前在线人数和姓名。

];

客户端

创建客户端

我们只需要在前端用js调用Websocket方法就可以创建一个websocket连接,服务器会完成连接并为我们握手。js使用事件机制来处理浏览器和服务器之间的交互:

//创建websocket连接

var ws=new web socket( ws://127 . 0 . 0 . 1:8080 );

//websocket创建成功事件

ws.onopen=function () {

};

//websocket收到消息事件

ws.onmessage=function (e) {

var msg=JSON . parse(e . data);

}

//websocket错误事件

ws.onerror=function () {

};

发送消息也很简单,直接调用ws.send(msg)方法即可。

页面功能

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

功能确认(事件){

var key _ num=event.keyCode

if (13==key_num) {

send();

}否则{

返回false

}

}

还有用户打开客户端时生成的默认唯一用户名;

然后还有一些数据的解析结构,客户端页面的更新,这里就不赘述了。有兴趣可以看看源代码。

用户名异步处理

这里不得不提一个用户登录时确定用户名的小问题。我本来想在客户端创建连接后直接把用户名发送给服务器,但是控制台报错“websocket仍然连接或关闭”。

未捕获的DOMException:无法对“WebSocket”执行“send”:仍处于连接状态。

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

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

总结

聊天室扩展方向

简单的聊天室完成了,当然会有一个充满希望的美好未来,希望有人实现。

页面美化(给信息增加色彩等。)

服务器识别字符 @ ,只将数据写入一个套接字,实现聊天室的私聊。

多进程(使用redis等缓存数据库来共享资源)

记录消息数据库持久性(日志或不便分析)

以上就是如何使用PHP websocket实现实时网络聊天的细节。关于如何用PHP websocket实现实时网络聊天的更多信息,请关注其他相关文章!

如何用PHP websocket实现网页实时聊天