Pull to refresh

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

Reading time 4 min
Views 45K
мой первый 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, обработка сигналов, демонизация процесса.
Tags:
Hubs:
+17
Comments 58
Comments Comments 58

Articles