Php Socket学习
php socket函数
https://www.php.net/manual/zh/ref.sockets.php
- socket_accept — 接受套接字上的连接
- socket_addrinfo_bind — 从给定的 addrinfo 创建并绑定一个套接字
- socket_addrinfo_connect — 指定 addrinfo 创建并连接套接字
- socket_addrinfo_explain — 获取有关 addrinfo 的信息
- socket_addrinfo_lookup — 获取数组,包含有关给定主机名的 getaddrinfo 内容
- socket_bind — 给套接字绑定名字
- socket_clear_error — 清除套接字或者最后的错误代码上的错误
- socket_close — 关闭 Socket 实例
- socket_cmsg_space — Calculate message buffer size
- socket_connect — 开启一个套接字连接
- socket_create_listen — 在端口上打开一个套接字以接受连接
- socket_create_pair — 创建一对彼此连接的套接字,并用数组存储
- socket_create — 创建一个套接字(通讯节点)
- socket_export_stream — Export a socket into a stream that encapsulates a socket
- socket_get_option — 获取套接字的套接字选项
- socket_getopt — 别名 socket_get_option
- socket_getpeername — 获取套接字远端名字,返回主机名和端口号或是 Unix 文件系统路径,具体取决于套接字类型
- socket_getsockname — 获取套接字本地端的名字,返回主机名和端口号或是 Unix 文件系统路径,具体取决于套接字类型
- socket_import_stream — 导入 stream
- socket_last_error — 返回套接字上的最后一个错误
- socket_listen — 监听套接字的连接
- socket_read — 从套接字中读取最大长度的数据
- socket_recv — 从已连接的 socket 接收数据
- socket_recvfrom — 从套接字接收数据,无论它是否是面向连接的
- socket_recvmsg — Read a message
- socket_select — 从给定套接字数组运行带指定超时时间的 select() 系统调用
- socket_send — 向已连接的套接字发送数据
- socket_sendmsg — Send a message
- socket_sendto — 向套接字发送消息,无论它是否已建立连接
- socket_set_block — 设置套接字为阻塞模式
- socket_set_nonblock — 设置套接字为非阻塞模式
- socket_set_option — 为套接字设置套接字选项
- socket_setopt — 别名 socket_set_option
- socket_shutdown — 关闭套接字接收或发送,或两者都关闭
- socket_strerror — 返回描述套接字错误的字符串
- socket_write — 向套接字写数据
- socket_wsaprotocol_info_export — 导出 WSAPROTOCOL_INFO 结构体
- socket_wsaprotocol_info_import — 从另一个进程导入套接字
- socket_wsaprotocol_info_release — 释放已导出的 WSAPROTOCOL_INFO 结构体
简单的server
netstat -an|grep 6001
telnet 127.0.0.1 6001
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket1,'0.0.0.0',6001); //想让局域网内其他主机连接到该socket,监听ip为0.0.0.0即可
//socket1 负责监听
socket_listen($socket1,3);
//socket2 负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
//读取客户端发送的数据
$res=socket_read($socket2,1024);
echo $res."\n";
socket_write($socket2,'hello client');
socket_close($socket2);
socket_close($socket1);
简单的client
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$host = '127.0.0.1';
//$host='192.168.0.102'; //SocketTool
$port = 6001;
$sock = null;
if (!$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) { //创建socket
echo "socket_create() failed ,reason:" .
socket_strerror(socket_last_error()) . "\n";
return;
}
if ($con = socket_connect($sock, $host, $port) === false) {
echo 'connect fail' . "\n";
return;
} else {
echo 'connect success' . "\n";
}
$message = "hello server";
if (socket_write($sock, $message, strlen($message)) === false) {
echo 'fail to write' . socket_strerror(socket_last_error());
} else {
echo 'client write success' . PHP_EOL . "\n";
//读取服务端返回来的套接流信息
while ($serverback = socket_read($sock, 1024)) {
echo 'server return message is' . PHP_EOL . $serverback;
}
}
调试工具:socketTool
服务端测试
客户端测试
浏览器连接socket
浏览器控制台:
ws=new WebSocket('ws://192.168.0.102:6001');
浏览器结果
服务端结果
这是因为服务端没有成功进行握手,我们需要对服务端代码进行改造
websocket01.php
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket1,'0.0.0.0',6002);
//socket1 负责监听
socket_listen($socket1,3);
//socket2 负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
//读取客户端发送的数据
$res=socket_read($socket2,1024);
echo $res.PHP_EOL;
handshake($res,$socket2);
socket_write($socket2,encode('hello client'));
socket_close($socket2);
socket_close($socket1);
//websocket握手处理
function handshake($buffer,$client_socket){
//截取Sec-WebSocket-Key的值并加密
$temp_str=substr($buffer,strpos($buffer,'Sec-WebSocket-Key')+18);
$client_key=trim(substr($temp_str,0,strpos($temp_str,"\r\n")));
$server_key=base64_encode(sha1($client_key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
echo 'client_key['.$client_key."]\n";
echo 'server_key['.$server_key."]\n\n";
//以下是响应包内容,浏览器会进行验证,成功后会建立连接
$handshake_msg="HTTP/1.1 101 Switching Protocols\r\n";
$handshake_msg.="Upgrade:websocket\r\n";
$handshake_msg.="Sec-WebSocket-Version: 13\r\n";
$handshake_msg.="Connection: Upgrade\r\n";
$handshake_msg.="Sec-WebSocket-Accept: ".$server_key."\r\n\r\n";
socket_write($client_socket,$handshake_msg,strlen($handshake_msg));
echo $handshake_msg;
}
//数据帧处理
function 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".chr(127).pack("xxxxN",$len).$buffer;
}
}
//数据帧处理
function 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 WebSocket('ws://192.168.0.102:6002');
这个时候没有报错了,说明浏览器已经校验通过了服务端发来的信息
这里的问题是==服务器并没有保持监听而是断开了连接,我们需要再次修改服务端代码==
websocket02.php
这个版本的服务端主要是解决了==持久连接的问题和多用户连接的问题==,注意这个版本不适配浏览器(搞定了持久化连接和诸多问题以后再去改成适配浏览器的版本)
建议debug看一下代码执行流程
注意这个socket_select($read, $write, $except, null)
,三个变量只有$read不是null,他只会监测$read,一旦$read里面的套接字有发生了变化的就进入if,并且让$read只剩下发生了变化的套接字,而我们的第二个if in_array($socket1, $read)
就限定了只监测接收我们的$socket1
如果socket_select($read, $write, $except, null)
没有监测到$read发生变化,就进入处理消息的那端代码了,就是说if (socket_select($read, $write, $except, null) > 0)
这段代码是管建立连接的,if (count($read) > 0)
这段代码是管处理消息的
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket1, '0.0.0.0', 6003);
//socket1 负责监听
socket_listen($socket1, 3);
$clients = array($socket1);
while (1) {
//socket_select对读写套接字的数字是引用,为了保证clients不被改变,拷贝一份
$read = $clients;
$write = null;
$except = null;
/*当read发生变化,说明:有客户端进行连接操作了,
$writr和$except均为null,所以只检测read数组的变化
socket_select的第四个参数null,表示阻塞,它会阻塞一直到发生变化,
0,表示为非阻塞,调用socket_select()后,立即返回,然后继续调用socket_select()不断循环
返回值是修改后的数组中包含套接字资源的数量
*/
if (socket_select($read, $write, $except, null) > 0) {
if (in_array($socket1, $read)) {
//socket2负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
$clients[] = $socket2;
socket_write($socket2, 'You are' . (count($clients) - 1) . "clients connect\r\n");
socket_getpeername($socket2, $ip, $port);
echo "\nnew client[" . $ip . ":" . $port . "]\n";
$key = array_search($socket1, $read);
unset($read[$key]);
}
if (count($read) > 0) {
foreach ($read as $socket_item) {
//读取客户端发来的数据
$msg = socket_read($socket_item, 1024);
//发给自己:$socket_item
socket_write($socket_item, 'received' . $msg);
foreach ($clients as $client_socket) {
//发给别人(除去监听和自己)
if ($client_socket != $socket1 && $client_socket != $socket_item) {
sleep(1);
socket_write($client_socket, $msg, strlen($msg));
}
}
}
}
} else {
continue;
}
}
这个服务端可以持久连接,但是还有一个新的问题,就是==有连接退出时我们没有进行处理,导致它会一直尝试连接==
websocket03.php
前面说上个版本的代码有连接退出时我们没有进行处理,导致它会一直尝试连接
这个版本完成==监测客户端断开并处理==
它做的事情就是给每次写数据都加了if,如果判断不通过就把这次不通过的套接字在数组里删掉
if (socket_select($read, $write, $except, null) > 0)
这段代码没变,if (count($read) > 0)
这段代码的foreach里面加了三个if
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket1, '0.0.0.0', 6004);
//socket1 负责监听
socket_listen($socket1, 3);
$clients = array($socket1);
while (1) {
//socket_select对读写套接字的数字是引用,为了保证clients不被改变,拷贝一份
$read = $clients;
$write = null;
$except = null;
/*当read发生变化,说明:有客户端进行连接操作了,
$write和$except均为null,所以只检测read数组的变化
socket_select的第四个参数null,表示阻塞,它会阻塞一直到发生变化,
0,表示为非阻塞,调用socket_select()后,立即返回,然后继续调用socket_select()不断循环
返回值是修改后的数组中包含套接字资源的数量
*/
if (socket_select($read, $write, $except, null) > 0) {
/*socket_select 接受三个套接字数组,分别检查数组中的套接字是否处于可以操作的状态(返回时只保留可操作的套接字)
使用最多的是 $read,因此以读为例,在套接字数组 $read 中最初应保有一个服务端监听套接字,每当该套接字可读时,就表示有一个用户发起了连接。此时你需要对该连接创建一个套接字,并加入到 $read 数组中
当然,并不只是服务端监听的套接字会变成可读的,用户套接字也会变成可读的,此时你就可以读取用户发来的数据了
socket_select 只在套接字数组发生了变化时才返回。也就是说,一旦执行到 socket_select 的下一条语句,则必有一个套接字是需要你操作的*/
if (in_array($socket1, $read)) {
//socket2负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
$clients[] = $socket2;
socket_write($socket2, 'You are ' . (count($clients) - 1) . " clients connect\r\n");
socket_getpeername($socket2, $ip, $port);
echo "\nnew client[" . $ip . ":" . $port . "]\n";
$key = array_search($socket1, $read);
unset($read[$key]);
}
if (count($read) > 0) {
foreach ($read as $socket_item) {
//读取客户端发来的数据
if (false === $msg = @socket_read($socket_item, 1024)) {
socket_close($socket_item);
$key = array_search($socket_item, $read);
unset($read[$key]);
unset($clients[$key]);
echo "READ ERROR client $key is closed!\n";
continue;
}
//发给自己:$socket_item
if (false === @socket_write($socket_item, 'I post : ' . $msg)) {
socket_close($socket_item);
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "MYclient $key is closed!\n";
continue;
}
foreach ($clients as $client_socket) {
//发给别人(除去监听和自己)
if ($client_socket != $socket1 && $client_socket != $socket_item) {
sleep(1);
//socket_write($client_socket, $msg, strlen($msg));
if (false === @socket_write($client_socket, $msg, strlen($msg))) {
socket_close($client_socket);
$key1 = array_search($client_socket, $read);
unset($read[$key1]);
$key2 = array_search($client_socket, $clients);
unset($clients[$key2]);
echo "otherclient $key is closed!\n";
}
}
}
}
}
} else {
continue;
}
}
但是可以看到这种判断方法并不优雅,实际上有更好的解决方法
websocket04.php
socket_recv
socket_recv()
函数用于从一个已连接的 socket收数据。
socket_recv($socket, &$buf, $, $flags);
参数说明:
$socket
: 已连接的 socket 资源。&$buf
: 接收到的数据将存储在该变量中。$len
: 期望接收的数据的最大字节数。$flags
:可选参数,用于指定接收操作的一些特殊选项。
返回值:
- 成功时,返回接收到的数据的字节数。
- 失败时,返回 false,并且可以通过调用
socket_last_error()
函数获取错误码。
示例代码如下:
$socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 将 $socket 连接到服务器
$buf = ""; // 存储接收到的数据
$len = 1024; // 最大字节数
$flags = 0; // 没有特殊选项
$bytesReceived = socket_recv($socket, $buf, $len, $flags);
if ($bytesReceived === false) {
echo "接收数据失败: " . socket_strerror(socket_last_error()) . "\n";
} else {
echo "接收到的数据: " . $buf . "\n";
echo "接收到的字节数:" . $bytesReceived . "\n";
}
socket_close($socket);
请注意,在使用 之前,你需要先创建一个已连接的 socket,并且通过
socket_connect()` 函数将其与远程服务器连接起来。
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket1, '0.0.0.0', 6005);
//socket1 负责监听
socket_listen($socket1, 3);
$clients = array($socket1);
while (1) {
//socket_select对读写套接字的数字是引用,为了保证clients不被改变,拷贝一份
$read = $clients;
$write = null;
$except = null;
/*当read发生变化,说明:有客户端进行连接操作了,
$write和$except均为null,所以只检测read数组的变化
socket_select的第四个参数null,表示阻塞,它会阻塞一直到发生变化,
0,表示为非阻塞,调用socket_select()后,立即返回,然后继续调用socket_select()不断循环
返回值是修改后的数组中包含套接字资源的数量
*/
if (socket_select($read, $write, $except, null) > 0) {
/*socket_select 接受三个套接字数组,分别检查数组中的套接字是否处于可以操作的状态(返回时只保留可操作的套接字)
使用最多的是 $read,因此以读为例,在套接字数组 $read 中最初应保有一个服务端监听套接字,每当该套接字可读时,就表示有一个用户发起了连接。此时你需要对该连接创建一个套接字,并加入到 $read 数组中
当然,并不只是服务端监听的套接字会变成可读的,用户套接字也会变成可读的,此时你就可以读取用户发来的数据了
socket_select 只在套接字数组发生了变化时才返回。也就是说,一旦执行到 socket_select 的下一条语句,则必有一个套接字是需要你操作的*/
if (in_array($socket1, $read)) {
//socket2负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
$clients[] = $socket2;
socket_write($socket2, 'You are ' . (count($clients) - 1) . " clients connect\r\n");
socket_getpeername($socket2, $ip, $port);
echo "\nnew client[" . $ip . ":" . $port . "]\n";
$key = array_search($socket1, $read);
unset($read[$key]);
}
if (count($read) > 0) {
foreach ($read as $socket_item) {
//读取客户端发来的数据
//$msg = socket_read($socket_item, 1024);
$result = socket_recv($socket_item, $msg, 1024, 0);
if ($result === false) {
//no data 客户端未发来消息
echo "no data" . PHP_EOL;
continue;
} elseif ($result === 0) {
//socket closed 客户端断开
socket_close($socket_item); //关闭服务器的socket
//清理已关闭的连接消息
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "client [$socket_item] is closed!\n";
} else {
echo "Server receive success [" . $msg . "]" . time() . PHP_EOL;
}
foreach ($clients as $client_socket) {
//发给别人(除去监听和自己)
if ($client_socket != $socket1 && $client_socket != $socket_item) {
sleep(1);
//socket_write($client_socket, $msg, strlen($msg));
if (false === @socket_write($client_socket, $msg, strlen($msg))) {
socket_close($client_socket);
$key1 = array_search($client_socket, $read);
unset($read[$key1]);
$key2 = array_search($client_socket, $clients);
unset($clients[$key2]);
echo "otherclient $key is closed!\n";
}
}
}
}
}
} else {
continue;
}
}
变化是
if (false === $msg = @socket_read($socket_item, 1024)) {
socket_close($socket_item);
$key = array_search($socket_item, $read);
unset($read[$key]);
unset($clients[$key]);
echo "READ ERROR client $key is closed!\n";
continue;
}
//发给自己:$socket_item
if (false === @socket_write($socket_item, 'I post : ' . $msg)) {
socket_close($socket_item);
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "MYclient $key is closed!\n";
continue;
}
变成了
$result = socket_recv($socket_item, $msg, 1024, 0);
if ($result === false) {
//no data 客户端未发来消息
echo "no data" . PHP_EOL;
continue;
} elseif ($result === 0) {
//socket closed 客户端断开
socket_close($socket_item); //关闭服务器的socket
//清理已关闭的连接消息
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "client [$socket_item] is closed!\n";
} else {
echo "Server receive success [" . $msg . "]" . time() . PHP_EOL;
}
发现失去了查看自己发送的消息的功能,我又给加上了
<?php
/*
* AF_INET IPv4网络协议,TCP和UDP都可用此协议
* AF_INET6 IPv6网络协议,TCP和UDP都可用此协议
* AF_UNIX 本地通讯协议,具有高性能和低成本的IPC(进程间通讯)
*
* SOCK_STREAM TCP协议套接字
* SOCK_DGRAM UDP协议套接字
*
* SOL_TCP TCP协议
* SOL_UDP UDP协议
*/
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket1, '0.0.0.0', 6004);
//socket1 负责监听
socket_listen($socket1, 3);
$clients = array($socket1);
while (1) {
//socket_select对读写套接字的数字是引用,为了保证clients不被改变,拷贝一份
$read = $clients;
$write = null;
$except = null;
/*当read发生变化,说明:有客户端进行连接操作了,
$write和$except均为null,所以只检测read数组的变化
socket_select的第四个参数null,表示阻塞,它会阻塞一直到发生变化,
0,表示为非阻塞,调用socket_select()后,立即返回,然后继续调用socket_select()不断循环
返回值是修改后的数组中包含套接字资源的数量
*/
if (socket_select($read, $write, $except, null) > 0) {
/*socket_select 接受三个套接字数组,分别检查数组中的套接字是否处于可以操作的状态(返回时只保留可操作的套接字)
使用最多的是 $read,因此以读为例,在套接字数组 $read 中最初应保有一个服务端监听套接字,每当该套接字可读时,就表示有一个用户发起了连接。此时你需要对该连接创建一个套接字,并加入到 $read 数组中
当然,并不只是服务端监听的套接字会变成可读的,用户套接字也会变成可读的,此时你就可以读取用户发来的数据了
socket_select 只在套接字数组发生了变化时才返回。也就是说,一旦执行到 socket_select 的下一条语句,则必有一个套接字是需要你操作的*/
if (in_array($socket1, $read)) {
//socket2负责处理通信(接收、发送)
$socket2 = socket_accept($socket1);
$clients[] = $socket2;
socket_write($socket2, 'You are ' . (count($clients) - 1) . " clients connect\r\n");
socket_getpeername($socket2, $ip, $port);
echo "\nnew client[" . $ip . ":" . $port . "]\n";
$key = array_search($socket1, $read);
unset($read[$key]);
}
if (count($read) > 0) {
foreach ($read as $socket_item) {
//读取客户端发来的数据
//$msg = socket_read($socket_item, 1024);
$result = socket_recv($socket_item, $msg, 1024, 0);
if ($result === false) {
//no data 客户端未发来消息
echo "no data" . PHP_EOL;
continue;
} elseif ($result === 0) {
//socket closed 客户端断开
socket_close($socket_item); //关闭服务器的socket
//清理已关闭的连接消息
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "client [$socket_item] is closed!\n";
} else {
echo "Server receive success [" . $msg . "]" . time() . PHP_EOL;
if (false === @socket_write($socket_item, 'I post : ' . $msg)) {
socket_close($socket_item);
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "MYclient $key is closed!\n";
continue;
}
}
foreach ($clients as $client_socket) {
//发给别人(除去监听和自己)
if ($client_socket != $socket1 && $client_socket != $socket_item) {
sleep(1);
//socket_write($client_socket, $msg, strlen($msg));
if (false === @socket_write($client_socket, $msg, strlen($msg))) {
socket_close($client_socket);
$key1 = array_search($client_socket, $read);
unset($read[$key1]);
$key2 = array_search($client_socket, $clients);
unset($clients[$key2]);
echo "otherclient $key is closed!\n";
}
}
}
}
}
} else {
continue;
}
}
websocket05.php
解决了php socket通信的诸多问题,我们现在来完成websocket聊天室雏形!
ws=new WebSocket('ws://192.168.10.106:6006');
ws.onmessage=function(msg){console.log(msg.data)};
ws.send('hello world!!');
服务端代码:
<?php
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket1, '0.0.0.0', 6006);
//socket1 负责监听
socket_listen($socket1, 3);
$clients = array($socket1);
while (true) {
//socket_select对读写套接字的数字是引用,为了保证clients不被改变,拷贝一份
$read = $clients;
$write = null;
$except = null;
if (socket_select($read, $write, $except, null) > 0) {
foreach ($read as $socket_item) {
if ($socket_item == $socket1) {
if (!$socket_client = socket_accept($socket_item)) continue;
$hello = @socket_read($socket_client, 1024);
if ($hello === false) {
socket_close($socket_client);
continue;
}
handshake($hello, $socket_client);
socket_getpeername($socket_client, $ip, $port);
$clients[$ip.":".$port] = $socket_client;
echo "new client[".$ip.":".$port."]\n";
echo "hello msg [".$hello."]\n";
}
else {
$result = @socket_recv($socket_item, $msg, 1024, 0);
if ($result === false) continue;
if ($result === 0) {
socket_close($socket_item);
$key1 = array_search($socket_item, $read);
unset($read[$key1]);
$key2 = array_search($socket_item, $clients);
unset($clients[$key2]);
echo "client [$socket_item] is closed!\n";
}
else {
$web_msg = decode($msg);
$id = search($socket_item, $clients);
echo "client [" . $id . "] say:" . $web_msg . "\n";
$broadcast = encode($web_msg);
foreach ($clients as $client_socket) {
//发给别人(除去监听和自己)
if ($client_socket != $socket1) {
//socket_write($client_socket, $msg, strlen($msg));
if (false === @socket_write($client_socket, $broadcast, strlen($broadcast))) {
socket_close($client_socket);
$key1 = array_search($client_socket, $read);
unset($read[$key1]);
$key2 = array_search($client_socket, $clients);
unset($clients[$key2]);
echo "otherclient [$socket_item] is closed!\n";
}else{
echo '消息已发送给'.$client_socket."\n";
}
}
}
}
}
}
} else {
continue;
}
}
function handshake($buffer, $client_socket)
{
//截取Sec-WebSocket-Key的值并加密
$temp_str = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key') + 18);
$client_key = trim(substr($temp_str, 0, strpos($temp_str, "\r\n")));
$server_key = base64_encode(sha1($client_key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
echo 'client_key[' . $client_key . "]\n";
echo 'server_key[' . $server_key . "]\n\n";
if ($client_key == '') return false;
//以下是响应包内容,浏览器会进行验证,成功后会建立连接
$handshake_msg = "HTTP/1.1 101 Switching Protocols\r\n";
$handshake_msg .= "Upgrade: websocket\r\n";
$handshake_msg .= "Sec-WebSocket-Version: 13\r\n";
$handshake_msg .= "Connection: Upgrade\r\n";
$handshake_msg .= "Sec-WebSocket-Accept: " . $server_key ."\r\n\r\n";
socket_write($client_socket, $handshake_msg, strlen($handshake_msg));
echo $handshake_msg;
return true;
}
//数据帧处理
function 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" . chr(127) . pack("xxxxN", $len) . $buffer;
}
}
function 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;
}
function search($socket_client, $clients)
{
$search = array_search($socket_client, $clients, true);
if ($search === null) $search = false;
return $search;
}
下面我们来搞一个客户端,主要就是javascript
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>socket</title>
</head>
<body>
<div id="cc" style="width:500px;">
<div id="content" style=" height:400px; overflow:auto; width:100%; border:1px solid #ccc;"></div>
<form id="form">
<textarea id="text" style="width:100%; height:200px;"></textarea>
<input type="button" onclick="ab();" value="发送">
</form>
</div>
<p id="a"></p>
<script src="https://code.jquery.com/jquery-2.1.3.min.js" type="text/javascript"></script>
</body>
</html>
<script>
var wsServer = 'ws://192.168.111.1:6006';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) {
console.log("Connected to WebSocket server.");
};
websocket.onclose = function (evt) {
console.log("Disconnected");
};
websocket.onmessage = function (evt) {
$('#content').append(evt.data+"<br>");
// document.getElementById('div').style.background = evt.data;
console.log('Retrieved data from server: ' + evt.data);
};
websocket.onerror = function (evt, e) {
console.log('Error occured: ' + evt.data);
};
function ab(){
var zhi = $('#text').val();
websocket.send(zhi);
$('#text').val('');
}
</script>