您的位置:365bet体育备用网址器 > 应用 > 上一篇 我们学习了简单的Python TCP Socket,您可能

上一篇 我们学习了简单的Python TCP Socket,您可能

2019-08-13 14:51
/**
* patServer
* PHP socket server base class
* Events that can be handled:
*  * onStart
*  * onConnect
*  * onConnectionRefused
*  * onClose
*  * onShutdown
*  * onReceiveData
*
* @version 1.1
* @author  Stephan Schmidt <schst@php-tools.de>
* @package patServer
*/
class patServer {
/**
* information about the project
* @var array $systemVars
*/
var $systemVars  = array(
  "appName"  => "patServer",
  "appVersion"  => "1.1",
  "author"  => array("Stephan Schmidt <schst@php-tools.de>", )
  );

/**
* port to listen
* @var integer  $port
*/
  var $port  = 10000;

/**
* domain to bind to
* @var string $domain
*/
  var $domain  = "localhost";

/**
* maximum amount of clients
* @var integer $maxClients
*/
  var $maxClients = -1;

/**
* buffer size for socket_read
* @var integer $readBufferSize
*/
  var $readBufferSize  = 128;

/**
* end character for socket_read
* @var integer $readEndCharacter
*/
  var $readEndCharacter = "n";

/**
* maximum of backlog in queue
* @var integer $maxQueue
*/
  var $maxQueue = 500;

/**
* debug mode
* @var boolean $debug
*/
  var $debug  = true;

/**
* debug mode
* @var string $debugMode
*/
  var $debugMode = "text";

/**
* debug destination (filename or stdout)
* @var string $debugDest
*/
  var $debugDest = "stdout";

/**
* empty array, used for socket_select
* @var array $null
*/
  var $null  = array();

/**
* all file descriptors are stored here
* @var array $clientFD
*/
  var $clientFD = array();

/**
* needed to store client information
* @var array $clientInfo
*/
  var $clientInfo = array();

/**
* needed to store server information
* @var array $serverInfo
*/
  var $serverInfo = array();

/**
* amount of clients
* @var integer  $clients
*/
  var $clients = 0;

/**
* create a new socket server
*
* @access public
* @param string  $domain  domain to bind to
* @param integer  $port  port to listen to
*/
function patServer( $domain = "localhost", $port = 10000 )
{
  $this->domain = $domain;
  $this->port  = $port;

  $this->serverInfo["domain"]     = $domain;
  $this->serverInfo["port"]     = $port;
  $this->serverInfo["servername"]   = $this->systemVars["appName"];
  $this->serverInfo["serverversion"] = $this->systemVars["appVersion"];

  set_time_limit( 0 );
}

/**
* set maximum amount of simultaneous connections
*
* @access public
* @param int $maxClients
*/
function setMaxClients( $maxClients )
{
  $this->maxClients = $maxClients;
}

/**
* set debug mode
*
* @access public
* @param mixed $debug [text|htmlfalse]
* @param string $dest destination of debug message (stdout to output or filename if log should be written)
*/
function setDebugMode( $debug, $dest = "stdout" )
{
  if( $debug === false )
  {
  $this->debug = false;
  return true;
  }

  $this->debug  = true;
  $this->debugMode = $debug;
  $this->debugDest = $dest;
}

/**
* start the server
*
* @access public
* @param int $maxClients
*/
function start()
{
  $this->initFD = @socket_create( AF_INET, SOCK_STREAM, 0 );
  if( !$this->initFD )
  die( "patServer: Could not create socket." );

  // adress may be reused
  socket_setopt( $this->initFD, SOL_SOCKET, SO_REUSEADDR, 1 );

  // bind the socket
  if( !@socket_bind( $this->initFD, $this->domain, $this->port ) )
  {
  @socket_close( $this->initFD );
  die( "patServer: Could not bind socket to ".$this->domain." on port ".$this->port." ( ".$this->getLastSocketError( $this->initFd )." )." );
  }

  // listen on selected port
  if( !@socket_listen( $this->initFD, $this->maxQueue ) )
  die( "patServer: Could not listen ( ".$this->getLastSocketError( $this->initFd )." )." );

  $this->sendDebugMessage( "Listening on port ".$this->port.". Server started at ".date( "H:i:s", time() ) );

  // this allows the shutdown function to check whether the server is already shut down
  $GLOBALS["_patServerStatus"] = "running";
  // this ensures that the server will be sutdown correctly
  register_shutdown_function( array( $this, "shutdown" ) );

  if( method_exists( $this, "onStart" ) )
  $this->onStart();

  $this->serverInfo["started"] = time();
  $this->serverInfo["status"]  = "running";

  while( true )
  {
  $readFDs = array();
  array_push( $readFDs, $this->initFD );

  // fetch all clients that are awaiting connections
  for( $i = 0; $i < count( $this->clientFD ); $i   )
   if( isset( $this->clientFD[$i] ) )
   array_push( $readFDs, $this->clientFD[$i] );

  // block and wait for data or new connection
  $ready = @socket_select( $readFDs, $this->null, $this->null, NULL );

  if( $ready === false )
  {
   $this->sendDebugMessage( "socket_select failed." );
   $this->shutdown();
  }

  // check for new connection
  if( in_array( $this->initFD, $readFDs ) )
  {
   $newClient = $this->acceptConnection( $this->initFD );

   // check for maximum amount of connections
   if( $this->maxClients > 0 )
   {
   if( $this->clients > $this->maxClients )
   {
    $this->sendDebugMessage( "Too many connections." );

    if( method_exists( $this, "onConnectionRefused" ) )
    $this->onConnectionRefused( $newClient );

    $this->closeConnection( $newClient );
   }
   }

   if( --$ready <= 0 )
   continue;
  }

  // check all clients for incoming data
  for( $i = 0; $i < count( $this->clientFD ); $i   )
  {
   if( !isset( $this->clientFD[$i] ) )
   continue;

   if( in_array( $this->clientFD[$i], $readFDs ) )
   {
   $data = $this->readFromSocket( $i );

   // empty data => connection was closed
   if( !$data )
   {
    $this->sendDebugMessage( "Connection closed by peer" );
    $this->closeConnection( $i );
   }
   else
   {
    $this->sendDebugMessage( "Received ".trim( $data )." from ".$i );

    if( method_exists( $this, "onReceiveData" ) )
    $this->onReceiveData( $i, $data );
   }
   }
  }
  }
}

/**
* read from a socket
*
* @access private
* @param integer $clientId internal id of the client to read from
* @return string $data  data that was read
*/
function readFromSocket( $clientId )
{
  // start with empty string
  $data  = "";

  // read data from socket
  while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) )
  {
  $data .= $buf;

  $endString = substr( $buf, - strlen( $this->readEndCharacter ) );
  if( $endString == $this->readEndCharacter )
   break;
  if( $buf == NULL )
   break;
  }

  if( $buf === false )
  $this->sendDebugMessage( "Could not read from client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );

  return $data;
}

/**
* accept a new connection
*
* @access public
* @param resource &$socket socket that received the new connection
* @return int  $clientID internal ID of the client
*/
function acceptConnection( &$socket )
{
  for( $i = 0 ; $i <= count( $this->clientFD ); $i   )
  {
  if( !isset( $this->clientFD[$i] ) || $this->clientFD[$i] == NULL )
  {
   $this->clientFD[$i] = socket_accept( $socket );
   socket_setopt( $this->clientFD[$i], SOL_SOCKET, SO_REUSEADDR, 1 );
   $peer_host = "";
   $peer_port = "";
   socket_getpeername( $this->clientFD[$i], $peer_host, $peer_port );
   $this->clientInfo[$i] = array(
       "host"  => $peer_host,
       "port"  => $peer_port,
       "connectOn" => time()
       );
   $this->clients  ;

   $this->sendDebugMessage( "New connection ( ".$i." ) from ".$peer_host." on port ".$peer_port );

   if( method_exists( $this, "onConnect" ) )
   $this->onConnect( $i );
   return $i;
  }
  }
}

/**
* check, whether a client is still connected
*
* @access public
* @param integer $id client id
* @return boolean $connected true if client is connected, false otherwise
*/
function isConnected( $id )
{
  if( !isset( $this->clientFD[$id] ) )
  return false;
  return true;
}

/**
* close connection to a client
*
* @access public
* @param int $clientID internal ID of the client
*/
function closeConnection( $id )
{
  if( !isset( $this->clientFD[$id] ) )
  return false;

  if( method_exists( $this, "onClose" ) )
  $this->onClose( $id );

  $this->sendDebugMessage( "Closed connection ( ".$id." ) from ".$this->clientInfo[$id]["host"]." on port ".$this->clientInfo[$id]["port"] );

  @socket_close( $this->clientFD[$id] );
  $this->clientFD[$id] = NULL;
  unset( $this->clientInfo[$id] );
  $this->clients--;
}

/**
* shutdown server
*
* @access public
*/
function shutDown()
{
  if( $GLOBALS["_patServerStatus"] != "running" )
  exit;
  $GLOBALS["_patServerStatus"] = "stopped";

  if( method_exists( $this, "onShutdown" ) )
  $this->onShutdown();

  $maxFD = count( $this->clientFD );
  for( $i = 0; $i < $maxFD; $i   )
  $this->closeConnection( $i );

  @socket_close( $this->initFD );

  $this->sendDebugMessage( "Shutdown server." );
  exit;
}

/**
* get current amount of clients
*
* @access public
* @return int $clients amount of clients
*/
function getClients()
{
  return $this->clients;
}

/**
* send data to a client
*
* @access public
* @param int  $clientId ID of the client
* @param string $data  data to send
* @param boolean $debugData flag to indicate whether data that is written to socket should also be sent as debug message
*/
function sendData( $clientId, $data, $debugData = true )
{
  if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
  return false;

  if( $debugData )
  $this->sendDebugMessage( "sending: "" . $data . "" to: $clientId" );

  if( !@socket_write( $this->clientFD[$clientId], $data ) )
  $this->sendDebugMessage( "Could not write '".$data."' client ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
}

/**
* send data to all clients
*
* @access public
* @param string $data  data to send
* @param array $exclude client ids to exclude
*/
function broadcastData( $data, $exclude = array(), $debugData = true )
{
  if( !empty( $exclude ) && !is_array( $exclude ) )
  $exclude = array( $exclude );

  for( $i = 0; $i < count( $this->clientFD ); $i   )
  {
  if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $exclude ) )
  {
   if( $debugData )
   $this->sendDebugMessage( "sending: "" . $data . "" to: $i" );

   if( !@socket_write( $this->clientFD[$i], $data ) )
   $this->sendDebugMessage( "Could not write '".$data."' client ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] )." )." );
  }
  }
}

/**
* get current information about a client
*
* @access public
* @param int  $clientId ID of the client
* @return array $info  information about the client
*/
function getClientInfo( $clientId )
{
  if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
  return false;
  return $this->clientInfo[$clientId];
}

/**
* send a debug message
*
* @access private
* @param string $msg message to debug
*/
function sendDebugMessage( $msg )
{
  if( !$this->debug )
  return false;

  $msg = date( "Y-m-d H:i:s", time() ) . " " . $msg;

  switch( $this->debugMode )
  {
  case "text":
   $msg = $msg."n";
   break;
  case "html":
   $msg = htmlspecialchars( $msg ) . "<br />n";
   break;
  }

  if( $this->debugDest == "stdout" || empty( $this->debugDest ) )
  {
  echo $msg;
  flush();
  return true;
  }

  error_log( $msg, 3, $this->debugDest );
  return true;
}

/**
* return string for last socket error
*
* @access public
* @return string $error last error
*/
function getLastSocketError( &$fd )
{
  $lastError = socket_last_error( $fd );
  return "msg: " . socket_strerror( $lastError ) . " / Code: ".$lastError;
}
function onReceiveData($ip,$data){

  $this->broadcastData( $data,array(), true );
}
}


$patServer = new patServer();
$patServer->start();

Python聊天室实例程序分享,python聊天室实例

上一篇 大家上学了简短的Python TCP Socket 编制程序,通过个别写服务端和客户端的代码明白基本的 Python Socket 编制程序模型。本文再通过三个例证来巩固一下对 Socket 编制程序的知道。

一、聊天室程序供给
作者们要落实的是简轻易单的聊天室的例证,就是允许两个人同有的时候间一齐聊天,每一个人发送的新闻全数人都能接到到,类似于 QQ 群的成效,并不是点对点的 QQ 老铁之间的谈天。如下图:

365bet在线官网 1

咱俩要贯彻的有两有的:

  • Chat Server:闲话服务器,担当与用户构建 Socket 连接,并将有些用户发送的新闻广播到全部在线的用户。
  • Telnet Client:用户聊天客户端,能够输入聊天的源委并发送,同一时候能够来得别的用户的音信记录。

同一,大家的音信通讯接纳 TCP 连接保险可信赖性。在独家对服务端和客户端进行程序设计此前,首先要读书一下 Python 中贯彻异步 I/O 的二个函数 —— select。

二、Python 异步I/O
Python 在 select 模块中提供了异步 I/O(Asynchronous I/O),那与 Linux 下的 select 机制相似,但进展局部简化。作者先是介绍一下 select,然后告诉您在 Python 中如何行使它。

前面小说使用四线程来并行管理多路 socket I/O,这里介绍的select 方法允许你响应差别 socket 的多少个事件以及其余分化事件。举例你能够让 select 在有个别 socket 有数量抵达时,只怕当有些 socket 能够写多少时,又可能是当某个 socket 产生错误时通报你,好处是您能够并且响应广大 socket 的多少个事件。

Linux 下 C 语言的 select 使用产生图来代表我们要关怀如何文件陈述符的事件,Python 中采纳 list 来代表大家监察和控制的公文描述符,当有事件达到时,重回的也是文本陈说符的 list,表示那个文件有事件达到。上边包车型大巴大约程序是代表等待从行业内部输入中拿走输入:

rlist, wlist, elist = select.select( [sys.stdin], [], [] )

print sys.stdin.read()

select 方法的多个参数都以 list 类型,分别表示读事件、写事件、错误事件,相同格局再次回到值也是四个list,饱含的是怎样事件(读、写、卓殊)满意了。上边的事例,由于参数唯有贰个风云sys.stdin,表示只关注规范输入事件,因而当 select 重临时 rlist 只会是 [sys.stdin],表示能够从 stdin 中读入数据了,我们采取 read 方法来读入数据。

理所必然 select 对于 socket 描述符也是实用的,上面的三个例证是开创了三个socket 客户端连接到远程服务器,select 用来监督哪个 socket 有多少达到:

import socket
import select

sock1 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock2 = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

sock1.connect( ('192.168.1.1', 25) )
sock2.connect( ('192.168.1.1', 25) )

while 1:

  # Await a read event
  rlist, wlist, elist = select.select( [sock1, sock2], [], [], 5 )

  # Test for timeout
  if [rlist, wlist, elist] == [ [], [], [] ]:
    print "Five seconds elapsed.n"

  else:
    # Loop through each socket in rlist, read and print the available data
    for sock in rlist:
      print sock.recv( 100 )

好了,有了上面包车型大巴基本功,大家就能够来设计聊天室的服务器和客户端了。

三、聊天室服务器
聊天室服务器主要变成上边两件事:

  • 收起多少个客户端的总是
  • 从各类客户端读入音信病广播到别的连接的客户端

咱俩定义叁个 list 型变量 CONNECTION_LIST 表示监听多少个 socket 事件的可读事件,那么利用方面介绍的我们的服务器使用 select 来管理多路复用 I/O 的代码如下:

# Get the list sockets which are ready to be read through select
read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])

当 select 返回时,说明在 read_sockets 上有可读的数据,这里又分为两种情景:

1、假若是主 socket(即服务器初阶创建的 socket,一贯处于监听状态)有数量可读,表示有新的总是央求能够摄取,此时亟需调用 accept 函数来收纳新的客户端连接,并将其三翻五次新闻播报到任何客户端。
2、假诺是别的 sockets(即与客户端已经创建连接的 sockets)有多少可读,那么表示客户端发送信息到服务器端,使用 recv 函数读音讯,并将新闻转载到任何具有连接的客户端。
上面三种情况到事关到广播消息的进度,广播也正是将从某些 socket 获得的新闻通过 CONNECTION_LIST 的每一种 socket (除了本人和主 socket)三个个出殡和埋葬出去:

def broadcast_data (sock, message):
  #Do not send the message to master socket and the client who has send us the message
  for socket in CONNECTION_LIST:
    if socket != server_socket and socket != sock :
      try :
        socket.send(message)
      except :
        # broken socket connection may be, chat client pressed ctrl c for example
        socket.close()
        CONNECTION_LIST.remove(socket)

要是发送战败,大家假如有些客户端已经断开了连年,关闭该 socket 病将其从一而再列表中剔除。

一体化的聊天室服务器源代码如下:

# Tcp Chat server

import socket, select

#Function to broadcast chat messages to all connected clients
def broadcast_data (sock, message):
  #Do not send the message to master socket and the client who has send us the message
  for socket in CONNECTION_LIST:
    if socket != server_socket and socket != sock :
      try :
        socket.send(message)
      except :
        # broken socket connection may be, chat client pressed ctrl c for example
        socket.close()
        CONNECTION_LIST.remove(socket)

if __name__ == "__main__":

  # List to keep track of socket descriptors
  CONNECTION_LIST = []
  RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
  PORT = 5000

  server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # this has no effect, why ?
  server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  server_socket.bind(("0.0.0.0", PORT))
  server_socket.listen(10)

  # Add server socket to the list of readable connections
  CONNECTION_LIST.append(server_socket)

  print "Chat server started on port "   str(PORT)

  while 1:
    # Get the list sockets which are ready to be read through select
    read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])

    for sock in read_sockets:
      #New connection
      if sock == server_socket:
        # Handle the case in which there is a new connection recieved through server_socket
        sockfd, addr = server_socket.accept()
        CONNECTION_LIST.append(sockfd)
        print "Client (%s, %s) connected" % addr

        broadcast_data(sockfd, "[%s:%s] entered roomn" % addr)

      #Some incoming message from a client
      else:
        # Data recieved from client, process it
        try:
          #In Windows, sometimes when a TCP program closes abruptly,
          # a "Connection reset by peer" exception will be thrown
          data = sock.recv(RECV_BUFFER)
          if data:
            broadcast_data(sock, "r"   '<'   str(sock.getpeername())   '> '   data)        

        except:
          broadcast_data(sock, "Client (%s, %s) is offline" % addr)
          print "Client (%s, %s) is offline" % addr
          sock.close()
          CONNECTION_LIST.remove(sock)
          continue

  server_socket.close()

在调节台下运转该程序:

$ python chat_server.py 
Chat server started on port 5000

四、聊天室客户端
咱俩写叁个客户端程序能够连绵不断到上边的服务器,完结发送消息和收取音信的历程。重要做下边两件事:

  • 监听服务器是还是不是有音讯发送过来
  • 检查用户的输入,假设用户输入某条音信,需求发送到服务器

此地有五个 I/O 事件须求监听:连接到服务器的 socket 和规范输入,同样大家能够运用 select 来完结:

rlist = [sys.stdin, s]

# Get the list sockets which are readable
read_list, write_list, error_list = select.select(rlist , [], [])

那逻辑就比比较粗略了,如若是 sys.stdin 有数据可读,表示用户从调控台输入数据并按下回车,那么就从专门的事业输入读数据,并发送到服务器;倘若是与服务器连接的 socket 有数量可读,表示服务器发送新闻给该客户端,那么就从 socket 接收数据。加上一些提醒音信及这几个管理的总体客户端代码如下:

# telnet program example
import socket, select, string, sys

def prompt() :
  sys.stdout.write('<You> ')
  sys.stdout.flush()

#main function
if __name__ == "__main__":

  if(len(sys.argv) < 3) :
    print 'Usage : python telnet.py hostname port'
    sys.exit()

  host = sys.argv[1]
  port = int(sys.argv[2])

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)

  # connect to remote host
  try :
    s.connect((host, port))
  except :
    print 'Unable to connect'
    sys.exit()

  print 'Connected to remote host. Start sending messages'
  prompt()

  while 1:
    rlist = [sys.stdin, s]

    # Get the list sockets which are readable
    read_list, write_list, error_list = select.select(rlist , [], [])

    for sock in read_list:
      #incoming message from remote server
      if sock == s:
        data = sock.recv(4096)
        if not data :
          print 'nDisconnected from chat server'
          sys.exit()
        else :
          #print data
          sys.stdout.write(data)
          prompt()

      #user entered a message
      else :
        msg = sys.stdin.readline()
        s.send(msg)
        prompt()

能够在八个终端下运营该代码:

$ python telnet.py localhost 5000
Connected to remote host. Start sending messages
<You> hello
<You> I am fine
<('127.0.0.1', 38378)> ok good
<You>

365bet在线官网,在另三个终极彰显的音讯:

<You> [127.0.0.1:39339] entered room
<('127.0.0.1', 39339)> hello
<('127.0.0.1', 39339)> I am fine
<You> ok good

总结
上面的代码注意两点:

1、聊天室客户端代码不能在 windows 下运作,因为代码应用 select 同时监听 socket 和输入流,在 Windows 下 select 函数是由 WinSock 库提供,无法管理不是由 WinSock 定义的公文陈述符。
2、客户端代码还有个毛病是,当某些客户端在输入消息但还未发送出去时,服务器也发送讯息过来,那样会冲刷掉客户纠正在输入的音讯。那眼下来看不能够消除的,独一的消除方法是利用像 ncurses 终端库使用户输入和出口独立开,大概写一个 GUI 的次序。
那正是说本文通过三个聊天室的楷模进一步读书了Python 下 Socket 编制程序。

您恐怕感兴趣的篇章:

  • 选择Java和WebSocket达成网页聊天室实例代码
  • Java基于socket完结简易聊天室实例
  • 使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
  • php html5基于websocket实现聊天室的不二等秘书技
  • java达成叁个简单TCPSocket聊天室效能分享
  • Java Socket聊天室编制程序(一)之利用socket完毕聊天之新闻推送
  • Spring Boot实战之netty-socketio完成轻松聊天室(给钦定用户推送新闻)
  • Java Socket聊天室编制程序(二)之利用socket落成单聊聊天室
  • Python socket C/S结构的聊天室应用达成
  • 运用socket达成网络聊天室和私聊功效

你大概感兴趣的篇章:

  • Python socket C/S结构的聊天室应用达成
  • Python达成的施用telnet登录聊天室实例
  • 基于进度内通信的python聊天室完结格局

上一篇 大家学习了简易的Python TCP Socket 编制程序,通过各自写服务端和客户端的代码精通主旨的...

本文由365bet体育备用网址器发布于应用,转载请注明出处:上一篇 我们学习了简单的Python TCP Socket,您可能

关键词: