<?php
set_time_limit(0);
class
SocketService
{
private
$address
=
'localhost'
;
private
$port
= 80;
private
$_sockets
;
public
function
__construct(
$address
=
''
,
$port
=
''
)
{
if
(!
empty
(
$address
)){
$this
->address =
$address
;
}
if
(!
empty
(
$port
)) {
$this
->port =
$port
;
}
}
public
function
service(){
$tcp
=
getprotobyname
(
"SOL_TCP"
); # 获取与协议名称关联的协议号
$sock
= socket_create(AF_INET, SOCK_STREAM,
$tcp
); # 创建一个套接字(通讯节点)
socket_set_option(
$sock
, SOL_SOCKET, SO_REUSEADDR, 1); # 设置套接字选项
if
(
$sock
< 0)
{
throw
new
Exception(
"failed to create socket: "
.socket_strerror(
$sock
).
"\n"
);
}
socket_bind(
$sock
,
$this
->address,
$this
->port); # 绑定
socket_listen(
$sock
,
$this
->port); # 监听套接字上的连接
$this
->_sockets =
$sock
;
}
public
function
run(){
$this
->service();
$clients
[] =
$this
->_sockets; # 数组存储 每个socket
# 让服务器无限获取客户端传过来的信息
while
(true){
$changes
=
$clients
;
$write
= NULL;
$except
= NULL;
socket_select(
$changes
,
$write
,
$except
, NULL);
foreach
(
$changes
as
$key
=>
$_sock
){
if
(
$this
->_sockets ==
$_sock
){ # 判断是不是新接入的socket
if
((
$newClient
= socket_accept(
$_sock
)) === false){ # 接受新的套接字上的连接 socket_accept的作用就是接受socket_bind()所绑定的主机发过来的套接流
die
(
'failed to accept socket: '
.socket_strerror(
$_sock
).
"\n"
); # 返回描述套接字错误的字符串
}
$line
= trim(socket_read(
$newClient
, 1024)); # 读取客户端传过来的资源,并转化为字符串 socket_read的作用就是读出socket_accept()的资源并把它转化为字符串
$this
->handshaking(
$newClient
,
$line
);
socket_getpeername (
$newClient
,
$ip
); # 查询给定套接字的远程端,这可能导致主机/端口或UNIX文件系统路径,具体取决于其类型。
$clients
[
$ip
] =
$newClient
;
}
else
{
# 读取该socket的信息,注意:第二个参数是引用传参即接收数据,第三个参数是接收数据的长度
$lenght
= socket_recv(
$_sock
,
$buffer
, 2048, 0); # 从已连接的socket接收数据
$lenght
接收到字符串长度
$msg
=
$this
->message(
$buffer
); # 接收到的信息
fwrite(STDOUT,
'Please input a argument:'
);
$response
= trim(
fgets
(STDIN));
$this
->send(
$_sock
,
'在线'
);
}
}
}
}
public
function
handshaking(
$newClient
,
$line
){
$headers
=
array
();
$lines
= preg_split(
"/\r\n/"
,
$line
); # 通过一个正则表达式分隔字符串。
foreach
(
$lines
as
$line
)
{
$line
=
chop
(
$line
); # 移除字符串右端的空白字符或其他预定义字符
if
(preg_match(
'/\A(\S+): (.*)\z/'
,
$line
,
$matches
))
{
$headers
[
$matches
[1]] =
$matches
[2];
}
}
$secKey
=
$headers
[
'Sec-WebSocket-Key'
];
$secAccept
=
base64_encode
(pack(
'H*'
, sha1(
$secKey
.
'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
)));
$upgrade
=
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
.
"Upgrade: websocket\r\n"
.
"Connection: Upgrade\r\n"
.
"WebSocket-Origin: $this->address\r\n"
.
"WebSocket-Location: ws://$this->address:$this->port/服务器地址\r\n"
.
"Sec-WebSocket-Accept:$secAccept\r\n\r\n"
;
return
socket_write(
$newClient
,
$upgrade
,
strlen
(
$upgrade
)); # socket_write的作用是向socket_create的套接流写入信息,或者向socket_accept的套接流写入信息
}
public
function
message(
$buffer
){
$len
=
$masks
=
$data
=
$decoded
= null;
$len
= ord(
$buffer
[1]) & 127;
if
(
$len
=== 126) {
$masks
=
substr
(
$buffer
, 4, 4);
$data
=
substr
(
$buffer
, 8);
}
else
if
(
$len
=== 127) {
$masks
=
substr
(
$buffer
, 10, 4);
$data
=
substr
(
$buffer
, 14);
}
else
{
$masks
=
substr
(
$buffer
, 2, 4);
$data
=
substr
(
$buffer
, 6);
}
for
(
$index
= 0;
$index
<
strlen
(
$data
);
$index
++) {
$decoded
.=
$data
[
$index
] ^
$masks
[
$index
% 4];
}
return
$decoded
;
}
public
function
send(
$newClinet
,
$msg
){
$msg
=
$this
->frame(
$msg
);
socket_write(
$newClinet
,
$msg
,
strlen
(
$msg
)); # 写入套接字
}
public
function
frame(
$s
) {
$a
=
str_split
(
$s
, 125); # 把字符串分割到数组中 第二个长度参数
if
(
count
(
$a
) == 1) {
return
"\x81"
.
chr
(
strlen
(
$a
[0])) .
$a
[0];
}
$ns
=
""
;
foreach
(
$a
as
$o
) {
$ns
.=
"\x81"
.
chr
(
strlen
(
$o
)) .
$o
;
}
return
$ns
;
}
public
function
close(){
# socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流
return
socket_close(
$this
->_sockets);
}
}
$sock
=
new
SocketService();
$sock
->run();