Добре де аз кой подход съм правил
Преди се опитвах да правя на PHP сървер... и го направих и ми върши работа.
Общо взето в тази последователност:
socket_create()
socket_bind()
socket_listen()
след това с socket_select() се определят сокетите на които има движение, а чрез socket_accept() се приема нова клиентска конекция ако има такава и се добавят в $client_socks[].
правят се два масива:
$client_socks[] с всички активни клиентски сокети и масива
$read[], които са тези сокети сред активните на които е детекнато движение.
След това по този списък се облужват(четат) един по-един тези на които преди това е детекното движението.
е това е кода дето успях да скалъпя...Орязъл съм го за пригледност така че може да има и несъответсвия.
GeSHi (PHP):
#!/usr/bin/php -Cq
<?php
// test
// nc localhost 8181
// ncat localhost 8181
//
// monitor
// netstat -ntc
$address = '127.0.0.1';
// '0.0.0.0' слуша на всички интерфейси
$address = '0.0.0.0';
$port = 8181;
$max_clients = 3;
function socket_init($address, $port)
{
// създава TCP мастер сокет който да слуша на $address:$port и връща този сокет
// при грешка се връща false
// Create a TCP Stream socket
if (false === ($sock = socket_create(AF_INET
, SOCK_STREAM
, SOL_TCP
))) {
return false;
}
// Bind the socket to an address/port
{
return false;
}
// Start listening for connections
// man 2 listen
// http://fixunix.com/unix/379049-server-socket-how-limit-number-connections.html
// http://stackoverflow.com/questions/5111040/listen-ignores-the-backlog-argument
//
// вторият параметър на socket_listen() се нарича 'backlog' и няма никакъв ефект за ограничаването
// на броя конекции. Той е само консултативен и посочва по някакъв вътрешен алгоритъм на OS-а
// колко ще бъде голяма опашката на чакащите конекции.
//
// The backlog argument to listen() is only advisory.
//
// POSIX says:
// The backlog argument provides a hint to the implementation which the implementation shall use
// to limit the number of outstanding connections in the socket's listen queue.
// Current versions of the Linux kernel round it up to the next highest power of two,
// with a minimum of 16. The revelant code is in reqsk_queue_alloc().
//
{
return false;
}
return $sock;
}
//////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////// Начало инициализация ////////////////////////////////////////////
/////////// Инициализация Сокети //////////////
// http://php.net/manual/en/sockets.examples.php
// http://www.binarytides.com/php-socket-programming-tutorial/
// http://bg2.php.net/manual/en/function.socket-select.php
if (false === ($master_sock = socket_init
($address, $port))) die;
fwrite(STDERR
, "Listen on port $port.\n");
//////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////// Главна програма ////////////////////////////////////////////
// масив с клиенските сокети, който реално ще се обслужват -- резултатите от $msg_sock = socket_accept($master_sock)
// масив от сокети, които ще се проверяват за нови конекции от socket_select()
// и това ще са върнатите сокети от socket_select(), които са кандидатите за обработка защото в тях вече има постъпили данни
// ако се приеме конекцията за обработка от read[] масива то тя ще се добави в $client_socks[]
// main loop
while (true)
{
// main loop подготовка на клиенските сокети за обработка - приемане отказване
//////////////////////////////////////////////////////////////////////////////
// prepare array of readable client sockets
// за да работи правилно socket_select() трябва първият в списъка който ще се подаде де е master сокета
$read[0] = $master_sock;
// прехвъляне на списъка с активните сокети $client_socks[] в $read[] масива за проверка
// т.е. правим копие на $client_socks[] в $read[] защото след socket_select() §$read[] масива ще е променен
for ($i = 0, $size=count($client_socks);
$i < $max_clients and
$i < $size;
$i++) {
if($client_socks[$i] !== null)
$read[$i+1] = $client_socks[$i];
}
// чака блокиращо докато се появи някъде, че има постъпили данни на някой сокет.
// последният параметър NULL показва блокиращо чакане.
// А другите два параметъра за следене по запис и exceptions $w= NULL, $e= NULL са забраннени да не се ползват.
//
// при изход socket_select() връща броя сокети в които има постъпили данни. и масива $read (по адрес), който
// е списък на сокетите в които има постъпили данни.
//
// когато във върнатият масив се съдържа и $master_sock това означава и че има и новопостъпила конекция.
// ??? една нова конекция или няколко. всички примери обслужват само една - може би точно така действа socket_select()
{
fwrite(STDERR
, "socket_select() failed: reason: " fwrite(STDERR
, "The Server can NOT service the incommning connection.\n");
break;
}
// if $read contains the master socket, then a new connection has come in
// намираме първият свободен слот в $client_socks[] и там записваме конекцията
// първият свободен слот може да е както в средата на масива така и накрая, след запълнените елементи
if (in_array($master_sock, $read, true)) {
$accepted=false;
for ($i = 0, $size=count($client_socks);
$i < $max_clients;
$i++) {
// може и само if ($client_socks[$i] === null)
// тази допълнителна проверка $i>=$size се прави само да няма Notice: Undefined variable за $client_socks[$i]
// когато се отработва ситуацията когато се добава сокет в края на масива
// (т.е $client_socks все още е по-малък от максималният си размер - $max_clients и $i излиза отвън)
if ($i>=$size OR $client_socks[$i] === null)
{
// Тази функция чака (блокиращо) за осъществяване на връзката от отдалеченият клиент.
// (В случая не чака а връща веднага защото конекцията е приготвена от преди от socket_select())
// Когато клиента осъществи връзка socket_accept() връща нов 'клиентски' сокет за комунукация за точно
// тази конекция.
//
// If there are no pending connections, socket_accept() will block until a connection becomes present.
// this function will accept incoming connections on that socket. Once a successful connection is made,
// a new socket resource is returned, which may be used for communication.
// If there are multiple connections queued on the socket, the first will be used.
// винаги да се ползва socket_clear_error() защото socket_last_error() не чисти грешката.
if (( $client_socks[$i] = $msg_sock = socket_accept($master_sock)) === false) {
fwrite(STDERR
, "socket_accept() failed: reason: ". fwrite(STDERR
, "The Server can NOT service the incommning connection.\n");
break 2;
}
fwrite(STDERR
, "($i) Connection established. $client_ip:$client_port\n");
// Винаги точно 3 реда съобщение - това ще позволи на автомата който се вързва към сървера
// лесно да отдели тези 3 реда.
// край на цикъла, защото е вече намерен слота и е осъществена връзката
$accepted=true;
break;
} //if
} // for ($i = 0; $i < $max_clients; $i++)
if (!$accepted)
{
// имало е индикация за нова конекция защото сме вътре в проверката if (in_array($master_sock, $read))
// но е нямало свободен слот. Затваря се конкцията към клиента
// единственият начин за затваряне на (пендиг) конекцията е тя да се приеме да се изпише кратко съобщение на клиента
// и да се затвори. Така няма да може да се предаде по нататък на тежката обработка.
if (( $msg_sock_denited = socket_accept($master_sock)) === false) {
fwrite(STDERR
, "socket_accept() failed: reason: ". fwrite(STDERR
, "The Server can NOT service the incommning connection.\n");
break 1;
}
fwrite(STDERR
, "Connection denied. $client_ip:$client_port\n");
socket_write($msg_sock_denited, "Welcome to the Server.\n");
socket_write($msg_sock_denited, "Server busy. Too many connections. Try again later.\n");
} // if (!$accepted)
} //if (in_array($master_sock, $read))
// main loop край на подготовката на клиенските сокети за обработка - приемане отказване
////////////////////////////////////////////////////////////////////////////////////////
//fwrite(STDERR, "Loop start.\n");
// loop - обслужване на клиентски сокети
////////////////////////////////////////
for ($i = 0, $size=count($client_socks);
$i < $max_clients and
$i < $size;
$i++) {
if (in_array($client_socks[$i] , $read, true)) {
$msg_sock=$client_socks[$i];
// fwrite(STDERR, "\n\n(start)\n");
// socket_read() чака
// и връща false при следните ситуации:
// 1. error
// 2. connection closed
if ( false === ($input_line = socket_get_data($msg_sock,$error,$error_str)) )
{
if(104 == $error) { fwrite(STDERR
, "($i) Connection closed.\n");
$client_socks[$i]=NULL;
continue; }
else { fwrite(STDERR
, "socket_read() failed: reason: $error_str\n");
break 2;
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////// оснавната част на скрипта ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
// !!! към това което се пише обратно ($input_line), не трябва да се добавя \n накрая защото и си има
$translated_line = $input_line;
// връща го обратно по нета
////////////////////////////////////////////////////////////////////////////////////////////////////
/////// основната част на скрипта ////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
// fwrite(STDERR, "Loop end.\n");
// loop - край на обслужване на клиентски сокети
////////////////////////////////////////////////
} // end of main loop
?>
Обаче въпроса който ме мъчи е как може да се ограничи броя на приеманите конекции? например разрешени са три едновременни конекции и искам на четвъртият клиент да му се откаже конекциятя? да му се върне нещо то рода на Server busy, access denied..... Това не можах да разбера как се прави и дали изобщо е възможно с tcp?
в socket_listen() има параметър дето се нарича 'backlog' той е някакъв кърнелси стек? за броя конекции но изобщо не оказва влияние на броя конекции.
Едиственото което успях да измисля е като дойде 4-та конекция да я приема да изпиша нещо към клиента от рода на Server busy и веднага да затворя съответният клиентски сокет?