php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络服务的客户端和服务端,这和mysql的客户端和服务端是一样的,你只要理解mysql的客户端和服务端是怎么一回事,你就应该能够理解下面我要讲的东西吧。

关于socket编程所涉及到的网络协议,什么TCP啊,UDP啊,什么socket三次握手等等,这些网络协议网上有很详细的解释,这里不讲,只截个socket建立套接的过程图让你瞧瞧:

socket是怎么建立连接的呢?上面已经提到过了,它建立连接的过程是与mysql的客户端和服务端的连接本质是一样的。而它与mysql不同的是,mysql的服务端和客户端都已经为我们编辑好了,我们只要应用就行了。但是,关键时刻来啦,socket它什么东西都没有提供给我们,唯一提供给我们的就是:几十个socket函数。

参考Socket根据官方文档:http://php.net/manual/zh/book.sockets.php

PHP Socket 编程之8个主要函数分别是:

  • socket_create — 创建一个套接字(通讯节点)作用:创建一个socket套接字,说白了,就是一个网络数据流。返回值:一个套接字,或者是false,参数错误发出E_WARNING警告socket_create创建并返回一个套接字,也称作一个通讯节点。一个典型的网络连接由2个套接字构成,一个运行在客户端,另一个运行在服务器端。
  • socket_bind — 给套接字绑定名字
  • socket_connect — 开启一个套接字连接 返回true 或false
  • socket_listen —监听一个套接字,返回值为true或者false
  • socket_accept — 接收套接字的资源信息,成功返回套接字的信息资源,失败为false
  • socket_read — 作用:读取套接字的资源信息,返回值:成功把套接字的资源转化为字符串信息,失败为false
  • socket_send — 发送数据
  • socket_write — 作用:把数据写入套接字中  返回值:成功返回字符串的字节长度,失败为false
  • socket_close — 关闭套接字资源

 一、服务端php代码:server.php

 <?php
/*
* AF_INET IPv4 网络协议。TCP 和 UDP 都可使用此协议。
* SOCK_STREAM 提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
* tcp Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
* TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
* TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
* 因此,TCP 应用程序必须允许传送部分报文的可能。
* */
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
/*绑定接收的套接流主机和端口,与客户端相对应*/
if(socket_bind($socket,'localhost',8888) == false){
echo 'server bind faild:'.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());
}
//让服务器无限获取客户端传过来的信息
do{
/*接收客户端传过来的信息*/
$accept_resource = socket_accept($socket);
/*socket_accept的作用就是接受socket_bind()所绑定的主机发过来的套接流*/ if($accept_resource !== false){ socket_getpeername($accept_resource, $addr, $port); //获取连接过来的客户端ip地址和端口
echo "client connect server: IP=$addr, Port=$port " . PHP_EOL; while (1)
{
/*读取客户端传过来的资源,并转化为字符串*/
$string = socket_read($accept_resource,1024);
/*socket_read的作用就是读出socket_accept()的资源并把它转化为字符串*/
echo 'read data from client:'.$string.PHP_EOL;//PHP_EOL为php的换行预定义常量
if($string != false){
//回写给客户端
$data = 'server receive data is: '.$string;
$data = strtoupper($data); //小写转大写
/*向socket_accept的套接流写入信息,也就是回馈信息给socket_bind()所绑定的主机客户端*/
//socket_write($accept_resource,$data,strlen($data));
socket_write($accept_resource,$data);
/*socket_write的作用是向socket_create的套接流写入信息,或者向socket_accept的套接流写入信息*/
}else{
//客户端关闭
socket_close($accept_resource);
echo "client close" . PHP_EOL;
break;
}
}
/*socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流*/
// socket_close($accept_resource);
} }while(true); socket_close($socket);
?>

开始启动服务:

我们用telnet来测试:

但其实这个TCP服务器是有问题的,它一次只能处理一个客户端的连接和数据传输,这是因为一个客户端连接过来后,进程就去负责读写客户端数据,当客户端没有传输数据时,tcp服务器处于阻塞读状态,无法再去处理其他客户端的连接请求了。

1)解决这个问题的一种办法就是采用多进程服务器,每当一个客户端连接过来,服务器开一个子进程专门负责和该客户端的数据传输,

而父进程仍然监听客户端的连接,但是起进程的代价是昂贵的,这种多进程的机制显然支撑不了高并发。

2)另一个解决办法是使用IO多路复用机制,使用php为我们提供的socket_select方法,它可以监听多个socket,

如果其中某个socket状态发生了改变,比如从不可写变为可写,从不可读变为可读,这个方法就会返回,从而我们就可以去处理这个socket,处理客户端的连接,读写操作等等。

来看php文档中对该socket_select的介绍

socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout

该函数定义如下:

int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int$tv_usec = 0 ] )

大致翻译下:

socket_select  ---  在给定的几组sockets数组上执行 select() 系统调用,用一个特定的超时时间。

socket_select() 接受几组sockets数组作为参数,并监听它们改变状态

这些基于BSD scokets 能够识别这些socket资源数组实际上就是文件描述符集合。

三个不同的socket资源数组会被同时监听。

这三个资源数组不是必传的, 你可以用一个空数组或者NULL作为参数,不要忘记这三个数组是以引用的方式传递的,在函数返回后,这些数组的值会被改变。

socket_select() 调用成功返回这三个数组中状态改变的socket总数,如果设置了timeout,并且在timeout之内都没有状态改变,这个函数将返回0,出错时返回FALSE,

可以用socket_last_error() 获取错误码。

二、使用 socket_select() 优化之前 phptcpserver.php 代码:

 <?php
/*
* AF_INET IPv4 网络协议。TCP 和 UDP 都可使用此协议。
* SOCK_STREAM 提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
* tcp Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
* TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
* TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
* 因此,TCP 应用程序必须允许传送部分报文的可能。
* */
$servsock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
/*绑定接收的套接流主机和端口,与客户端相对应*/
if(socket_bind($servsock,'localhost',8888) == false){
echo 'server bind faild:'.socket_strerror(socket_last_error());
/*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/
}
//监听套接流 允许多少个客户端来排队连接
if(socket_listen($servsock,4)==false){
echo 'server listen fail:'.socket_strerror(socket_last_error());
} /* 要监听的三个sockets数组 */
$read_socks = array();
$write_socks = array();
$except_socks = NULL; // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量 $read_socks[] = $servsock;
//让服务器无限获取客户端传过来的信息
do{ /* 这两个数组会被改变,所以用两个临时变量 */
$tmp_reads = $read_socks;
$tmp_writes = $write_socks;
// int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
$count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL); // timeout 传 NULL 会一直阻塞直到有结果返回 foreach ($tmp_reads as $read)
{
if ($read == $servsock)
{
/* 有新的客户端连接请求 */
$connsock = socket_accept($servsock); //响应客户端连接, 此时不会造成阻塞
if ($connsock)
{
socket_getpeername($connsock, $addr, $port); //获取远程客户端ip地址和端口
echo "client connect server: ip = $addr, port = $port" . PHP_EOL; // 把新的连接sokcet加入监听
$read_socks[] = $connsock;
$write_socks[] = $connsock;
}
}else{
/* 客户端传输数据 */
$data = socket_read($read, 1024); //从客户端读取数据, 此时一定会读到数组而不会产生阻塞
if($data === ''){
//移除对该 socket 监听
foreach ($read_socks as $key => $val)
{
if ($val == $read) unset($read_socks[$key]);
}
//移除回写 socket 监听
foreach ($write_socks as $key => $val)
{
if ($val == $read) unset($write_socks[$key]);
}
socket_close($read);
echo "client close" . PHP_EOL;
}else{
//如果data不为空 返回给对方客户端
socket_getpeername($read, $addr, $port); //获取远程客户端ip地址和端口
echo "read from client # $addr:$port # " . $data; $data = strtoupper($data); //小写转大写
$data = "from server:".$data;
if (in_array($read, $tmp_writes))
{
//如果该客户端可写 把数据回写给客户端
socket_write($read, $data);
}
}
}
} }while(true);
//最后关闭连接
socket_close($socket);
?>

现在,这个TCP服务器就可以支持多个客户端同时连接了!分别启用2个 telnet 来测试

测试服务输出如下:

三、稍微修改上面的服务器返回,返回一个HTTP响应头和一个简单的HTTP响应体,这样就摇身一变成了一个最简单的HTTP服务器

 <?php
/*
* AF_INET IPv4 网络协议。TCP 和 UDP 都可使用此协议。
* SOCK_STREAM 提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
* tcp Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
* TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
* TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
* 因此,TCP 应用程序必须允许传送部分报文的可能。
* */
$servsock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
/*绑定接收的套接流主机和端口,与客户端相对应*/
if(socket_bind($servsock,'localhost',8888) == false){
echo 'server bind faild:'.socket_strerror(socket_last_error());
/*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/
}
//监听套接流 允许多少个客户端来排队连接
if(socket_listen($servsock,4)==false){
echo 'server listen fail:'.socket_strerror(socket_last_error());
} /* 要监听的三个sockets数组 */
$read_socks = array();
$write_socks = array();
$except_socks = NULL; // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量 $read_socks[] = $servsock;
//让服务器无限获取客户端传过来的信息
do{ /* 这两个数组会被改变,所以用两个临时变量 */
$tmp_reads = $read_socks;
$tmp_writes = $write_socks;
// int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
$count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL); // timeout 传 NULL 会一直阻塞直到有结果返回 foreach ($tmp_reads as $read)
{
if ($read == $servsock)
{
/* 有新的客户端连接请求 */
$connsock = socket_accept($servsock); //响应客户端连接, 此时不会造成阻塞
if ($connsock)
{
socket_getpeername($connsock, $addr, $port); //获取远程客户端ip地址和端口
echo "client connect server: ip = $addr, port = $port" . PHP_EOL; // 把新的连接sokcet加入监听
$read_socks[] = $connsock;
$write_socks[] = $connsock;
}
}else{
/* 客户端传输数据 */
$data = socket_read($read, 1024); //从客户端读取数据, 此时一定会读到数组而不会产生阻塞
if($data === ''){
//移除对该 socket 监听
foreach ($read_socks as $key => $val)
{
if ($val == $read) unset($read_socks[$key]);
}
//移除回写 socket 监听
foreach ($write_socks as $key => $val)
{
if ($val == $read) unset($write_socks[$key]);
}
socket_close($read);
echo "client close" . PHP_EOL;
}else{
//如果data不为空 返回给对方客户端
socket_getpeername($read, $addr, $port); //获取远程客户端ip地址和端口
echo "read from client # $addr:$port # " . $data; $data = strtoupper($data); //小写转大写
$data = "from server:".$data;
$len = strlen($data)+1;
//增加http响应头 和响应体
$response = "HTTP/1.1 200 OK\r\n";
$response .= "Server: DaoKrHttpServer\r\n";
$response .= "Content-Type: text/html\r\n";
$response .= "Content-Length: $len\r\n\r\n";
$response .= "$data\n"; if (in_array($read, $tmp_writes))
{
//如果该客户端可写 把数据回写给客户端
//socket_write($read, $data);
//如果该客户端可写 把数据回写给客户端
socket_write($read, $response);
socket_close($read); // 主动关闭客户端连接
//关闭后删掉当前的读取监听
foreach ($read_socks as $key => $val)
{
if ($val == $read) unset($read_socks[$key]);
}
//关闭后删掉当前的回写监听
foreach ($write_socks as $key => $val)
{
if ($val == $read) unset($write_socks[$key]);
}
}
}
}
} }while(true);
//最后关闭连接
socket_close($socket);
?>

重新启动该服务器,用curl模拟请求该http服务器:

 root@DK:/home/daokr# curl 127.0.0.1:
from server:GET / HTTP/1.1
HOST: 127.0.0.1:
USER-AGENT: CURL/7.47.
ACCEPT: */*

服务端返回:

4)使用apache 的ab命令来压力测试下我们的程序;如果没有安装请安装

  nginx压力测试方法:
#ab命令
#安装ab
#Centos系统
yum install apr-util
#Ubuntu系统
sudo apt-get install apache2-utils

执行命令:

ab -c  -n  http://127.0.0.1:8888/

输出结果:

root@DK:/home/daokr# ab -n 100 -c 10 http://127.0.0.1:8888/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done

Server Software: DaoKrHttpServer
Server Hostname: 127.0.0.1
Server Port: 8888

Document Path: /
Document Length: 95 bytes

Concurrency Level: 10
Time taken for tests: 0.212 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 18400 bytes
HTML transferred: 9500 bytes
Requests per second: 471.32 [#/sec] (mean)
Time per request: 21.217 [ms] (mean)
Time per request: 2.122 [ms] (mean, across all concurrent requests)
Transfer rate: 84.69 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 7
Processing: 0 6 28.6 1 205
Waiting: 0 6 28.5 1 204
Total: 0 6 28.6 1 205

Percentage of the requests served within a certain time (ms)
50% 1
66% 2
75% 2
80% 2
90% 4
95% 6
98% 204
99% 205
100% 205 (longest request)
root@DK:/home/daokr#

5)另一个压力测试工具

debian和ubuntu用户可以通过apt-get install siege来安装siege.

siege是一个跟ab.exe相似的http压力测试软件。

我们可以用siege来测试我们的网站和服务器性能。

siege -r 100 -c 10 http://www.domain.com/test.php

-r 是 repeat , -r 100是重复100次测试

-c 10是表示模拟10个用户同时并发连接

最后面是要测试的URL地址。

测试过程中可以随时按CTRL+C中止进程,siege会生成一个报告给我们。

我们可以同时根据siege的测试结果和监视服务器的负载情况,对系统压力状况进行一个深入了解和分析。接下来可以帮助我们判断该如何进行下一步操作,是继续优化配置,还是升级硬件。

6)客户端测试代码 client.php

 <?php
//创建一个socket套接流
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
/****************设置socket连接选项,这两个步骤你可以省略*************/
//接收套接流的最大超时时间1秒,后面是微秒单位超时时间,设置为零,表示不管它
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 10, "usec" => 0));
//发送套接流的最大超时时间为6秒
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 10, "usec" => 0));
/****************设置socket连接选项,这两个步骤你可以省略*************/ //连接服务端的套接流,这一步就是使客户端与服务器端的套接流建立联系
if(socket_connect($socket,'localhost',8888) == false){
echo 'connect fail massege:'.socket_strerror(socket_last_error());
}else{
//像服务器写数据
$message = 'l love you my socket';
//转为GBK编码,处理乱码问题,这要看你的编码情况而定,每个人的编码都不同
$message = mb_convert_encoding($message,'GBK','UTF-8');
//向服务端写入字符串信息 if(socket_write($socket,$message,strlen($message)) == false){
echo 'fail to write'.socket_strerror(socket_last_error());
}else{
echo 'client write data success'.PHP_EOL;
//读取服务端返回来的套接流信息
while($callback = socket_read($socket, 1024)){
echo '服务器响应返回:'.PHP_EOL.$callback;
}
}
}
socket_close($socket);//工作完毕,关闭套接流 ?>

PHP Socket 编程之9个主要函数的使用之测试案例的更多相关文章

  1. 详述socket编程之select()和poll()函数

    转自:http://www.cppblog.com/myjfm/archive/2011/10/26/159093.aspx select()函数和poll()函数均是主要用来处理多路I/O复用的情况 ...

  2. iPhone socket 编程之BSD Socket篇

    iPhone socket 编程之BSD Socket篇 收藏在进行iPhone网络通讯程序的开发中,不可避免的要利用Socket套接字.iPhone提供了Socket网络编程的接口CFSocket, ...

  3. linux网络编程之shutdown() 与 close()函数详解

    linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...

  4. [深入浅出WP8.1(Runtime)]Socket编程之UDP协议

    13.3 Socket编程之UDP协议 UDP协议和TCP协议都是Socket编程的协议,但是与TCP协议不同,UDP协议并不提供超时重传,出错重传等功能,也就是说其是不可靠的协议.UDP适用于一次只 ...

  5. 03 shell编程之case语句与函数

    本文所有内容均来自当年博主当年学习笔记,若有不足欢迎指正 Shell编程之case语句与函数 学习目标: 掌握case语句编程 掌握shell函数的使用 目录结构: Case语句 Case语句的作用 ...

  6. 老雷socket编程之websocket实现

    老雷socket编程之websocket实现 我们主要实现私聊和群聊两个功能,要在web端实现想微信QQ那样的即时通讯的功能,我们需要了解一下websocket.websocket是一种可以双向通讯的 ...

  7. 老雷socket编程之PHP利用socket扩展实现聊天服务

    老雷socket编程之PHP利用socket扩展实现聊天服务 socket聊天服务原理 PHP有两个socket的扩展 sockets和streamssockets socket_create(AF_ ...

  8. linux socket编程之TCP与UDP

    转:http://blog.csdn.net/gaoxin1076/article/details/7262482 TCP/IP协议叫做传输控制/网际协议,又叫网络通信协议 TCP/IP虽然叫传输控制 ...

  9. socket编程之accept()函数【转载】

    名称 accept() 接收一个套接字中已建立的连接 使用格式 #include <sys/types.h> #include <sys/socket.h> int accep ...

随机推荐

  1. Spring MVC拦截器完整代码示例

     拦截器的作用: 编写一个自定义的类,实现相关拦截器接口:  preHandler不放行,直接return false:直接跳转到错误页面error.jsp postHandler后置处理器,也就是C ...

  2. COCOeval接口使用

    COCOeval类简介 class COCOeval: # Interface for evaluating detection on the Microsoft COCO dataset. # # ...

  3. java之hibernate之crud

    这篇文章主要讲解: 1>.对Hibernate使用的一些简单封装: · 2>.在单元测试中,使用Hibernate的封装的工具进行增删改查的测试 1.目录结构展示 2.代码展示 2.0 配 ...

  4. web API .net - .net core 对比学习-使用Swagger

    根据前两篇的介绍,我们知道.net web api 和 .net core web api在配置方面的不同如下: 1. .net web api的配置是在 App_Stat文件夹里面添加对应的配置类, ...

  5. C# vb .net实现对比度调整特效滤镜效果

    在.net中,如何简单快捷地实现Photoshop滤镜组中的对比度效果呢?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码: 设置授权 第一 ...

  6. semaphore demo 并行 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    import 'dart:async'; import 'package:semaphore/semaphore.dart'; import 'dart:io'; import 'dart:conve ...

  7. BUAA OO 2019 第三单元作业总结

    目录 总 JML规格化设计 理论基础 工具链 规格验证 验证代码 代码静态检查 自动生成测试样例 生成结果 错误分析 作业设计 第九次作业 架构 代码实现 第十次作业 架构 代码实现 第十一次作业 架 ...

  8. “http”和“https”的区别是什么?优缺点是什么?

    1. http 的URL 以http:// 开头,https以https:// 开头. 2. http 标准端口是80 ,https是443. 3.https 协议需要到ca申请证书,http不需要. ...

  9. 采集15个代理IP网站,打造免费代理IP池

    采集的站点: 免费代理IP http://ip.yqie.com/ipproxy.htm66免费代理网 http://www.66ip.cn/89免费代理 http://www.89ip.cn/无忧代 ...

  10. 安卓MediaPlayer框架之Binder机制

    Binder简介 Binder是Android系统进程间通信的主要方式之一. 1.在ASOP中,Binder使用传统的C/S通信方式:即一个进程作为服务端提供诸如视音频解封装,解码渲染,地址查询等各种 ...