php中的socket一点总结

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再次返回,于是代码继续执行。所以会输出两次。