- <?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协议号码。
- $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);
- //获取client ip
- 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, $response); # 第二个参数是获取数据 要发送的信息
- $this->send($_sock, '在线');
- }
- }
- }
- }
- /**
- * 握手处理
- * @param $newClient socket
- * @return int 接收到的信息
- */
- 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的套接流写入信息
- }
- /**
- * 解析接收数据
- * @param $buffer
- * @return null|string
- */
- 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;
- }
- /**
- * 发送数据
- * @param $newClinet 新接入的socket
- * @param $msg 要发送的数据
- * @return int|string
- */
- 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;
- }
- /**
- * 关闭socket
- */
- public function close(){
- # socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流
- return socket_close($this->_sockets);
- }
- }
- $sock = new SocketService();
- $sock->run();
- <!DOCTYPE html>
- <html>
- <head>
- <title>Socket 测试</title>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no">
- <link href="https://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
html, body {
min-height: 100%; }
body {
margin: 0;
padding: 0;
width: 100%;
font-family: "Microsoft Yahei",sans-serif, Arial; }
.container {
text-align: center; }
.title {
font-size: 16px;
color: rgba(0, 0, 0, 0.3);
position: fixed;
line-height: 30px;
height: 30px;
left: 0px;
right: 0px;
background-color: white; }
.content {
background-color: #f1f1f1;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
margin-top: 30px; }
.content .show-area {
text-align: left;
padding-top: 8px;
padding-bottom: 168px; }
.content .show-area .message {
width: 70%;
padding: 5px;
word-wrap: break-word;
word-break: normal; }
.content .write-area {
position: fixed;
bottom: 0px;
right: 0px;
left: 0px;
background-color: #f1f1f1;
z-index: 10;
width: 100%;
height: 160px;
border-top: 1px solid #d8d8d8; }
.content .write-area .send {
position: relative;
top: -28px;
height: 28px;
border-top-left-radius: 55px;
border-top-right-radius: 55px; }
.content .write-area #name{
position: relative;
top: -20px;
line-height: 28px;
font-size: 13px; }
</style>
- </head>
- <body>
- <div class="container">
- <div class="title">Socket 测试长连接</div>
- <div class="content">
- <div class="show-area"></div>
- <div class="write-area">
- <div><button class="btn btn-default send" >发送</button></div>
- <div><input name="name" id="name" type="text" placeholder="input your name"></div>
- <div>
- <textarea name="message" id="message" cols="38" rows="4" placeholder="input your message..."></textarea>
- </div>
- </div>
- </div>
- </div>
-
- <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
- <script src="https://cdn.bootcss.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
- <script>
- var wsurl = 'ws://localhost:80/websocket/test2.php';
- var websocket;
- websocket = new WebSocket(wsurl);
- //连接建立
- websocket.onopen = function(evevt){
- console.log("Connected to WebSocket server.");
- $('.show-area').append('<p class="bg-info message"><i class="glyphicon glyphicon-info-sign"></i>Connected to WebSocket server!</p>');
- }
- //收到消息
- websocket.onmessage = function(event) {
- console.log(event);
- $('.show-area').append('<p class="bg-info message"><i class="glyphicon glyphicon-info-sign"></i>'+event.data+'</p>');
- }
- //发生错误
- websocket.onerror = function(event){
- console.log("Connected to WebSocket server error");
- $('.show-area').append('<p class="bg-danger message"><a name=""></a><i class="glyphicon glyphicon-info-sign"></i>Connect to WebSocket server error.</p>');
- }
- //连接关闭
- websocket.onclose = function(event){
- console.log('websocket Connection Closed. ');
- $('.show-area').append('<p class="bg-warning message"><a name=""></a><i class="glyphicon glyphicon-info-sign"></i>websocket Connection Closed.</p>');
- }
- // 发送信息
- function send(){
- var name = $('#name').val();
- var message = $('#message').val();
- if(!name){
- alert('请输入用户名!');
- return false;
- }
- if(!message){
- alert('发送消息不能为空!');
- return false;
- }
- var msg = {
- message: message,
- name: name
- };
- try{
- websocket.send(JSON.stringify(msg));
- } catch(ex) {
- console.log(ex);
- }
- }
- //点发送按钮发送消息
- $('.send').bind('click',function(){
- send();
- });
- </script>
- </body>
- </html>