socket的中文名字叫作套接字,这种东西就是对TCP/IP的“封装”。现实中的网络实际上只有四层而已,从上至下分别是应用层、传输层、网络层、数据链路层。最经常使用的http协议则是属于应用层的协议,而socket,能够简单粗暴的理解为是传输层的一种东西。若是仍是很难理解,那再粗暴地点儿tcp://218.221.11.23:9999,看到没?这就是一个tcp socket。php
socket赋予了咱们操控传输层和网络层的能力,从而获得更强的性能和更高的效率,socket编程是解决高并发网络服务器的最经常使用解决和成熟的解决方案。任何一名服务器程序员都应当掌握socket编程相关技能。git
在php中,能够操控socket的函数一共有两套,一套是socket_*系列的函数,另外一套是stream_*系列的函数。socket_*是php直接将C语言中的socket抄了过来获得的实现,而stream_*系则是php使用流的概念将其进行了一层封装。下面用socket_*系函数简单为这一系列文章开个篇。程序员
先来作个最简单socket服务器:github
<?php $host = '0.0.0.0'; $port = 9999; // 建立一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 进入while循环,不用担忧死循环死机,由于程序将会阻塞在下面的socket_accept()函数上 while( true ){ // 此处将会阻塞住,一直到有客户端来链接服务器。阻塞状态的进程是不会占据CPU的 /* 之因此不会占据CPU,由于CPU运算的时候,相似有个指挥官的家伙会调度,进程切换,简称调度,它只会指挥准备开始打战和正在打战的人,而正在休息军人(阻塞中)不须要命令他们打战,这样也符合常理了 你也能够看到下图,调度只在运行和就绪之间的,因此cpu不会傻傻等正在休息的士兵起来了,在再指挥 */ // 因此你不用担忧while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 向客户端发送一个helloworld $msg = "helloworld\r\n"; socket_write( $connection_socket, $msg, strlen( $msg ) ); socket_close( $connection_socket ); } socket_close( $listen_socket );
将文件保存为server.php,而后执行php server.php运行起来。客户端咱们使用telnet就能够了,打开另一个终端执行telnet 127.0.0.1 9999按下回车便可。运行结果以下:编程
简单解析一下上述代码来讲明一下tcp socket服务器的流程:服务器
上面这个案例中,有两个很大的缺陷:网络
分析了上述问题后,又联想到了前面说的多进程,那咱们能够在accpet到一个请求后就fork一个子进程来处理这个客户端的请求,这样当accept了第二个客户端后再fork一个子进程来处理第二个客户端的请求,这样问题不就解决了吗?OK!撸一把代码演示一下:并发
<?php $host = '0.0.0.0'; $port = 9999; // 建立一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 进入while循环,不用担忧死循环死机,由于程序将会阻塞在下面的socket_accept()函数上 while( true ){ // 此处将会阻塞住,一直到有客户端来链接服务器。阻塞状态的进程是不会占据CPU的 // 因此你不用担忧while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 当accept了新的客户端链接后,就fork出一个子进程专门处理 $pid = pcntl_fork(); // 在子进程中处理当前链接的请求业务 if( 0 == $pid ){ // 向客户端发送一个helloworld $msg = "helloworld\r\n"; socket_write( $connection_socket, $msg, strlen( $msg ) ); // 休眠5秒钟,能够用来观察时候能够同时为多个客户端提供服务 echo time().' : a new client'.PHP_EOL; sleep( 5 ); socket_close( $connection_socket ); exit; } } socket_close( $listen_socket );
将代码保存为server.php,而后执行php server.php,客户端依然使用telnet 127.0.0.1 9999,只不过此次咱们开启两个终端来执行telnet。重点观察当第一个客户端链接上去后,第二个客户端时候也能够链接上去。运行结果以下:socket
经过接受到客户端请求的时间戳能够看到如今服务器能够同时为N个客户端服务的。可是,接着想,若是前后有1万个客户端来请求呢?这个时候服务器会fork出1万个子进程来处理每一个客户端链接,这是会死人的。fork自己就是一个很浪费系统资源的系统调用,1W次fork足以让系统崩溃,即使当下系统承受住了1W次fork,那么fork出来的这1W个子进程也够系统内存喝一壶了,最后是好不容易费劲fork出来的子进程在处理完毕当前客户端后又被关闭了,下次请求还要从新fork,这自己就是一种浪费,不符合社会主义主流价值观。若是是有人恶意攻击,那么系统fork的数量还会呈直线上涨一直到系统崩溃。tcp
因此,咱们就再次提出增进型解决方案。咱们能够预估一下业务量,而后在服务启动的时候就fork出固定数量的子进程,每一个子进程处于无限循环中并阻塞在accept上,当有客户端链接挤进来就处理客户请求,当处理完成后仅仅关闭链接但自己并不销毁,而是继续等待下一个客户端的请求。这样,不只避免了进程反复fork销毁巨大资源浪费,并且经过固定数量的子进程来保护系统不会因无限fork而崩溃。
<?php $host = '0.0.0.0'; $port = 9999; // 建立一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 给主进程换个名字 cli_set_process_title( 'phpserver master process' ); // 按照数量fork出固定个数子进程 for( $i = 1; $i <= 10; $i++ ){ $pid = pcntl_fork(); if( 0 == $pid ){ cli_set_process_title( 'phpserver worker process' ); while( true ){ $conn_socket = socket_accept( $listen_socket ); $msg = "helloworld\r\n"; socket_write( $conn_socket, $msg, strlen( $msg ) ); socket_close( $conn_socket ); } } } // 主进程不能够退出,代码演示比较粗暴,为了避免保证退出直接走while循环,休眠一秒钟 // 实际上,主进程真正该作的应该是收集子进程pid,监控各个子进程的状态等等 while( true ){ sleep( 1 ); } socket_close( $connection_socket );
将文件保存为server.php后php server.php执行,而后再用ps -ef | grep phpserver | grep -v grep来看下服务器进程状态:
能够看到master进程存在,除此以外还有10个子进程处于等待服务状态,再同一个时刻能够同时为10个客户端提供服务。咱们经过telnet 127.0.0.1 9999来尝试一下,运行结果以下图:
好啦,php新的征程系列就先经过一个简单的入门开始啦!下篇将会讲述一些比较深入的理论基础知识。