Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Perl: Общие вопросы > проблема записи хэндлов в глоабальный хэш ?


Автор: zerg13new 25.3.2010, 13:42
народ, ОЧЕНЬ нужна помощь, так как я уже устал и не понимаю совсем ничего
пишу в linux на perl и цель программы такова: создать сервер, кот принимал бы соединения от клиентов и мог посылать любому выбранном на сервере клиенту сообщение. пишу под консоль, итак код той части что не получается:

 # my server
      2 # 2010.03.05
      3 
      4 #!/usr/bin/perl -w
      5 
      6 use strict;
      7 use IO::Socket;
      8 #use Thread;
      9 use threads;
     10 use threads::shared;
     11 #use POSIX 'WNOHANG'; # for SIGNAL
     12 use constant BUFSIZE => 1024;
     13 
     14 
     15 my %clients :shared;
     16 my( $addr, $port );
     17 
     18 
     19 $addr = &get_ip(); # get IP from interface
     20 
     21 # get port
     22 print "enter port: ";
     23 while( <STDIN> ) {
     24         chomp($_);
     25         $port = $_;
     26         if ( /[0-9]{4,5}/ && $_ > 1024 ) { last; }
     27         print "either wrong value or port is deny, enter new value: ";
     28 }
     29 
     30 my $server = IO::Socket::INET -> new( LocalAddr => $addr,
     31                                         LocalPort => $port,
     32                                         Listen => 255,
     33                                         Type => SOCK_STREAM,
     34                                         Proto => 'tcp',
     35                                         Reuse => 1 );
     36 die "can't create, bind or listen: $!\n" unless $server ;
     37 print "socket OK\n bind OK\n listen OK\n Server started at $addr:$port\n";
     38 warn "Server ready. Waiting for connections... \n";
     39 
     40 threads -> create( \&to_client ) || die "$! \n"; # STDIN
     41
     42 # start threads to treat querys
     43 while( my $client = $server -> accept ) {
     44         my $client_ip = $client -> peerhost();
     45         my $client_port = $client -> peerport();
     46         my $tmp = "$client_ip:$client_port";
     47 
     48        %clients = { %clients , "$client_ip:$client_port"  => $client } ; # add new socket to %client ИМЕННО ЗДЕСЬ
# здесь никакого кода, просто пустота
     56 syswrite( $clients{ "$client_ip:$client_port" }, "hi, how are you??") || die ":$! _ $@";
     57 #foreach my $q ( keys %clients ) { print "debug0: $q\n"; }
     58         print "got a connection from: $client_ip:$client_port $client\n";
     59         $client -> autoflush;
     60 
     61         threads -> create( \&from_client, $client, $client_ip, $client_port ) || die "$! \n"; #STDOUT
     62 
     63 }
     64 exit 0;
     65 

В итоге я получу ошибку "Invalid value for shared scalar at server.pl line 48, <STDIN> line 1."
Хотелось бы прочитать совет спецов, показывающий наглядно как положить мне хэндл сокета $client в расшареный хэш %clients и как потом его оттуда правильно извлечь, чтобы он считался хэндлом. Если не сложно, то не отказался бы и от обьяснений где и почем же я балбес. Я честно пробывал множество всевозможных ссылок, разыменований, читал документацию типо http://perldoc.perl.org/threads/shared.html но ничего толкового не получилось.

Автор: Pfailed 25.3.2010, 14:08
Цитата

%clients = { %clients , $clients{ "$client_ip:$client_port" } = $client } ;


Это что-то странное.
Попробуйте проще:
Код

$clients{"$client_ip:$client_port"} = $client;

Автор: zerg13new 25.3.2010, 15:48
Цитата(Pfailed @  25.3.2010,  14:08 Найти цитируемый пост)
1:$clients{"$client_ip:$client_port"} = $client;

пробывал и так, всё равно будет ошибка при выполнении программы.
Вообще как я заметил, ошибка появляется как только хэш %clients становится глобальным. То есть я делал массив хэндлов и запихивал в них хэндлы push_ом без проблем, но как только обьявлял массив shared, то тут же появлялась эта ошибка

Автор: DurRandir 25.3.2010, 15:57
>use threads;
>use threads::shared;

О нет, нет, нет

Автор: zerg13new 25.3.2010, 16:33
Цитата(DurRandir @ 25.3.2010,  15:57)
>use threads;
>use threads::shared;

О нет, нет, нет

как насчёт обьяснить чего не нравится или есть ли какие предложения дельные ?? рассмотрю с удовольствием, а то я уже  smile 

Автор: mvsgt 25.3.2010, 16:38
"нет нет нет" работает нормально, только очень тормозит если злоупотреблять.

Почему бы не сделать простой форкающий сервер - модулей для *daemon*  много.

Добавлено через 3 минуты и 30 секунд
Код

48        %clients = { %clients , $clients{ "$client_ip:$client_port" } = $client } ; # add new socket to %client ИМЕННО ЗДЕСЬ


это непонятно что. 
и, кажется, с shared хэшами нельзя так рабоать вообще - только поэлементно. в инструкции про это что-то есть.

Автор: zerg13new 25.3.2010, 16:56
mvsgt
Цитата(mvsgt @  25.3.2010,  16:38 Найти цитируемый пост)
  %clients = { %clients , $clients{ "$client_ip:$client_port" } = $client } ; # add new socket to %client ИМЕННО ЗДЕСЬ

извиняюсь, не заметил, но исправил в теме, этого конечно у меня в коде нет, это какой-то бред )
у меня так:  %clients = { %clients , "$client_ip:$client_port"  => $client } ; 
переписывание на $clients{"$client_ip:$client_port"}  = $client  ;

Цитата(mvsgt @  25.3.2010,  16:38 Найти цитируемый пост)
Почему бы не сделать простой форкающий сервер - модулей для *daemon*  много.

в оригинале я должен запустить этот сервер под Win, а с fork() у меня возникли какие-то проблемы с блокировками или буферизацией(только по очереди обменивались клиент и сервер) . думаешь с fork() оно будет работать лучше ?? Я не знаю разделение переменных между потоками и процессами в perl разные вещи ли !?
и всё равно у меня вопрос будет " а как сделать глобальный хэш с дескрипторами!?" или вообще что-то глобальное, откуда любой процесс/поток может забрать нужный ему десриптор !?

Автор: mvsgt 25.3.2010, 17:08
никак нельзя, я думаю. дескрипторы так не распространяются. А вот под windows как раз может и получиться - там форки тредами эмулируются. Но всё равно это муторно и ненадёжно. Надо ещё раз задачу обдумать. Может вообще нужно что-то типа comet.

Автор: DurRandir 25.3.2010, 18:17
Основная проблема с потоками - вам _кажется_, что всё очевидно. А это не так. Вот вы помянули некие блокировки (для форка) - а для потоков они вам, видимо, и не нужны? Ну, флаг в руки. Для эффективного использования сетевого I/O - посмотрите на AnyEvent/EV - последние версии вполне собираются под strawberry perl.

Автор: zerg13new 25.3.2010, 18:37
DurRandir
пока что я исхожу из того, что:
1. я могу передать в поток хэндл сокета как аргумент (вначале я просто передавал аргументом потоку именно хэндл $client и поток с ним нормально работал, следовательно в поток передать хэндл можно и он будет действительный и с ним работает  )
2. я могу создать расшаренный для потоков хэш (я указывал хэндл для хэша как "$client" и он его спокойно записывал, и поток читал из хэша, но хэндлом уже не считал(вдать изменяет контекст из-за кавычек), а потому преполагаю пункт 1)
 ТО наверное можно как-то совместить: создав что-то глоабльное для всех потоков, откуда потоки смогут спокойно читать, к примеру попытать хэш. И только из-за этих 2х пунктов я верю, что я просто чего-то недопонимаю и потому никак не могу записать нормально хэндл в хэш, и жду от вас победы над моей проблемой  smile  (естественно сам я не бездействую)

Автор: mvsgt 25.3.2010, 18:41
в линуксовом перле потоки вообще есть?

Автор: ginnie 25.3.2010, 18:47
zerg13new, быстрым поиском нашел http://perlmonks.org/?node_id=395513 на PerlMonks о доступе к сокетам из потоков. Посмотрите, может найдете что-нибудь полезное. Сам я подобными задачами не занимался, так что конкретно ничем помочь не могу.

Автор: zerg13new 25.3.2010, 19:20
mvsgt
безусловно, как и в виндовой версии strawberry perl

ginnie
спасибо, прочёл,  попробуем обмозговать.. А вообще этот блог читается на одном дыхании как художественная книжка, здорово и эмоционально написано ))

кстати вот попробывал ещё такой вариант:
 $clients{ "$client_ip:$client_port" } = *client; # add new socket to %client
print ref($client) . "++ $client\n";
49(номер строки)  syswrite( *{$clients{"$client_ip:$client_port"}}{IO}, "hi, how are you??") || die ":$! _ $@";

вывод:
IO::Socket::INET++ IO::Socket::INET=GLOB(0x8a399d8)
Can't use string ("*main::client") as a symbol ref while "strict refs" in use at server.pl line 49, <STDIN> line 1.

Автор: gcc 25.3.2010, 19:53
может хэш сделать:

Код

my %dispatch = (
  foo => *foo{IO},
  bar => *bar{IO},
);



http://perldoc.perl.org/perlref.html#Making-References
Цитата

#

A reference can be created by using a special syntax, lovingly known as the *foo{THING} syntax. *foo{THING} returns a reference to the THING slot in *foo (which is the symbol table entry which holds everything known as foo).

   1. $scalarref = *foo{SCALAR};
   2. $arrayref = *ARGV{ARRAY};
   3. $hashref = *ENV{HASH};
   4. $coderef = *handler{CODE};
   5. $ioref = *STDIN{IO};
   6. $globref = *foo{GLOB};
   7. $formatref = *foo{FORMAT};

All of these are self-explanatory except for *foo{IO} . It returns the IO handle, used for file handles (open), sockets (socket and socketpair), and directory handles (opendir). For compatibility with previous versions of Perl, *foo{FILEHANDLE} is a synonym for *foo{IO} , though it is deprecated as of 5.8.0. If deprecation warnings are in effect, it will warn of its use.

*foo{THING} returns undef if that particular THING hasn't been used yet, except in the case of scalars. *foo{SCALAR} returns a reference to an anonymous scalar if $foo hasn't been used yet. This might change in a future release.

*foo{IO} is an alternative to the *HANDLE mechanism given in "Typeglobs and Filehandles" in perldata for passing filehandles into or out of subroutines, or storing into larger data structures. Its disadvantage is that it won't create a new filehandle for you. Its advantage is that you have less risk of clobbering more than you want to with a typeglob assignment. (It still conflates file and directory handles, though.) However, if you assign the incoming value to a scalar instead of a typeglob as we do in the examples below, there's no risk of that happening.

   1. splutter(*STDOUT); # pass the whole glob
   2. splutter(*STDOUT{IO}); # pass both file and dir handles
   3.
   4. sub splutter {
   5. my $fh = shift;
   6. print $fh "her um well a hmmm\n";
   7. }
   8.
   9. $rec = get_rec(*STDIN); # pass the whole glob
  10. $rec = get_rec(*STDIN{IO}); # pass both file and dir handles
  11.
  12. sub get_rec {
  13. my $fh = shift;
  14. return scalar <$fh>;
  15. }

Автор: zerg13new 25.3.2010, 20:42
gcc
Цитата(gcc @  25.3.2010,  19:53 Найти цитируемый пост)
может хэш сделать:КодincludeSyntax('perl');my %dispatch = (  foo => *foo{IO},  bar => *bar{IO},);

спасибо, сделал уже так:
 $clients{ "$client_ip:$client_port" } = *client{IO}; # add new socket to %client
да вот только не знаю как же извлечь то хэндл (не тот либо неопределно значние либо ещё что-нибудь) для
syswrite( (вот уж как здесь не изголяюсь с обзыванием), "hi, how are you??") || die ":$! _ $@";
есть идеи как излечь из хэша ?? а то он не хочет...

Автор: gcc 25.3.2010, 20:47
Код

use Data::Dumper;
print Dumper  \%clients;


может надо посмотреть... дайте вывод

Автор: ginnie 25.3.2010, 20:49
zerg13new, *client{IO} у Вас undef потому, что для сокета Вы используете лексическую переменную, а указанная конструкция работает только с динамическими (т.е. глобальными переменными).

Автор: ramus 25.3.2010, 21:45
Вы не читали книгу "Разработка сетевых программ на PERL" Линкольн Д.Штайн ?
В этой книге подробно рассматриваются все аспекты написания сервера (включая неблокирующийся ввод-вывод и демонизацию).
Очень рекомендую. Почерпнете очень многое. Она у меня есть как в бумажном виде так и в электронном. Весит 20МБ. Могу выслать куда нибудь...

Автор: zerg13new 26.3.2010, 11:27
пока кода ввода в маси и вывода выглядит так
   $clients{ "$client_ip:$client_port" } = *client{IO}; # add new socket to %client
   syswrite( $clients{"$client_ip:$client_port"}, "hi, how are you??") || die ":$! _ $@";

gcc
Can't use an undefined value as a symbol reference at server.pl line 50, <STDIN> line 1.
HASH(0x8a923f8)Perl exited with active threads:

ginnie
я на сайтах читал, что так получают. примеры были таковы
$scalarref = *s{SCALAR}; # Ссылка на скалярную переменную
$arrayref  = *s{ARRAY};  # Ссылка на массив скаляров
$hashref   = *s{HASH};   # Ссылка на ассоциативный массив
$coderef   = *s{CODE};   # Ссылка на подпрограмму
$ioref     = *s{IO};     # Ссылка на дескриптор файла, сокета
$globref   = *s{GLOB};   # Ссылка на все запись
не спорю что я могу неправильно потом их вытаскивать, но я уже как только не пробую  smile  если подскажите как лучше, я только буду рад

ramus
вот с неё то и начинал, причём примеры те кот он приводит с глоабыльными переменными уже не работают,потому как с версии 5.6 или более поздней глобальные переменные не разделяются просто так, для этого нужны спец модули... вот потому пришлось отказаться от этой книги, хотя как основу я её прочёл одну из первых для сетевого взаимодействия
так же я прочёл/пролистал:
Ellie Quigley - Perl by Example
Lincoln D Stein - network programming with Perl
Холзнер - Perl Специальный справочник 2001

Автор: ginnie 26.3.2010, 12:54
zerg13new, поясню свою мысль:

Код

$coderef   = *s{CODE};


правильно работает для глобальной переменной $s

Код

our $s;


У Вас в коде используется лексическая переменная $client

Код

while( my $client = $server -> accept ) {



а здесь уже используется глобальная переменная $client (угадайте, что записано в таблице символов для client в файловом дескрипторе - undef)
Код

$clients{ "$client_ip:$client_port" } = *client{IO};


Теперь я понятно объяснил (сомневаюсь :о)?

Автор: ginnie 26.3.2010, 13:27
По поводу конкретных советов: наиболее верным мне видится вариант из первого сообщения, похоже проблема в том, что неявный файловый дескриптор $client неразделяемый (not shared). Можно попробовать сделать его разделяемым при помощи share($client). Сработает такой вариант или нет - не знаю, особенно учитывая что в описании ни слова о indirect filehandles
Цитата

Тhis module supports the sharing of the following data types only: scalars and scalar refs, arrays and array refs, and hashes and hash refs.


Других вариантов с разделением между потоками набора неявных файловых дескрипторов я пока придумать не смог.

Автор: DurRandir 26.3.2010, 13:53
А, мне тут пришла в голову идея. Раз уже вам очень-очень хочется это сделать на потоках, то передавайте в поток fileno($handle), а в принимающем потоке воссоздавайте объект через new_from_fd (метод из IO::Handle). Но если это под win - то надо проверять работоспособность. 

Автор: zerg13new 26.3.2010, 17:13
ginnie
спасибо, всё ясно о чём вы  smile  заодно и вспомнил по этой статье эту тематику http://forum.woweb.ru/topic10409.html . Спасибо.

DurRandir
почитаем о реализации такой идеи и если ничего не выйдет с более вышестоящим советом, то обязательно попробуем, спасибо

Автор: zerg13new 3.4.2010, 17:06
Проблема решена, но лично у меня только в Linux (debian ) на perl v 5.10.0:
Первоисточник, где я общался по поводу проблемы, как использовать массив хэндлов между потоками в сервере по обмена сообщениями из консоли здесь: http://perlmonks.org/index.pl?node_id=830907
В кратце суть решения проблемы такова оказалась:

## В ОСНОВНОМ потоке вот так записываем хэндлы в хэш
while( my $client = $server -> accept ) {
       ##
        $clients{ "$client_ip:$client_port" } = fileno( $client );  ## здесь кладём в хэш хэндл сокета (точнее некий его заменитель, число)
       ##
}

В ДОЧЕРНЕМ потоке, которому надо работать с этими хэндлами из этого хэша:
 my $client;
 my $fileno = $clients{$clnt_ip_port};
 open $client, "+<&$fileno" || die "$!\n


ВСЁ народ, всем ОГРОМНОЕ спасибо за то что живо отреагировали на мои проблемы и дали дельные советы. Вы мне помогли очень-очень. Именно за такие сообщества я и обожаю инет  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)