Как стать автором
Поиск
Написать публикацию
Обновить

классический TCP сервер

Время на прочтение4 мин
Количество просмотров46K
мой первый TCP Сервер был создан пару лет назад. Основой создания послужила книга Р.Стивенсона «Unix — Профессиональное программирование.» Есть несколько подходов к созданию TCP-серверов. В данном посте хочется рассказать про классический TCP сервер.



При построение классического TCP Сервера можно выделить три составные части:
  • демонизация процесса
  • работа с сокетами
  • создание дочернего процесса и обработка его завершения

При работе с сокетами необходимо:
  • создать дескриптор сокетов
  • назначить сокету адрес
  • принять запрос на установку соединения
  • установить соединение
  • принять / отправить данные

рассмотрим код, смотрим комментарии:
1 struct sockaddr_in addr;
2 struct sockaddr_un local;
3
4 int err,len;
5
6 // создаем дескриптор сокета
7 if( -1 == ( ls = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) )) {
8 perror( "Socket can not created!\n");
9 return ;
10 }
11
12 // задаем сокету опцию SO_REUSEADDR
13 // повторное использование локальных адресов для функц. bind()
14 setsockopt( ls, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof(rc) );
15 memset( &addr , 0, sizeof(addr) );
16
17 // задаем адрес прослушивания и порт
18 addr.sin_family = AF_INET;
19 addr.sin_port = htons (port);
20 addr.sin_addr.s_addr = INADDR_ANY ;
21
22 // связываем адрес с дескриптором слушающего сокета
23 if ( err = bind( ls, (struct sockaddr*) &addr, sizeof(addr) ) < 0 ) {
24 close(ls);
25 perror( "bind error!\n%s ", gai_strerror(err) );
26 return ;
27 }
28
29
30 // начинаем слушать сокет
31 if ( listen( ls, 25) < 0) { ;
32 close(ls);
33 perror( "listen error!\n");
34 return ;
35 }
36
37 // демонизируем процесс
38 pid_t pid = Demonize();
39
40 if ( pid==-1 ) {
41 close(ls);
42 perror( "demonize error!\n");
43 return;
44 }
45
46 // бесконечный цикл на прием соединения
47 while (true) {
48 // прием соединения, создание дескриптора
49 rc = accept( ls, NULL, NULL );
50 // пораждаем дочерний процесс
51 pid = fork();
52
53 if ( pid < 0) syslog( LOG_ERR, " fork abort" );
54 if (pid == 0 ) { // дочерний процесс
55 close( ls ); // закрываем дескриптор сдушающего сокетов
56
57 process(rc); // пользовательская функция,
58 // параметром передается дескриптор открытого соединения
59 close(rc);
60 return;
61 } else {
62 // Родительский процесс
63 close( rc ); // закрываем дескриптор соединения
64 }
65
66 } // end while


А теперь немного пояснений:

Создаем дескриптор слушающего сокета Строки 7-10:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

константа domain:
AF_INET — IP сокет
AF_UNIX — UNIX сокет

константа type:
SOCK_STREAM — сокет постоянного соединения
SOCK_DGRAM — сокет датаграм

константа int protocol:
IPPROTO_TCP — тип протокола

Функция socket возвращает номер дескриптора прослушающего сокета. Если результат меньше 0, то ошибка системы.

Строки 12-28 Назначаем сокету адрес.

#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
задает сокету опциии:
SO_REUSEADDR повторное использование локальных адресов для функции bind()
Если результат выполнение операции меньше 0, то ошибка системы.

Строки 18-20 задаем адрес прослушивания и порт в структуре sockaddr_in
addr.sin_family = AF_INET; // тип домена INET
addr.sin_port = htons (port); // назначаем порт
addr.sin_addr.s_addr = INADDR_ANY; // любой входящий адрес

Функция bind ассоциирует адрес с безымяннымсокетом:
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
int socket — дескриптор сокета
address — адресс структуры sockaddr с
address_len — размер структуры sockaddr
Если результат выполнение операции меньше 0, то ошибка системы.

Начинаем прослушивать сокет Строки 31-35. С помощью функции listen сервер заявляет свое желание принимать запросы на установку соединения:

#include <sys/socket.h>
int listen(int socket, int backlog);
int socket — дескриптор сокета
int backlog — максимальная длинна очереди ожидающих запросов на соединение
Если результат выполнение операции меньше 0, то ошибка системы.

Строка 38-44, Демонизируем процесс. Более подробно в сл. статье о процессах-демонах.

Строки 47-66, обработка соединения

Строка 49, Функция accept прием запроса и преобразование его в соединение.
#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
int socket — дескриптор слушающего сокета, он не связан для соединения и остается для прослушивания последующих соединений,
address — указатель на структуру адреса сокета, в которой по окончании будет хранится адрес клиента
address_len — размер буфера, для размещения структуры адреса, если address и address_len передать NULL — то нас не интересует адрес клиента.
Функция accept возвращает дескриптор сокета, связанного с соединением или -1 в случае неудачи.

Мы можем обработать соединение и принять следующее, но как правило, соединения обрабатываются какое-то длительное время а сервер ждать не может, по этому, после установки соединения порождается дочерний процесс строка 51, командой fork. Далее, в родительском процессе закрываем дескриптор соединения, а в дочернем процессе закрываем дескриптор слушающего сокета и вызываем пользовательскую функцию, которая обработает наше соединение.
Обработка соединения осуществляется командами read/write:
запись в сокет: write (rc, buff, len )
чтение из сокета: read (rc, buff, len )
rc — дескриптор соединения
buff — буфер чтения/записи
len длинна буфера.
По окончанию обработки соединения, дескриптор сокета rc необходимо закрыть: close(rc).

Что не вошло и будет отдельно изложено другими статьями: описание команды fork, обработка сигналов, демонизация процесса.
Теги:
Хабы:
Всего голосов 65: ↑41 и ↓24+17
Комментарии58

Публикации

Ближайшие события