一、客户端socket
1. 创建socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0);
该函数返回socket描述符,三个参数分别是:
地址协议:AF_INET (这里是ipv4)
连接接类型:SOCK_STREAM(面向连接的TCP协议)
协议:0|IPPROTO_IP (IP协议)
【错误处理】
如果任何socket函数失败,可以使用 socket_last_errr 和 socket_strerror 函数检索错误信息
if(!($socket = socket_create(AF_INET,SOCK_STREAM,0))){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket");
}
【注意】
除了 SOCK_STREAM 类型的socket协议之外, 还有 SOCK_DGRAM 的类型 表示 UDP协议,因此需要知道远程服务器的 ip 地址
2. 连接到服务器
if(!socket_connect($socket,'104.193.88.77',80)){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("connect failed !");
}
到这里,我们运行
$ php socket.php
就能检测 ip 为104.193.88.77 端口号为 80 是否开启,由此可以构建一个端口扫描程序。
注意:这里连接必须是一个ip地址,有时候我们只知道域名,可以使用如下方式转换:
$ip_address = gethostbyname('www.baidu.com');
连接的概念适用于 SOCK_STREAM/TCP ,而 UDP,ICMP,ARP等其他基于非连接的通信没有连接的概念
3. 发送数据
函数 send 只会发送数据,它需要socket描述符,发送的数据及其大小。
$message = "GET / HTTP/1.1\r\n\r\n";
if(!socket_send($socket,$message,strlen($message),0)){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("send failed !");
}
该消息实际上是一个获取网站主页的http命令
【注意】
将数据发送到socket时,您基本上是将数据写入socket 类似于将数据写入文件,因此还可以使用 write 函数 将数据发送到socket。
4. 接收数据
使用 recv 函数接收套接字上的数据
if(socket_recv($socket,$buf,2045,MSG_WAITALL) === false){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("receive failed !");
}
echo $buf;
5. 关闭socket
socket_close($socket);
总结一下:客户端发起socket请求流程
》创建socket
》连接到远程服务器
》发送数据
》接收数据
》关闭socket
其实就是类似于打开浏览器访问www.baidu.com一样的整个流程
二、服务端socket
1. 创建一个服务端master socket
if(!($socket = socket_create(AF_INET,SOCK_STAREAM,0))){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("create socket failed ");
}
2. 绑定到地址(和端口)
函数 bind 可用于套接字绑定到特定的地址和端口,他需要一个类似 connect 函数的结构 服务socket、ip 和端口
if(!socket_bind($socket,'127.0.0.1',5001)){
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("bind socket failed ");
}
如果你想接收所有的 ip 可以将 ip 改为 0.0.0.0
3. 监听连接
socket_listen($socket, 10);
第二个参数是控制程序在已经很忙碌的时候保持等待的传入连接数,比如 已经有10个连接等待处理,第11个连接请求将会被拒绝
4. 接受连接,关闭连接
$client = socket_accept($socket);
if(socket_getpeername($client,$address,$port)){
echo "";
}
socket_close($client);
socket_close($socket);
$ php server.php
此时该程序正在等待端口5001上的传入连接我们新开一个窗口
$ telnet localhost 5001
【注意】
socket_getpeername 函数用于获取有关通过特定socket连接到服务器的客户端详细信息
我们建立了连接,并立即关闭了他,这并没有什么实际的用处,那我们回复客户端,我们可以使用socket_write 函数向传入的套接字
写入内容
5. 读取/发送数据
$input = socket_read($client,102400);
$response = "ok ... $input";
socket_write($client,$response);
socket_close($client);
在终端中运行
$ telnet localhost 5001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. h
Connection closed by foreign host
我们收到回复之后,连接被立即关闭了,而像百度这样的服务器总是一直在接收传入的链接,这意味着服务器应该一直在
运行,所以我们想让自己的服务器一直运行,最简单的方法是将 accept 置于一个循环中以便他能一直接收传入的连接
6. 在线服务器
$address = '0.0.0.0';
$port = '5001';
while(true){
$client = socket_accept($socket);
if(socket_getpeername($client,$address,$port)){
echo '';
}
$input = socket_read($client, 1024000);
$response = "OK .. $input";
socket_write($client, $response);
}
这里我们将 accpet 加入了循环,但是每个发起的客户端,仍然没有有效的通信,他无法一次处理多个连接
7. 多连接处理
为了处理每个连接,我们需要一个单独的处理代码来与主服务器接受连接一起运行。实现此目的的一种方法是使用线程。主服务器程序接受连接并创建一个新线程来处理连接的通信,然后服务器返回以接受更多连接。但是php不直接支持线程。
另一种方法是使用 select 函数。select 函数基本上'轮询'或观察一组套接字用于某些事件,比如它是否可读,可写或有问题等等。
因此 select 函数可用于监视多个客户端并检查哪个客户端发送了一条消息。
error_reporting(~E_NOTICE);
set_time_limit (0);
$address = "0.0.0.0";
$port = 5000;
$max_clients = 10;
//1. 创建连接
if(!($sock = socket_create(AF_INET, SOCK_STREAM, 0)))
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg \n");
}
echo "Socket created \n";
// 绑定连接
if( !socket_bind($sock, $address , 5000) )
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not bind socket : [$errorcode] $errormsg \n");
}
echo "Socket bind OK \n";
//监听
if(!socket_listen ($sock , 10))
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not listen on socket : [$errorcode] $errormsg \n");
}
echo "Socket listen OK \n";
echo "Waiting for incoming connections... \n";
//array of client sockets
$client_socks = array();
//array of sockets to read
$read = array();
//开始循环接收进来的连接和已经存在的连接
while (true)
{
//准备一个存储socket的数组
$read = array();
//第一个是主服务的 socket
$read[0] = $sock;
//然后添加存在的 client sockets
for ($i = 0; $i < $max_clients; $i++)
{
if($client_socks[$i] != null)
{
$read[$i+1] = $client_socks[$i];
}
}
//调用 select - 遇到错误 终止调用
if(socket_select($read , $write , $except , null) === false)
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not listen on socket : [$errorcode] $errormsg \n");
}
//如果包含了主服务的 socket, 那么就可以就收新的客户端socket连接
if (in_array($sock, $read))
{
for ($i = 0; $i < $max_clients; $i++)
{
if ($client_socks[$i] == null)
{
$client_socks[$i] = socket_accept($sock);//接受连接
//显示连接信息
if(socket_getpeername($client_socks[$i], $address, $port))
{
echo "Client $address : $port is now connected to us. \n";
}
//发送语句给客户端
$message = "Welcome to php socket server version 1.0 \n";
$message .= "Enter a message and press enter, and i shall reply back \n";
socket_write($client_socks[$i] , $message);
break;
}
}
}
//检查每个客户端是否有数据发送
for ($i = 0; $i < $max_clients; $i++)
{
if (in_array($client_socks[$i] , $read))
{
$input = socket_read($client_socks[$i] , 1024);//读取数据
if ($input == null)
{
//如果输入为空 那么意味着客户端失去连接 关闭客户端并移除
unset($client_socks[$i]);
socket_close($client_socks[$i]);
}
$n = trim($input);
$output = "OK ... $input";
echo "Sending output to client \n";
//发送回复信息给客户端
socket_write($client_socks[$i] , $output);
}
}
}
运行上述服务器并像以前一样打开3个终端。现在,服务器将为连接到它的每个客户端创建一个线程。
总结一下: 服务端创建一个socket服务需要如下几步
》1. 打开socket
》2. 绑定到地址(和端口)
》3. 监听传入的连接
》4. 接受数据
》5. 读取数据/发送数据
原文来自 :https://www.binarytides.com/php-socket-programming-tutorial/