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;
    }

 

 

posted @ 2019-05-28 17:05  小匡程序员  阅读(1003)  评论(0编辑  收藏  举报