Linux. Полное руководство.

27.3.7. Программа-сервер.

В этом пункте мы напишем две программы — сервер и клиент. Программа-сервер после запуска сразу же перейдет в режим ожидания («прослушивания») новых клиентов. Максимальное количество клиентов —3. Как только подключится клиент, сервер отправит ему сообщение «What is your name?», в ответ на которое клиент передаст свое имя — «Denis». Сервер прочитает переданную клиентом информацию и выведет ее на консоль. Клиент, в свою очередь, выведет на консоль запрос сервера.

С целью упрощения исходного кода как сервера, так и клиента, обработку ошибок производить не будем, поэтому будьте готовы к тому, что ваш клиент выдаст сообщение Segmentation fault в ответ на неверно заданные параметры. Я рекомендую в качестве имени сервера использовать localhost и обе программы запускать на одном компьютере — это же только демонстрация.

Вот исходный код программы-сервера:

Листинг 27.3. Программа-сервер.

#include <sys/types.h>

#include <netdb.h>

#include <memory.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <stdio.h>

#define SERVER_PORT 1234.

#define BUF_SIZE 64.

#define MSG_TO_SEND "What is your name?\n".

Int main() {

 int sock1, sock2;

 int ans_len, total=0;

 char buffer[BUF_SIZE];

 struct sockaddr_in sin, client;

 sock1 = socket(AF_INET, SOCK_STREAM, 0);

 memset((char *)&sin, '\0', sizeof(sin));

 sin.sin_family = AF_INET;

 sin.sin_addr.s_addr = INADDR_ANY;

 sin.sin_port = SERVER_PORT;

 bind(sock1, (struct sockaddr *)&sin, sizeof(sin));

 printf("Server running...\n");

 listen(sock1, 3);

 while (1) {

  ans_len = sizeof(client);

  sock2 = accept(sock1, &client, &ans_len);

  write(sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND));

  total+=1;

  ans_len = read(sock2, buffer, BUF_SIZE);

  write(1, buffer, ans_len);

  printf("Client no %d\n", total);

  shutdown(sock2, 0);

  close(sock2);

 }

 return 0;

}

Теперь разберемся, что есть что. Сначала мы определяем некоторые макросы: номер порта, который будет прослушивать сервер, размер буфера передаваемых данных и текст запроса клиенту.

Стандартные номера портов определены в файле netinet/in.h:

Enum {

 IPPORT_ECHO = 7, /* Echo service. */

 IPPORT_DISCARD = 9, /* Discard transmissions service. */

 IPPORT_SYSTAT = 11, /* System status service. */

 IPPORT_DAYTIME = 13, /* Time of day service. */

 IPPORT_NETSTAT = 15, /* Network status service. */

 IPPORT_FTP = 21, /* File Transfer Protocol. */

 IPPORT_TELNET =23, /* Telnet protocol. */

 IPPORT_SMTP = 25, /* Simple Mail Transfer Protocol. */

 IPPORT_TIMESERVER = 37, /* Timeserver service. */

 IPPORT_NAMESERVER = 42, /* Domain Name Service. */

 IPPORT_WHOIS = 43, /* Internet Whois service. */

 IPPORT_MTP = 57,

 IPPORT_TFTP = 69, /* Trivial File Transfer Protocol. */

 IPPORT_RJE = 77,

 IPPORT_FINGER = 79, /* Finger service. */

 IPPORT_TTYLINK = 87,

 IPPORT_SUPDUP = 95, /* SUPDUP protocol. */

 IPPORT_EXECSERVER = 512, /* execd service. */

 IPPORT_LOGINSERVER = 513, /* rlogind service. */

 IPPORT_CMDSERVER = 514,

 IPPORT_EFSSERVER = 520,

 /* UDP ports. */

 IPPORT_BIFFUDP = 512,

 IPPORT_WHOSERVER = 513,

 IPPORT_ROUTESERVER = 520,

 /* Ports less than this value are reserved.

    for privileged processes. */

 IPPORT_RESERVED = 1024,

 /* Ports greater this value are reserved.

    for (non-privileged) servers. */

 IPPORT_USERRESERVED = 5000.

};

Нам понадобятся сразу два сокета: первый — это сокет сервера, а через второй сокет мы будем производить обмен данными с клиентом.

Int sock1, sock2;

Следующие переменные: ans_len используется для хранения размера передаваемой клиентом информации — фактического размера структуры struct sockaddr_in, а total — это счетчик числа клиентов, используемый для вывода порядкового номера клиента.

Переменная buffer размера BUF_SIZE — это наш буфер для обмена информацией. Нам нужны две структуры типа sockaddr_in — одна для сервера (sin) и одна для клиента (client).

В строке.

Sock1 = socket(AF_INET, SОСК_STREAM, 0);

Мы создаем наш, «серверный», сокет: набор протоколов — TCP/IP, режим — с установлением соединения.

Затем мы инициализируем структуру sin:

Memset((char *)&sin, '\0', sizeof(sin));

Sin.sin_family = AF_INET; // TCP/IP.

Sin.sin_addr.s_addr = INADDR_ANY; // можем работать на.

                                  // любом адресе.

Sin.sin_port = SERVER_PORT; // указываем порт (1234).

После создания сокета и инициализации структуры sin, нужно связать наш сокет с адресом и портом сервера:

Bind(sock1, (struct sockaddr *)&sin, sizeof(sin));

Оператор listen(sock1, 3) означает, что мы будем прослушивать сокет sock1 (порт 1234) и максимальное число клиентов не должно превышать 3.

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

1. получаем размер структуры client.

Ans_len = sizeof(client);

2. создаем сокет sock2, через который будем обмениваться данными с клиентом. Если в очереди listen нет клиентов, мы переходим в состояние ожидания.

Sock2 = accept(sock1, &client, &ans_len);

3. как только подключится клиент, мы отправим ему сообщение MSG_TO_SEND.

Write(sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND));

4. увеличиваем счетчик клиентов.

Total+=1;

5. получаем размер прочитанных данных, сами данные записываются в буфер buffer.

Ans_len = read(sock2, buffer, BUF_SIZE);

6. выводим прочитанные данные на стандартный вывод.

Write(1, buffer, ans_len);

7. завершаем сеанс связи.

Shutdown(sock2, 0);

8. закрываем сокет.

Close(sock2);

Конечно, любой нормальный сервер при поступлении определенных сигналов, например, SIG_HUP, должен корректно перезапуститься или вообще завершить работу. Наш сервер этого не делает — обработку сигналов, я надеюсь, вы можете добавить сами.

Теперь мы можем откомпилировать нашу программу:

$ gcc -о server server.c.

Запускаем:

./server.

Программа перешла в состояние ожидания новых клиентов.