socket俗称套接字,是网络通信编程的核心。php开发中也会用到,关于socket在php中的应用,做一点总结。
socket在TCP/IP协议中,属于应用层和传输层中间,相当于一层接口,将复杂的协议内容藏在后面。
既然socket用于网络通信,那么就以聊天室为例子:
首先搭建一个聊天室,我们需要一个服务端,作为一个平台,用于让用户连接,我们可以用socket搭建一个服务端。
先贴上代码
<?php
/*
聊天室服务器端
*/
/* 防止连接超时 */
set_time_limit(0);
$host = "127.0.0.1";
$port = 8888;
$reads = [];
$write = $except = null;
// 建立socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if(!is_resource($socket)) die("init socket fail");
// 可以重复利用本地ip
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// 绑定地址和端口
socket_bind($socket, $host, $port);
// backlog队列中最多有十个排队等待处理的链接
socket_listen($socket, 10);
$reads[] = $socket;
while(true){
// 最初有一个服务器端监听的socket
// 当数组中的某个有活动时,才会返回
// 返回时剔除了$read里不可操作的套接字
// 并不是服务端监听的套接字会变成可读,用户连接的套接字也会变成可读
// 如果是服务器监听套接字有活动,创建一个新的socket用于处理用户的请求
// 如果是用户连接套接字有活动,处理用户消息
$readUps = $reads;
$return = @socket_select($readUps, $write, $except, NULL);
if($return === false) {
die("Failed to listen for clients: ".socket_strerror(socket_last_error()));
}
foreach($readUps as $k => $_socket){
// 有活动的是服务器监听的套接字
if($_socket === $socket){
// 相当于用户连接建立的套接字,用于处理用户传来的数据
$connect = socket_accept($socket);
$reads[] = $connect;
continue;
}
// 获取用户消息
// $data为引用传递
if(!socket_recv($_socket, $data, 1024, 0) || !$data){
socket_close($_socket);
continue;
}
echo $data;die;
// preg_match("#Sec-WebSocket-Key: (.*?)\r\n#", $buffer, $match) && $key = $match[1];
// $key .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// $key = sha1($key);
// $key = pack('H*', $key);
// $key = base64_encode($key);
// $upgrade =
// "HTTP/1.1 101 Switching Protocols\r\n" .
// "Upgrade: websocket\r\n" .
// "Connection: Upgrade\r\n" .
// "Sec-WebSocket-Accept: {$key}\r\n\r\n";
}
sleep(1);
}
这段代码中主要一个是socket_select还有一个就是socket_accept
首先socket_select:
socket_select 接受三个套接字数组,分别检查数组中的套接字是否处于可以操作的状态(返回时只保留可操作的套接字)
使用最多的是 $read,因此以读为例
在套接字数组 $read 中最初应保有一个服务端监听套接字
每当该套接字可读时,就表示有一个用户发起了连接。此时你需要对该连接创建一个套接字,并加入到 $read 数组中
当然,并不只是服务端监听的套接字会变成可读的,用户套接字也会变成可读的,此时你就可以读取用户发来的数据了
socket_select 只在套接字数组发生了变化时才返回。也就是说,一旦执行到 socket_select 的下一条语句,则必有一个套接字是需要你操作的
再来是socket_accept:
此函数接受唯一参数,即前面socket_create创建的socket文件(句柄)。返回一个新的资源,或者FALSE。本函数将会通知socket_listen(),将会传入一个连接的socket资源。一旦成功建立socket连接,将会返回一个新的socket资源,用于通信。如果有多个socket在队列中,那么将会先处理第一个。关键就是这里:如果没有socket连接,那么本函数将会等待,直到有新socket进来。
其实就是,如果有一个客户端连接服务器了,那么就创建一个新的socket用于跟服务器端交流
如果前面不用socket_select在没有socket的时候阻塞住程序,那么就卡在这里永远无法结束了。
服务器端代码好了,下面就是客户端,代码如下:
<?php
/*
聊天室客户端
*/
/* 防止连接超时 */
set_time_limit(0);
$host = "127.0.0.1";
$port = 8888;
// 建立socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if(!is_resource($socket)) die("init socket fail");
echo "trying connect $host on port : $port".PHP_EOL;
if(!socket_connect($socket, $host,$port)) die("connenct fail");
echo "success!";
socket_write($socket, "hello", 5);
socket_close($socket);
我们连接服务端,然后发送了一个hello,窗口下已经能看到输出了
这里有个地方需要注意,如果我们在服务端的代码这块加上输出
$return = @socket_select($readUps, $write, $except, NULL);var_dump($readUps);
你会发现有两个输出,而且是不一样的socket,?????
因为client连接server时,socket_select检测到服务端监听的socket有活动,所有socket_select返回了,代码往下执行了,接着socket_accept新建了一个socket,紧接着client又socket_write,向server发送信息,socket_select检测到刚刚accept新建的socket(也就是客户端过来的连接)有活动,于是socket_select再次返回,于是代码继续执行。所以会输出两次。