UNIX: разработка сетевых приложений
Рис. 5.6. TCP-соединение клиент-сервер с точки зрения сервера
Локальный порт (заранее известный порт сервера) задается функцией
. Обычно сервер также задает в этом вызове универсальный IP-адрес, хотя может и ограничиться получением соединений, предназначенных для одного определенного локального интерфейса путем связывания с IP-адресом, записанным без символов подстановки (то есть не универсального). Если сервер связывается с универсальным IP-адресом на узле с несколькими сетевыми интерфейсами, он может определить локальный IP-адрес (указываемый как адрес отправителя в исходящих пакетах) при помощи вызова функцииbindпосле установления соединения (см. раздел 4.10). Два значения удаленного адреса возвращаются серверу при вызове функции accept. Как мы отмечали в разделе 4.10, если сервером, вызывающим функцию accept, выполняется с помощью функции exec другая программа, то эта программа может вызвать функциюgetsockname, чтобы при необходимости определить IP-адрес и порт клиента.getpeername5.18. Формат данных
В нашем примере сервер никогда не исследует запрос, который он получает от клиента. Сервер лишь читает все данные, включая символ перевода строки, и отправляет их обратно клиенту, отслеживая только разделитель строк. Это исключение, а не правило, так как обычно необходимо принимать во внимание формат данных, которыми обмениваются клиент и сервер.
Пример: передача текстовых строк между клиентом и сервером
Изменим наш сервер так, чтобы он, по-прежнему принимая текстовую строку от клиента, предполагал, что строка содержит два целых числа, разделенных пробелом, и возвращал сумму этих чисел. Функции
наших клиента и сервера остаются прежними, как и функцияmain. Меняется только функцияstr_cli, что мы показываем в листинге 5.11.str_echoЛистинг 5.11. Функция str_echo, суммирующая два числа
//tcpcliserv/str_echo08.c1 #include "unp.h"2 void3 str_echo(int sockfd)4 {5 long arg1, arg2;6 ssize_t n;7 char line[MAXLINE];8 for (;;) {9 if ((n = Readline(sockfd, line, MAXLINE)) == 0)10 return; /* соединение закрывается удаленным концом */11 if (sscanf(line, "%ld%ld", &arg1, &arg2) == 2)12 snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);13 else14 snprintf(line, sizeof(line), "input error\n");15 n = strlen(line);16 Writen(sockfd, line, n);17 }18 }Мы вызываем функцию11-14, чтобы преобразовать два аргумента из текстовых строк в целые числа типаsscanf, а затем функциюlongдля преобразования результата в текстовую строку.snprintfЭти клиент и сервер работают корректно вне зависимости от порядка байтов на их узлах.
Пример: передача двоичных структур между клиентом и сервером
Теперь мы изменим код клиента и сервера, чтобы передавать через сокет не текстовые строки, а двоичные значения. Мы увидим, что клиент и сервер работают некорректно, когда они запущены на узлах с различным порядком байтов или на узлах с разными размерами целого типа
(см. табл. 1.5).longФункции
наших клиента и сервера не изменяются. Мы определяем одну структуру для двух аргументов, другую структуру для результата и помещаем оба определения в наш заголовочный файлmain, представленный в листинге 5.12. В листинге 5.13 показана функцияsum.h.str_cliЛистинг 5.12. Заголовочный файл sum.h
//tcpcliserv/sum.h1 struct args {2 long arg1;3 long arg2;4 };5 struct result {6 long sum;7 };Листинг 5.13. Функция str_cli, отправляющая два двоичных целых числа серверу
//tcpcliserv/str_cli09.c1 #include "unp.h"2 #include "sum.h"3 void4 str_cli(FILE *fp, int sockfd)5 {6 char sendline[MAXLINE];7 struct args args;8 struct result result;9 while (Fgets(sendline, MAXLINE, fp) != NULL) {10 if (sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2) {11 printf("invalid input, %s", sendline);12 continue;13 }14 Writen(sockfd, &args, sizeof(args));15 if (Readn(sockfd, &result, sizeof(result)) == 0)16 err_quit("str_cli: server terminated prematurely");17 printf("%ld\n", result.sum);18 }19 }Функция10-14преобразует два аргумента из текстовых строк в двоичные. Мы вызываем функциюsscanfдля отправки структуры серверу.writenМы вызываем функцию15-17для чтения ответа и выводим результат с помощью функцииreadn.printf