转载请注明文章出处:https://tlanyan.me/php-review...PHP复习系列目录PH??P基础web请求cookieweb响应session数据库操作加解密Composer自创Composer包发送邮件IO流Web开发一直是PHP的主战场,也是PHP最为人熟知的方面。其实只要你愿意去探索,PHP除了制作网页之外,在很多方面也是个小能手。本文简要介绍PHPSocket编程。在开始之前,我希望您已经了解网络编程的一些基本概念。例如OSI七层模型、TCP/IP四层模型;TCP中的三次握手和四次挥手。这些概念是网络编程的理论基础。它们在实践中可能用不到,但可以让你掌握全局,更快地定位编程中的问题。再说说Socket。我们常说的网络编程指的是Socket编程,它不仅仅是指一组实现TCP/IP协议族的网络编程API,还指客户端和服务器之间的连接。Socket是套接字/接口的意思,在计算机中常译为“套接字”。在实践中,可以简单的认为网络编程等同于Socket编程,一个tcp连接等同于一个socket。PHP中的APIPHP有一套以socket开头的函数API,用于Socket编程。PHP5引入“stream”这个抽象概念后,一套以stream开头的API也可以用于网络编程。两者的主要区别在于:stream是PHP中的核心概念,所以以stream开头的函数总是可用的;sockets是PHP的扩展,虽然在大多数情况下它是默认启用的;socket系列的函数是比较底层的,而stream系列的函数是高层的抽象。如果想体验Socket编程的原汁原味,以socket开头的API更合适;否则,建议使用流功能。关于流的知识可以参考我之前的博文:PHP回顾流。接下来,我们使用stream函数来实现一个简单的TCP客户端和服务端。客户端客户端网络编程可以归结为三个简单的步骤:连接到服务器(connect);发送和接收消息(接收/发送);关闭连接(close)。下面是客户端的代码,发送10条消息到服务端://client.php$host="127.0.0.1";$port=8000;$socket=@stream_socket_client("tcp://{$host}:{$port}",$errno,$errMsg);if($socket===false){thrownew\RuntimeException("unabletocreatesocket:".$errMsg);}fwrite(STDOUT,"成功连接到服务器:[{$host}:{$port}]...\n");foreach(range(1,10)as$i){if($i%5===0){$method="广播";}else{$method="echo";}$args=[sprintf("第%d次问候",$i)];$message=json_encode(["method"=>$method,"args"=>$args,]);fwrite(STDOUT,"\nsendtoserver:$message\n");$len=@fwrite($socket,$message);if($len===0){fwrite(STDOUT,"套接字关闭\n");休息;}$msg=@fread($socket,4096);if($msg){fwrite(STDOUT,"接收服务器:$msg\n");}elseif(feof($socket)){fwrite(STDOUT,"socketclosed\n");休息;}睡眠(2);}fwrite(标准输出,“关闭连接...\n”);fclose($socket);client已经搞定了,再来看serverserverserver编程也很简单,四步搞定:监听端口(listen);接受);发送和接收网络消息(receive/send);循环第二步和第三步(loop)。由于服务器一般会运行很长时间,除非重启或者杀掉进程,否则很少会主动关闭服务。另外,服务端一般需要长时间运行,所以应该以CLI方式运行(短期客户端代码可以在web中使用,比如代替CURL获取网页内容,连接redis/MQ等)。我们简单地将接收到的消息返回给客户端(Echo服务器)://server.php$port=8000;$socket=@stream_socket_server("tcp://0.0.0.0:$port",$errno,$errMsg);if($socket===false){thrownew\RuntimeException("监听端口失败:{$port}!");}fwrite(STDOUT,"socketserverlistenonport:{$port}".PHP_EOL);while(true){$client=@stream_socket_accept($socket);如果($client==false){继续;}fwrite(STDOUT,"client:".(int)$client."connected.\n");@fwrite($client,"欢迎加入!\n");while(true){$msg=@fread($client,4096);if($msg){fwrite(STDOUT,"\n接收客户端:$msg\n");//echo@fwrite($client,$msg);}elseif(feof($client)){fwrite(STDOUT,"client:".(int)$client."disconnect!\n");fclose($client);休息;}}}首先启动服务端脚本:phpserver.php,然后新开一个窗口启动客户端:phpclient.php。您可以看到消息已正确发送和接收。客户端退出后,可以重新运行客户端脚本多次,查看效果。如果你并发运行两个或多个客户端,你会发现第二个卡住了,前一个客户端退出后还会继续运行。回头看服务端代码,可以看到接受了一个客户端后,服务端会专心为它服务,直到断开连接才会服务下一个。同时服务多个客户是我们所期望的。默认情况下,socket是阻塞模式,没有数据时fread函数会一直等待,这样程序就无法退出去服务其他客户端。同时服务多个客户端,第一步是设置非阻塞模式,第二步是改变轮询方式。stream函数中的stream_set_blocking和stream_select这两个函数就是我们想要的。将服务端的代码修改如下://server.php$client){while(true){$msg=@fread($client,4096);if($msg){fwrite(STDOUT,"receiveclient".(int)$client."message:$msg\n");$json=json_decode($msg,true);if($json){$method=$json["method"];if($method==='echo'){@fwrite($client,$msg);}else{foreach($clientsas$cl){@fwrite($cl,"消息来自".(int)$client.":$msg");}}}}else{if(feof($client)){fwrite(STDOUT,"\nclient".(int)$cl病人。"关闭。\n");fclose($client);$key=array_search($client,$clients);取消设置($clients[$key]);}休息;}}}}然后启动服务器:phpserver.php,然后同时启动多个客户端,或者使用多个进程同时发送消息(需要安装pcntl扩展)://client.phpfor($index=0;$index<10;++$index){$pid=pcntl_fork();if($pid<0){fwrite(STDERR,"分叉失败!\n");出口;}if($pid===0){connectServer();//connectServer是上面client.php中的代码exit;}}//父进程先退出,不会出现僵尸进程,忽略孤儿进程的处理。启动客户端后,可以看到服务器同时正确处理了多个客户端,这是我们期待的缺点。上面的代码实现了客户端和并发服务器,作为演示基本够用了。如果要在实践中使用,至少存在以下不足:缺乏多进程/多线程/协程,除了处理网络消息外,不能(难以)做其他逻辑服务;不进行协议分析,将多条信息合并为一条读取(或将一条信息拆分为多条);select效率低,有并发连接数限制,客户端大时需要poll/epoll等技术;每个方面都至少是一篇长文。本文的目的是简单介绍PHP中的Socket编程,本文的目的已经达到。由于网络协议的复杂性,深入网络编程请参考比较权威的文档。总结本文基于PHP5引入的流,简单介绍了PHP中的Socket编程,并给出了一个简单的并发服务器实现。本文中的代码仅用于演示。在生产环境中,请使用成熟的网络框架/库。参考http://php.net/manual/en/book...http://www.unixguide.net/netw...http://php.net/manual/en/book...
