如何使用PHP监听TCP端口?

14

我有一个GPS追踪器,通过GPRS连接连接并发送数据至特定的公共服务器:端口。

我可以定义GPS设备的ip:端口。

我的问题是,我是否可以在我的服务器上打开一个端口,并使用PHP监听/保存接收到的数据呢?

谢谢。


1
http://php.net/manual/en/function.socket-listen.php? - Madara's Ghost
GPS追踪器的品牌和型号是什么?大多数我知道的也支持HTTP GET。 - Baba
@hakre 我的限制更多是关于服务器,PHP能否在一个关闭端口的服务器上创建一个套接字? - gustyaquino
@hakre 我的意思是,我刚刚安装了一个新的服务器,唯一的服务就是Apache(80),而且一切都是默认设置。下一步该做什么?iptables? - gustyaquino
我认为你在Ubuntu服务器支持论坛中讨论你所拥有的各种问题会更容易入门(这是一个非常广泛的话题),这样你会获得更多成功。 - hakre
显示剩余3条评论
3个回答

16

编辑 / 更新于 2017 年 8 月 16 日: 用户和库作者 <@Navarr> 发布了新的更新版本的库,我的原始答案中的代码基于此。新代码链接在他的 github 页面上(点击此处)。您可以自由探索新代码,并参考原例子来获取更多信息(我没有亲自探索或使用新代码)。


下面的代码将使用在此处找到的 SocketServer.class.php 文件。它旨在作为独立进程运行,这意味着在 Linux 下,我必须使该文件可执行,然后使用“php my_server.php”从命令行运行它。 有关从命令行运行 php 脚本的更多信息,请访问: http://www.funphp.com/?p=33

首先,请在此处获取 SocketServer.class.php 文件: http://www.phpclasses.org/browse/file/31975.html

尝试使用它,然后根据需要调整以处理接收自己的传入数据。希望它能帮到您。

<?php

require_once("SocketServer.class.php"); // Include the File
$server = new SocketServer("192.168.1.6",31337); // Create a Server binding to the given ip address and listen to port 31337 for connections
$server->max_clients = 10; // Allow no more than 10 people to connect at a time
$server->hook("CONNECT","handle_connect"); // Run handle_connect every time someone connects
$server->hook("INPUT","handle_input"); // Run handle_input whenever text is sent to the server
$server->infinite_loop(); // Run Server Code Until Process is terminated.


function handle_connect(&$server,&$client,$input)
{
    SocketServer::socket_write_smart($client->socket,"String? ","");
}
function handle_input(&$server,&$client,$input)
{
    // You probably want to sanitize your inputs here
    $trim = trim($input); // Trim the input, Remove Line Endings and Extra Whitespace.

    if(strtolower($trim) == "quit") // User Wants to quit the server
    {
        SocketServer::socket_write_smart($client->socket,"Oh... Goodbye..."); // Give the user a sad goodbye message, meany!
        $server->disconnect($client->server_clients_index); // Disconnect this client.
        return; // Ends the function
    }

    $output = strrev($trim); // Reverse the String

    SocketServer::socket_write_smart($client->socket,$output); // Send the Client back the String
    SocketServer::socket_write_smart($client->socket,"String? ",""); // Request Another String
}

编辑:为了保持本答案的相关性和功能性,我认为最好不要继续依赖来自外部源代码的代码,因为该代码可能不会始终可用(或在我链接中提供的给定URL)。因此,为了方便起见,我在下面添加了与我在本帖子顶部链接到的SocketServer.class.php文件相对应的代码。(抱歉长度可能较长,并且复制/粘贴时可能缺少缩进/格式,我不是下面代码的作者)。

  <?php
  /*! @class SocketServer
   @author Navarr Barnier
   @abstract A Framework for creating a multi-client server using the PHP language.
   */
  class SocketServer
  {
    /*! @var config
     @abstract Array - an array of configuration information used by the server.
     */
    protected $config;

    /*! @var hooks
     @abstract Array - a dictionary of hooks and the callbacks attached to them.
     */
    protected $hooks;

    /*! @var master_socket
     @abstract resource - The master socket used by the server.
     */
    protected $master_socket;

    /*! @var max_clients
     @abstract unsigned int - The maximum number of clients allowed to connect.
     */
    public $max_clients = 10;

    /*! @var max_read
     @abstract unsigned int - The maximum number of bytes to read from a socket at a single time.
     */
    public $max_read = 1024;

    /*! @var clients
     @abstract Array - an array of connected clients.
     */
    public $clients;

    /*! @function __construct
     @abstract Creates the socket and starts listening to it.
     @param string - IP Address to bind to, NULL for default.
     @param int - Port to bind to
     @result void
     */
    public function __construct($bind_ip,$port)
  {
    set_time_limit(0);
    $this->hooks = array();

    $this->config["ip"] = $bind_ip;
    $this->config["port"] = $port;

    $this->master_socket = socket_create(AF_INET, SOCK_STREAM, 0);
    socket_bind($this->master_socket,$this->config["ip"],$this->config["port"]) or die("Issue Binding");
    socket_getsockname($this->master_socket,$bind_ip,$port);
    socket_listen($this->master_socket);
    SocketServer::debug("Listenting for connections on {$bind_ip}:{$port}");
  }

    /*! @function hook
     @abstract Adds a function to be called whenever a certain action happens. Can be extended in your implementation.
     @param string - Command
     @param callback- Function to Call.
     @see unhook
     @see trigger_hooks
     @result void
     */
    public function hook($command,$function)
  {
    $command = strtoupper($command);
    if(!isset($this->hooks[$command])) { $this->hooks[$command] = array(); }
    $k = array_search($function,$this->hooks[$command]);
    if($k === FALSE)
  {
    $this->hooks[$command][] = $function;
  }
  }

    /*! @function unhook
     @abstract Deletes a function from the call list for a certain action. Can be extended in your implementation.
     @param string - Command
     @param callback- Function to Delete from Call List
     @see hook
     @see trigger_hooks
     @result void
     */
    public function unhook($command = NULL,$function)
  {
    $command = strtoupper($command);
    if($command !== NULL)
  {
    $k = array_search($function,$this->hooks[$command]);
    if($k !== FALSE)
  {
    unset($this->hooks[$command][$k]);
  }
  } else {
    $k = array_search($this->user_funcs,$function);
    if($k !== FALSE)
  {
    unset($this->user_funcs[$k]);
  }
  }
  }

    /*! @function loop_once
     @abstract Runs the class's actions once.
     @discussion Should only be used if you want to run additional checks during server operation. Otherwise, use infinite_loop()
     @param void
     @see infinite_loop
     @result bool - True
     */
    public function loop_once()
  {
    // Setup Clients Listen Socket For Reading
    $read[0] = $this->master_socket;
    for($i = 0; $i < $this->max_clients; $i++)
  {
    if(isset($this->clients[$i]))
  {
    $read[$i + 1] = $this->clients[$i]->socket;
  }
  }

    // Set up a blocking call to socket_select
    if(socket_select($read,$write = NULL, $except = NULL, $tv_sec = 5) < 1)
  {
    // SocketServer::debug("Problem blocking socket_select?");
    return true;
  }

    // Handle new Connections
    if(in_array($this->master_socket, $read))
  {
    for($i = 0; $i < $this->max_clients; $i++)
  {
    if(empty($this->clients[$i]))
  {
    $temp_sock = $this->master_socket;
    $this->clients[$i] = new SocketServerClient($this->master_socket,$i);
    $this->trigger_hooks("CONNECT",$this->clients[$i],"");
    break;
  }
    elseif($i == ($this->max_clients-1))
  {
    SocketServer::debug("Too many clients... :( ");
  }
  }

  }

    // Handle Input
    for($i = 0; $i < $this->max_clients; $i++) // for each client
  {
    if(isset($this->clients[$i]))
  {
    if(in_array($this->clients[$i]->socket, $read))
  {
    $input = socket_read($this->clients[$i]->socket, $this->max_read);
    if($input == null)
  {
    $this->disconnect($i);
  }
    else
  {
    SocketServer::debug("{$i}@{$this->clients[$i]->ip} --> {$input}");
    $this->trigger_hooks("INPUT",$this->clients[$i],$input);
  }
  }
  }
  }
    return true;
  }

    /*! @function disconnect
     @abstract Disconnects a client from the server.
     @param int - Index of the client to disconnect.
     @param string - Message to send to the hooks
     @result void
     */
    public function disconnect($client_index,$message = "")
  {
    $i = $client_index;
    SocketServer::debug("Client {$i} from {$this->clients[$i]->ip} Disconnecting");
    $this->trigger_hooks("DISCONNECT",$this->clients[$i],$message);
    $this->clients[$i]->destroy();
    unset($this->clients[$i]);
  }

    /*! @function trigger_hooks
     @abstract Triggers Hooks for a certain command.
     @param string - Command who's hooks you want to trigger.
     @param object - The client who activated this command.
     @param string - The input from the client, or a message to be sent to the hooks.
     @result void
     */
    public function trigger_hooks($command,&$client,$input)
  {
    if(isset($this->hooks[$command]))
  {
    foreach($this->hooks[$command] as $function)
  {
    SocketServer::debug("Triggering Hook '{$function}' for '{$command}'");
    $continue = call_user_func($function,$this,$client,$input);
    if($continue === FALSE) { break; }
  }
  }
  }

    /*! @function infinite_loop
     @abstract Runs the server code until the server is shut down.
     @see loop_once
     @param void
     @result void
     */
    public function infinite_loop()
  {
    $test = true;
    do
  {
    $test = $this->loop_once();
  }
    while($test);
  }

    /*! @function debug
     @static
     @abstract Outputs Text directly.
     @discussion Yeah, should probably make a way to turn this off.
     @param string - Text to Output
     @result void
     */
    public static function debug($text)
  {
    echo("{$text}\r\n");
  }

    /*! @function socket_write_smart
     @static
     @abstract Writes data to the socket, including the length of the data, and ends it with a CRLF unless specified.
     @discussion It is perfectly valid for socket_write_smart to return zero which means no bytes have been written. Be sure to use the === operator to check for FALSE in case of an error.
     @param resource- Socket Instance
     @param string - Data to write to the socket.
     @param string - Data to end the line with. Specify a "" if you don't want a line end sent.
     @result mixed - Returns the number of bytes successfully written to the socket or FALSE on failure. The error code can be retrieved with socket_last_error(). This code may be passed to socket_strerror() to get a textual explanation of the error.
     */
    public static function socket_write_smart(&$sock,$string,$crlf = "\r\n")
  {
    SocketServer::debug("<-- {$string}");
    if($crlf) { $string = "{$string}{$crlf}"; }
    return socket_write($sock,$string,strlen($string));
  }

    /*! @function __get
     @abstract Magic Method used for allowing the reading of protected variables.
     @discussion You never need to use this method, simply calling $server->variable works because of this method's existence.
     @param string - Variable to retrieve
     @result mixed - Returns the reference to the variable called.
     */
    function &__get($name)
  {
    return $this->{$name};
  }
  }

  /*! @class SocketServerClient
   @author Navarr Barnier
   @abstract A Client Instance for use with SocketServer
   */
  class SocketServerClient
  {
    /*! @var socket
     @abstract resource - The client's socket resource, for sending and receiving data with.
     */
    protected $socket;

    /*! @var ip
     @abstract string - The client's IP address, as seen by the server.
     */
    protected $ip;

    /*! @var hostname
     @abstract string - The client's hostname, as seen by the server.
     @discussion This variable is only set after calling lookup_hostname, as hostname lookups can take up a decent amount of time.
     @see lookup_hostname
     */
    protected $hostname;

    /*! @var server_clients_index
     @abstract int - The index of this client in the SocketServer's client array.
     */
    protected $server_clients_index;

    /*! @function __construct
     @param resource- The resource of the socket the client is connecting by, generally the master socket.
     @param int - The Index in the Server's client array.
     @result void
     */
    public function __construct(&$socket,$i)
  {
    $this->server_clients_index = $i;
    $this->socket = socket_accept($socket) or die("Failed to Accept");
    SocketServer::debug("New Client Connected");
    socket_getpeername($this->socket,$ip);
    $this->ip = $ip;
  }

    /*! @function lookup_hostname
     @abstract Searches for the user's hostname and stores the result to hostname.
     @see hostname
     @param void
     @result string - The hostname on success or the IP address on failure.
     */
    public function lookup_hostname()
  {
    $this->hostname = gethostbyaddr($this->ip);
    return $this->hostname;
  }

    /*! @function destroy
     @abstract Closes the socket. Thats pretty much it.
     @param void
     @result void
     */
    public function destroy()
  {
    socket_close($this->socket);
  }

    function &__get($name)
  {
    return $this->{$name};
  }

    function __isset($name)
  {
    return isset($this->{$name});
  }
} 

这个脚本必须在同一台服务器上运行,对吗? - gustyaquino
太好了,我改变了端口号,现在它可以工作了。我将尝试将 GPS 重定向到我的地址。 - gustyaquino
在Linux上,您可以使用netstat --listen命令验证某个端口是否正在本地侦听,以防出现关于套接字已在使用中的错误。如果是这样,那么您需要删除该进程才能绑定到该端口。这次使用netstat -tulpn并查找绑定到该套接字的进程的PID,最后使用kill [pid]来删除该进程。现在您应该能够绑定到该套接字了。 - Brandon K
如果您能够重构代码,使用我在https://github.com/navarr/Sockets上的新库,我将不胜感激。SocketServer.class.php现在有些过时了 :) - Navarr
@BrandonK 谢谢 :) 如果我有一些空闲时间(可惜的是,不太可能),我宁愿提交对你的答案本身的编辑,因为它已经被接受并且正确(只是过时了) - Navarr
显示剩余4条评论

11

1
服务器有没有进行任何操作?打开了端口吗? - gustyaquino
是的,你可以在 socket_bind 调用中指定。 - hexist

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接