php中socket系、fsockopen、stream_socket系、curl_init系、获取http请求报文
参考:
https://www.php.cn/php-weizijiaocheng-363026.html(socket实现长连接)
socket系列 水泥、沙子,底层的东西
fsockopen 水泥预制件,可以用来搭房子
stream_socket系列
curl系列 毛坯房,自己装修一下就能住了
curl系列函数:应用层
基本用法:
// 创建一个新cURL资源 $ch = curl_init(); // 设置URL和相应的选项 curl_setopt($ch, CURLOPT_URL, "http://www.example.com/"); curl_setopt($ch, CURLOPT_HEADER, 0);
//或者
curl_setopt_array($ch,$opt_arr); // 抓取URL并把它传递给浏览器 $res = curl_exec($ch); // 关闭cURL资源,并且释放系统资源 curl_close($ch);
辅助函数:
curl_getinfo($ch)//返回资源句柄信息 curl_error($ch)//错误字符串 curl_errno($ch)//作物代码 curl_copy_handle($ch)//复制一个句柄和它的所有选项
stream_socket系列的函数
参考:https://www.jianshu.com/p/79591db09b5a
stream_socket服务端:
重要函数:
stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr);//创建服务 stream_socket_accept($socket);//获取连接句柄
fwrite($fpcon)
fgetc($fpcon)
fgets($fpcon)
fread($fpcon)
feof($fpcon)
fclose($fpcon)
使用方法:
$socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr); if (!$socket) { echo "$errstr ($errno)<br />\n"; } else { while ($conn = stream_socket_accept($socket)) { $data = fread($conn); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a') . "\n"); fclose($conn); } fclose($socket); }
stream_client客户端:
重要函数:
stream_socket_client("tcp://www.example.com:80", $errno, $errstr, 30);
fclose($fp)
使用方法:
$fp = stream_socket_client("tcp://www.example.com:80", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)<br />\n"; } else { fwrite($fp, "GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n"); while (!feof($fp)) { echo fgets($fp, 1024); } fclose($fp); }
fsockopen函数
fsockopen可以忽略socket里面的creat, connect, send, recv等等函数的用法,直接就open了
stream_socket_client 和 fsockopen 没有本质上的区别
stream_socket_client 和 fsockopen 分属不同流派的对 socket 的封装
fsockopen 是比较底层的调用,属于网络系统的socket调用,而curl经过的包装支持HTTPS认证,HTTP POST方法, HTTP PUT方法,FTP上传, kerberos认证,HTTP上传,代理服务器, cookies,用户名/密码认证,下载文件断点续传,上载文件断点续传,http代理服务器管道( proxy tunneling),甚至它还支持IPv6, socks5代理服务器,,通过http代理服务器上传文件到FTP服务器等等,功能十分强大。fsockopen 返回的是没有处理过的数据,包括数据的长度数据内容和数据的结束符。而curl是处理后的内容。
在用户使用时,curl 更加方便,但其参数很多,配置稍微复杂,fsockopen 则有固定的几个参数,简单,但获取结果可能需要再做处理。
http:get
function request_by_socket($remote_server,$remote_path,$post_string,$port = 80,$timeout = 30) { $socket = fsockopen($remote_server, $port, $errno, $errstr, $timeout); if (!$socket) die("$errstr($errno)"); fwrite($socket, "POST $remote_path HTTP/1.0"); fwrite($socket, "User-Agent: Socket Example"); fwrite($socket, "HOST: $remote_server"); fwrite($socket, "Content-type: application/x-www-form-urlencoded"); fwrite($socket, "Content-length: " . (strlen($post_string) + 8) . ""); fwrite($socket, "Accept:*/*"); fwrite($socket, ""); fwrite($socket, "mypost=$post_string"); fwrite($socket, ""); $header = ""; while ($str = trim(fgets($socket, 4096))) { $header .= $str; } $data = ""; while (!feof($socket)) { $data .= fgets($socket, 4096); } return $data; }
tcp
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)<br />\n"; } else { $out = "GET / HTTP/1.1\r\n"; $out .= "Host: www.example.com\r\n"; $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); while (!feof($fp)) { echo fgets($fp, 128); } fclose($fp); }
udp
$fp = fsockopen("udp://127.0.0.1", 13, $errno, $errstr); if (!$fp) { echo "ERROR: $errno - $errstr<br />\n"; } else { fwrite($fp, "\n"); echo fread($fp, 26); fclose($fp); }
socket底层函数
服务端:
重要函数:
* 1,创建 $socket=socket_create() * 2,绑定端口 socket_bind($socket) * 3,监听连接 socket_listen($socket) * 4,接收 $accept_resource=socket_accept($socket) 循环接收 * 5,读取 socket_read($accept_resource) * 6,写入 socket_write($accept_resource) * 7,关闭主机发过来的套接流 socket_close($accept_resource) * 8,关闭 socket_close($socket)
socket_last_error($socket),参数为socket_create的返回值,作用是获取套接字的最后一条错误码号,返回值套接字code socket_strerror($code),参数为socket_last_error函数的返回值,获取code的字符串信息,返回值也就是套接字的错误信息
socket_set_option($sfd, SOL_SOCKET, SO_REUSEADDR, 1);
socket_set_nonblock($sfd);
socket_select($rs, $ws, $es, 3);
使用方法(长连接):
date_default_timezone_set("Asia/Shanghai"); include_once "Db.php"; $sfd = socket_create(AF_INET, SOCK_STREAM, 0); socket_bind($sfd, "192.168.191.1", 9001); socket_listen($sfd, 10); //监听10个 socket_set_option($sfd, SOL_SOCKET, SO_REUSEADDR, 1); //重用端口 socket_set_nonblock($sfd); //非阻塞 $rfds = array($sfd); $wfds = array(); do{ $rs = $rfds; $ws = $wfds; $es = array(); $ret = socket_select($rs, $ws, $es, 3); //read event foreach($rs as $fd){ if($fd == $sfd){ $cfd = socket_accept($sfd); socket_set_nonblock($cfd); $rfds[] = $cfd; echo "new client coming, fd=$cfd\n"; }else{ //获取客户端IP地址 socket_getpeername($fd, $addr, $port); //读取客户端信息 $msg = socket_read($fd, 1024); echo date("H:i:s")." $fd $msg"; $arr=explode(",", $msg); //更新到数据库 $sql = "update test set wendu = $arr[0],shidu = $arr[1], green = $arr[2],yellow=$arr[3] ,red=$arr[4],dianliu=$arr[5], dianya=$arr[6] where ip = '$addr'" ; Db::query($sql); } } }while(true);
原来的写法;只能处理一个连接
//创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); /*绑定接收的套接流主机和端口,与客户端相对应*/ if(socket_bind($socket,'127.0.0.1',8889) == false){ echo 'server bind fail:'.socket_strerror(socket_last_error()); /*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/ } //监听套接流 if(socket_listen($socket,4)==false){ echo 'server listen fail:'.socket_strerror(socket_last_error()); } //echo 'create success'; //让服务器无限获取客户端传过来的信息 do{ /*接收客户端传过来的信息*/ $accept_resource = socket_accept($socket);//这里只能接受一个连接 /*socket_accept的作用就是接受socket_bind()所绑定的主机发过来的套接流*/ if($accept_resource !== false){ /*读取客户端传过来的资源,并转化为字符串*/ while(true){//让服务器无限获取客户端传过来的信息 $string = mb_convert_encoding(socket_read($accept_resource,1024),'utf-8','GBK'); /*mb_convert_encoding()*/ /*socket_read的作用就是读出socket_accept()的资源并把它转化为字符串*/ echo 'server receive is :'.$string.PHP_EOL;//PHP_EOL为php的换行预定义常量 if($string != false){ $return_client = 'server receive is : '.$string.PHP_EOL; /*向socket_accept的套接流写入信息,也就是回馈信息给socket_bind()所绑定的主机客户端*/ socket_write($accept_resource,$return_client,strlen($return_client)); /*socket_write的作用是向socket_create的套接流写入信息,或者向socket_accept的套接流写入信息*/ }else{ echo 'socket_read is fail'; } } /*socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流*/ socket_close($accept_resource); } }while(1) socket_close($socket);
客户端:
重要函数
* 1,创建 $socket=socket_create() * 2,连接 socket_connect() * 3,写入 socket_write($socket) * 4,读取 socket_read($socket) 循环读取 * 5,关闭 socket_close($socket)
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 1, "usec" => 0));
使用方法(长连接):
//创建一个socket套接流 $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); /****************设置socket连接选项,这两个步骤你可以省略*************/ //接收套接流的最大超时时间1秒,后面是微秒单位超时时间,设置为零,表示不管它 socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 1, "usec" => 0)); //发送套接流的最大超时时间为6秒 socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 6, "usec" => 0)); /****************设置socket连接选项,这两个步骤你可以省略*************/ //连接服务端的套接流,这一步就是使客户端与服务器端的套接流建立联系 if(socket_connect($socket,'127.0.0.1',8889) == false){ echo 'connect fail massege:'.socket_strerror(socket_last_error()); }else{ while(true){ fwrite(STDOUT, "Enter the message:"); $message = trim(fgets(STDIN)); $message = mb_convert_encoding($message,'UTF-8','GBK'); //向服务端写入字符串信息 if(socket_write($socket,$message,mb_strlen($message)) == false){ echo 'fail to write'.socket_strerror(socket_last_error()); }else{ echo 'client write success'.PHP_EOL; //读取服务端返回来的套接流信息 while($callback = socket_read($socket,1024)){ echo 'server return message is:'.PHP_EOL.$callback; } } } } socket_close($socket);//工作完毕,关闭套接流
总之,file_get_contents 和 curl 能干的,socket都能干。socket能干的,curl 就不一定能干了。file_get_contents 更多的时候只是去拉取数据。效率比较高也比较简单。
只讨论 curl 与file_get_contents 的话,有这么一些结论:
1. fopen /file_get_contents 每次请求都会重新做DNS查询,并不对DNS信息进行缓存。但是CURL会自动对DNS信息进行缓存。对同一域名下的网页或者图片的请求只需要一次DNS查询。这大大减少了DNS查询的次数。所以CURL的性能比fopen /file_get_contents 好很多。
2. fopen /file_get_contents在请求HTTP时,使用的是http_fopen_wrapper,不会keeplive。而curl却可以。这样在多次请求多个链接时,curl效率会好一些。
3. fopen / file_get_contents函数会受到php.ini文件中allow_url_open选项配置的影响。如果该配置关闭了,则该函数也就失效了。而curl不受该配置的影响。
4. curl可以模拟多种请求,例如:POST数据,表单提交等,用户可以按照自己的需求来定制请求。而fopen / file_get_contents只能使用get方式获取数据。
PS:file_get_contents()函数获取https链接内容的时候,需要php 中mod_ssl的支持(或安装opensll)。
那么file_get_contents呢?
有些时候用 file_get_contents() 调用外部文件容易超时报错。
curl 效率比 file_get_contents() 和 fsockopen() 高一些,原因是CURL会自动对DNS信息进行缓存。
file_get_contents 需要php.ini里开启allow_url_fopen。
file_get_contents()单个执行效率高,返回没有头的信息。
function send_post($url, $post_data) { $postdata = http_build_query($post_data); $options = array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type:application/x-www-form-urlencoded', 'content' => $postdata, 'timeout' => 15 * 60 // 超时时间(单位:s) ) ); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); return $result; } $post_data = array( 'username' => 'abcdef', 'password' => '123456' ); send_post('http://xxx.com', $post_data);
结论就是:curl 效率及稳定都比 file_get_contents() 要好,fsockopen 也很强大,但是比较偏底层。
$_POST 和 file_get_contents ("PHP://input") 的区别
$_POST [‘paramName’]
只能接收 Content-Type: application/x-www-form-urlencoded 提交的数据,php 会将 http 请求 body 相应数据会 填入到数组 $_POST,填入到 $_POST 数组中的数据是进行 urldecode () 解析的结果。(其实,除了该 Content-Type,还有 multipart/form-data 表示数据是表单数据)
file_get_contents (“php://input”)
适用大多数类型的 Content-type,php://input 允许读取 POST 的原始数据。和 $HTTP_RAW_POST_DATA 比起来,它给内存带来的压力较小,并且不需要任何特殊的 php.ini 设置。php://input 不能用于 enctype=”multipart/form-data”。
$GLOBALS [‘HTTP_RAW_POST_DATA’]
总是产生 $HTTP_RAW_POST_DATA 变量包含有原始的 POST 数据。此变量仅在碰到未识别 MIME 类型的数据时产生。$HTTP_RAW_POST_DATA 对于 enctype=”multipart/form-data” 表单数据不可用。
特别注意此方式在 php 版本低的时候,并且 php.ini 配置开启 always_populate_raw_post_data 值为 On 可以使用,php7 之后就废弃了。
总结一下
1、Coentent-Type 仅在取值为 application/x-www-data-urlencoded 和 multipart/form- data 两种情况下,PHP 才会将 http 请求数据包中相应的数据填入全局变量 $_POST
2、PHP 不能识别的 Content-Type 类型的时候,会将 http 请求包中相应的数据填入变量 $HTTP_RAW_POST_DATA
3、只有 Coentent-Type 不为 multipart/form-data 的时候,PHP 不会将 http 请求数据包中的相应数据填入 php: //input,否则其它情况都会。填入的长度,由 Coentent-Length 指定。
4、只有 Content-Type 为 application/x-www-data-urlencoded 时,php://input 数据才 跟 $_POST 数据相一致。
5、php://input 数据总是跟 $HTTP_RAW_POST_DATA 相同,但是 php://input 比 $HTTP_RAW_POST_DATA 更凑效,且不需要特殊设置 php.ini
6、PHP 会将 PATH 字段的 query_path 部分,填入全局变量 $_GET。通常情况下,GET 方法提交的 http 请求,body 为空。
总之:
1、如果是 application/x-www-form-urlencoded 和 multipart/form-data 格式 用 $_POST;
2、如果不能获取的时候比如 text/xml、application/json、soap,使用 file_get_contents (‘php://input’);
获取http请求原文
取得请求行:Method、URI、协议
可以从超级变量$_SERVER中获得,三个变量的值如下:
$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n";
取得所有Header
PHP有个内置函数getallheader(),是apache_request_headers()函数的一个别名,可以将HTTP请求的所有Header以数组形式返回。但这个函数只能工作在Apache下,如果换了Nginx或者命令行,会直接报函数不存在的错误。
比较通用的方法是,从超级变量$_SERVER中提取出来,有关Header的键值都是“HTTP_”开头的,可以根据此特点取得所有的Header。
具体代码如下:
function get_all_headers() {
$headers = array();
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$key = substr($key, 5);
$key = strtolower($key);
$key = str_replace('_', ' ', $key);
$key = ucwords($key);
$key = str_replace(' ', '-', $key);
$headers[$key] = $value;
}
}
return $headers;
}
取得Body
官方提供了一种获取请求Body的方法,即:
file_get_contents('php://input')
最终完整代码如下:
function get_http_raw() { $raw = '';// (1) 请求行 $raw .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n"; // (2) 请求Headers foreach($_SERVER as $key => $value) { if(substr($key, 0, 5) === 'HTTP_') { $key = substr($key, 5); $key = str_replace('_', '-', $key); $raw .= $key.': '.$value."\r\n"; } } // (3) 空行 $raw .= "\r\n"; // (4) 请求Body $raw .= file_get_contents('php://input'); return $raw; }