Сервер TCP Версия для печати
 
Исходный код: Написать хороший сервер довольно непросто. К тому же все зависит от поставленной задачи. Существует масса решений этой задачи и опытный программист должен выбрать оптимальный вариант. Сложность так же состоит и в том, что при проектировании программы-сервера необходимо руководствоваться соображениями максимальной стабильности и безопасности.

Пример сервера

Давайте рассмотрим процесс создания серверной стороны сокета. Правда для тестирования нам придется написать простенького клиента, но я думаю это для вас уже не проблема. В любом случае, о клиенте после. Итак, сервер:

#!C:/perl/bin/perl -w # sited.pl Simple TCP server # For more information see http://whirlwind.ru/give/?oid=12 use strict; use Socket; my $host = "localhost"; my $port = 10000; my $work = 1; my $sock_name = GetSockName($host,$port) 	or die "Couldn't convert $host into an Internet address: $!\n"; socket(SERVER,PF_INET,SOCK_STREAM,getprotobyname('tcp')) 	or die "Couldn't create socket: $!\n"; setsockopt(SERVER,SOL_SOCKET,SO_REUSEADDR,1) 	or die "setsockopt() failed: $!\n"; bind(SERVER,$sock_name) 	or die "Couldn't bind to port $port: $!\n"; listen(SERVER,SOMAXCONN); while ($work){ 	my $rem_addr = accept(CLIENT,SERVER); 	print CLIENT "Ok\n"; 	print "Connection...\n"; 	close CLIENT; 	$work = undef; } close SERVER; sub GetSockName{ 	my ($nm,$pt) = @_; 	return undef unless defined($nm); 	return undef unless defined($pt); 	return undef unless $nm = gethostbyname($nm); 	return sockaddr_in($pt,$nm); } 
Как видим, первоначальные шаги при создании сервера мало чем отличаются от аналогичных для клиента (см. Клиент TCP). Сначала сокет идентифицируется. Функция bind связывает созданный сокет с конкретным адресом. С помощью функции listen мы устанавливаем количество подключений, которые будут поставлены в очередь ожидания. SOMAXCONN это константа модуля Socket, которая содержит максимально-допустимый размер очереди входящих подключений. Пусть вас не смущает бессмысленный цикл while. Мы пока просто тестируем работу серверной стороны, поэтому здесь нам этот цикл не нужен. Функция accept принимает входящие подключения. В качестве аргументов эта функция принимает дескриптор, подключаемый к клиенту, и дескриптор серверной стороны. В случае отсутствия входящих подключений функция accept блокирует работу программы. В качестве результата функция accept возвращает идентификатор удаленного сокета, то есть адрес и номер порта упакованный функцией inet_ntoa.
while ($work){ 	my $rem_addr = accept(CLIENT,SERVER); 	print CLIENT "Ok\n"; 	my ($ip,$pt) = GetSockAddr($rem_addr); 	print "Connection from $ip:$pt...\n"; 	close CLIENT; 	$work = undef; } 

Функция GetSockAddr

Функция GetSockAddr выполняет работу противоположную GetSockName:

sub GetSockAddr{ 	my ($in) = @_; 	return undef unless defined($in); 	my ($pt,$ip) = sockaddr_in($in); 	return (inet_ntoa($ip),$pt); } 
Функция inet_ntoa выполняет конвертацию адреса в его строковое представление. Так же есть обратная функция inet_aton которая преобразовывает, соответственно, из строкового представления в числовое. Дело в том, что функция gethostbyname возвращает адрес уже преобразованный к числовому. Поэтому в функции GetSockName мы не использовали inet_aton. Если бы мы работали не с именами, а непосредственно с IP-адресами, то код функции GetSockName выглядел бы следующим образом:
sub GetSockName{ 	my ($ip,$pt) = @_; 	return undef unless defined($ip); 	return undef unless defined($pt); return sockaddr_in($pt,inet_aton($ip)); } 
Но так как gethostbyname возвращает правильный результат, даже в случае если передаваемое имя является строковым представлением IP-адреса, то inet_aton мы не используем. По поводу inet_aton и inet_ntoa необходимо добавить, что эти функции выполняют платформозависимые преобразования, в соответствии с принятым в системы представлением значений в памяти (тупоконечное, остроконечное).

Функция sockaddr_in в списковом контексте выполняет распаковку значений порта и адреса хоста из структуры SOCKADDR_IN. После приведения IP-адреса в строковое представление функция GetSockAddr возвращает список из двух элементов: IP-адреса и порта. Теперь, если мы вернемся к коду цикла while, становится ясно, каким образом мы получаем информацию об удаленном хосте.

"Быстрый" клиент

Не долго думая, мы воспользуемся модулем IO::Socket для тестирования нашего сервера:

#!C:/perl/bin/perl -w # client.pl Simple TCP client # For more information see http://whirlwind.ru/give/?oid=12 use strict; use IO::Socket; my $host = 'localhost'; my $port = 10000; my $socket = IO::Socket::INET->new( 	PeerAddr	=> $host, 	PeerPort	=> $port, 	Proto		=> 'tcp', 	Type		=> SOCK_STREAM); my $line = <$socket>; print "Server send: $line\n"; close($socket); 
Тут все нам кажется знакомым, за исключением методов модуля IO::Socket. Давайте тестировать.

Сначала, естественно, должен быть запущен сервер:

C:\TESTS\>sited.pl 
Теперь, в другом терминале (консольном окне) запускаем клиента:
C:\TESTS\>client.pl 
Если все прошло удачно, то после запуска клиента в терминале с запущеным сервером должно отразиться примерно следущее:
Connection from 127.0.0.1:1686... 
Номер порта может быть другим.

Получение имени хоста по его IP-адресу

Давайте еще немного усовершенствуем программу. Попробуем добиться того, что бы наш сервер выдавал не только IP-адрес удаленного компьютера, но так же и его полное доменное имя. Для этого немного усовершенствуем функцию GetSockAddr:

sub GetSockAddr{ 	my ($in) = @_; 	return undef unless defined($in); 	my ($pt,$ip) = sockaddr_in($in); 	return (inet_ntoa($ip),$pt,gethostbyaddr($ip,AF_INET)); } 
А цикл while теперь должен выглядеть так:
while ($work){ 	my $rem_addr = accept(CLIENT,SERVER); 	print CLIENT "Ok\n"; 	my ($ip,$pt,$hn) = GetSockAddr($rem_addr); 	$hn = "" unless defined($hn); 	print "Connection from $hn ($ip):$pt...\n"; 	close CLIENT; 	$work = undef; } 
Вот таким образом мы получаем исчерпывающую информацию об удаленном компьютере. Но, необходимо заметить, что функция gethostbyaddr не самая быстрая. Для обратного разрешения имени может потребоваться достаточно много времени. Совсем не удивительно если при использовании этой функции вы будете сидеть и ждать когда же ваша программа сделает свое дело.

Снова запускаем сервер, а затем и клиента. В терминале с сервером должно быть примерно следующее (опять же за исключением номера порта):

Connection from thunder.whirlwind.ru (127.0.0.1):1801 
Ну вот, господа, мы и закончили с серверными азами. -----------------------------7d42801f240504 Content-Disposition: form-data; name="allow_html_d" yes
 
Автор: Whirlwind
 
Оригинал статьи: http://woweb.ru/publ/58-1-0-435
 
Рейтинг@Mail.ru
© Студия WEB-дизайна PM-Studio, 2005. Все права защищены.