2015-09-22 39 views
5

Próbuję napisać podstawową aplikację do czatu sieciowego w języku Perl do celów edukacyjnych. Obecnie mam program serwera i klienta, który działa prawie tak, jak chcę. Wielu klientów może łączyć się z serwerem i wysyłać wiadomości do iz niego. Jednak nie jestem pewien, jak wysyłać wiadomości od jednego klienta do drugiego i doceniłbym pchnięcie we właściwym kierunku. Oto kod, który mam do tej pory, myśli?Podstawowa aplikacja do czatowania w sieci w Perlu

Uwaga: Jest to moja pierwsza próba użycia sieci lub użycia Perla do prawidłowego projektu, więc wszelkie inne wskazówki na temat tego, jak jest napisane, również zostałyby docenione.

chat_server.pl

#!/usr/bin/perl -w 
# chat_server.pl 
use strict; 
use IO::Socket::INET; 

my $port = shift or die "Port required!\n"; 
my $socket = IO::Socket::INET->new(
     LocalPort => $port, 
     Proto  => 'tcp', 
     Listen  => SOMAXCONN 
    ) or die "Can't create socket: $!!\n"; 
my $child; 

print "Listening for clients on $port...\n"; 
REQUEST: 
while(my $client = $socket->accept) { 
    my $addr = gethostbyaddr($client->peeraddr, AF_INET); 
    my $port = $client->peerport; 

    if($child = fork) { 
     print "New connection from $addr:$port\n"; 
     close $client; 
     next REQUEST; 
    } die "fork failed!\n" unless defined $child; 

    while (<$client>) { 
     print "[$addr:$port] says: $_"; 
     print $client "[$addr:$port] says: $_"; 
    } 
} 
close $socket; 

chat_client.pl

#!/usr/bin/perl -w 
# chat_client.pl 
use strict; 
use IO::Socket::INET; 

my $port   = shift or die "No port\n"; 
my $server   = shift or die "No server\n"; 
my $client_socket = IO::Socket::INET->new(
     PeerPort => $port, 
     PeerAddr => $server, 
     Proto  => 'tcp' 
    ) or die "Can't create send socket: $!!\n"; 

my $child; 

if($child = fork) { 
    while(1) { 
     sleep(1); 
     print scalar <$client_socket>; 
    } 
} 

die "fork failed!\n" unless defined $child; 
print "Connected to $server:$port!\n"; 

do { 
    print "> "; 
    print $client_socket $_ if defined $_; 
} while(<STDIN>); 

print "Closing connection"; 
close $client_socket; 
+2

Jest to całkiem niezłe ze względu na poziom doświadczenia.Od razu powiem tylko, że powinieneś upewnić się, że twoje materiały do ​​nauki są aktualne. '-w' na linii poleceń lub linii shebang od dawna jest zastępowane przez" ostrzeżenia o używaniu ". Powinieneś również zadeklarować '$ child' w kodzie serwera wewnątrz' while', tuż przed 'if' – Borodin

Odpowiedz

1

Pojedynczy klient na jednym serwerze nie jest zbyt trudne - co robisz z kodem tam jest - skutecznie - tworzenie relacji 1 do 1. Twój lokalny serwer rozmawia wyłącznie z twoim klientem.

Aby uzyskać informacje do rozprzestrzeniania (za pośrednictwem serwera) między wieloma klientami, będziesz musiał nieco bardziej skomplikować - ponieważ masz oddzielne procesy, te procesy muszą się teraz komunikować ze sobą. Jest to wystarczająco duże pytanie, że istnieje cały segment dokumentacji perl o tym: perlipc.

To naprawdę znacznie zwiększy złożoność kodu, ponieważ przechodzisz na relację jeden-do-wielu w komunikacji, a wszystko to będzie się działo asynchronicznie.

Komunikacja oparta na gniazdach jest jedną z form komunikacji między procesami (IPC) i już to robisz. Ale twoja "gotcha" polega na tym, że przenosisz od 1 do 1 komunikatów do 1 do wielu komunikatorów. Musisz mieć możliwość nadawania, a ten tryb komunikacji nie wspiera tego szczególnie dobrze.

Co Proponuję spojrzeć na IO::Pipe - Mam pewne przykładowy kod tutaj: How to extract data from Parallel::ForkManager in perl

następnie użyć IO::Select i can_read do asynchronicznie zdecydować, czy jest jakichkolwiek danych pochodzących w na rurze. Prawdopodobnie będziesz potrzebować szeregu potoków - po jednym na klienta - w przeciwnym razie możesz uzyskać nakładające się współbieżne elementy.

Np

(Z IO::Pipe strona doc:

my $pipe = IO::Pipe->new(); 
if($pid = fork()) { # Parent 
     $pipe->reader(); 
     while(<$pipe>) { 
     ... 
     } 
    } 
    elsif(defined $pid) { # Child 
     $pipe->writer(); 
     print $pipe ... 
    } 

Niestety istnieje niewielka haczyka tutaj - twoje rury będą tworzone w procesie rozwidlone, ale to z kolei oznacza, że ​​trzeba dowiedzieć się, jak obsłużyć szereg rur i sprawdzić, czy są one czytelne

Oznacza to, że nie można siedzieć w pętli while wokół przyjmowania gniazd - blokuje, więc masz wiadomości w kolejce un dopóki inny klient nie połączy się (co naprawdę nie będzie tym, czego chcesz). Musisz więc ponownie użyć select, aby sprawdzić, czy najpierw jest coś, co jest gotowe do accept.

+0

Dzięki za odpowiedź, szczerze mówiąc, wciąż jestem bardzo zdezorientowany, jak to zaimplementować. Czy nadal mogę to zrobić z tylko jednym procesem na połączenie lub czy będę musiał ponownie się rozwidlić? Jak mogę przekazać pakiet danych otrzymany z jednego połączenia do wszystkich otwartych połączeń? Wpadłem na pomysł; mówię, że każdy rozwidlony proces nasłuchuje danych, po otrzymaniu niektórych danych, mógłbym użyć listy wspólnej pomiędzy wszystkimi procesami zawierającymi wszystkie żywe połączenia (chociaż nie mam pewności, jak to zrobić, lub jeśli jest to nawet możliwe) i po prostu przeglądaj wszystkie połączenia i wysyłaj je w ten sposób. Myśli? –

+0

Trudniej niż się wydaje, ponieważ udostępnianie list przez widelec jest trudne. Wątki mogą jednak. – Sobrique

+0

Ten sam wniosek, do którego doszedłem, czy mógłbyś nieco wyjaśnić, w jaki sposób mogę wykorzystać tutaj wątki? –