- •Сетевое программирование
- •Материал этих методических указаний предпологает использование языка cи, возможностей ос Linux, cистемных вызовов этой операционной системы, и ее библиотек.
- •Краткие теоретические сведения.
- •1.1 Создание socket'а
- •1.2 Связывание socket'а
- •1.3 Ожидание установления связи
- •1.4 Запрос на установление соединения
- •1.5 Прием запроса на установление связи
- •1.6 Формирование адреса узла сети
- •1.7 Функции обмена данными
- •1.8 Посылка данных
- •Получение данных
- •Функции закрытия связи
- •Цикл лабораторных работ «Сетевое программирование на базе
- •Примеры использования socket-интерфейса для организации клиент – серверного взаимодействия
- •Программа-сервер
- •Программа-клиент
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
- •Возвращаемое значение
- •Прототип
- •Описание
Программа-клиент
Текст программы-клиента на языке программирования СИ выглядит следующим образом
#include <tiuser.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <memory.h>
#define SRV_HOST "delta"
#define SRV_PORT 1234
#define ASK_TXT "What must I do?\n"
#define CONT_TXT "Continue\n"
#define CANC_TXT "Cancel\n"
main () {
int fd;
int flags;
struct t_unitdata *ud;
struct sockaddr_in *p_addr;
struct hostent *hp;
extern int t_errno;
fd = t_open ("/dev/udp", O_RDWR, NULL);
t_bind (fd, NULL, NULL);
ud = (struct t_unitdata *) t_alloc (fd, T_UNITDATA, T_ALL);
memset (ud->addr.buf, '\0', ud->addr.maxlen);
p_addr = (struct sockaddr_in *) ud->addr.buf;
hp = gethostbyname (SRV_HOST);
p_addr->sin_family = AF_INET;
memcpy((char *)&(p_addr->sin_addr),hp->h_addr,hp->h_length);
p_addr->sin_port = SRV_PORT;
ud->addr.len = sizeof(struct sockaddr_in);
while (1) {
strcpy (ud->udata.buf, ASK_TXT);
ud->udata.len = sizeof(ASK_TXT);
t_sndudata (fd, ud);
t_rcvudata (fd, ud, &flags);
write (1, ud->udata.buf, ud->udata.len);
if ( strcmp(ud->udata.buf, CONT_TXT) )
break;
38 };
t_free ((char *) ud, T_UNITDATA);
t_close (fd);
exit (0);
42 }
В строках 8 и 9 описываются константы SRV_HOST и SRV_PORT, определяющие имя удаленного узла, на котором функционирует программа-сервер, и номер порта, к которому привязана транспортная точка сервера. В строках 20 и 21 создается транспортная точка, имеющая не интересующий нас в этой программе транспортный адрес.
В строке 22 выделяется оперативная память под структуру данных типа struct t_unitdata и под три буфера, определяемых полями addr, opt и udata этой структуры (размер памяти, выделяемой под эти буфера, функция t_alloc вычисляет самостоятельно на основе информации о конкретном поставщике транспортных услуг). В строке 25 посредством библиотечной функции gethostbyname транслируется символическое имя удаленного узла (в данном случае "delta"), на котором должен функционировать сервер, в адрес этого узла, размещенный в структуре типа hostent. В строке 27 адрес удаленного узла копируется из структуры типа struct hostent в соответствующее поле структуры типа struct sockaddr_in, которая размещена в буфере ud- >addr. В строках 31 и 32 заполняются поля структуры ud->udata передаваемой серверу информацией и длиной этой информации. В строке 33 с помощью функции t_sndudata посылается запрос серверу. В строке 34 с помощью функции t_rcvudata принимается ответ от сервера. При этом транспортный адрес транспортной точки отправителя ответа (сервера) размещается функцией в ud->addr, а сами данные, составляющие ответ, - в ud->udata. В строке 39 освобождается оперативная память, занимавшаяся структурой типа struct t_unitdata. Строка 40 посредством функции t_close закрывает (удаляет) транспортную точку.
Программа HttpClient
#include <stdlib.h> // stdlib functions
#include <unistd.h> // getopt
#include <stdio.h> // printf, etc
#include <string.h> // strncpy, etc
#include <sys/socket.h> // sockets API
#include <netdb.h> // gethostbyname
#include <errno.h> // errno
// Необходимо для getopt
extern char *optarg;
// Функция main, входная точка программы
int main(int argc /*количество аргументов*/, char* argv[]/*массив аргументов*/)
{
// Текст помощи
const char* usageInfo =
"Использование: ./HttpClient [-u http://host[:port][/page]] [-h]\n"
"\t-u - URL для получения страницы.\n"
"\t-h - эта справка.\n";
// Буффер для строчки с URL
static const int MAX_URL_LEN = 1024;
char urlBuffer[MAX_URL_LEN];
memset(urlBuffer, 0, MAX_URL_LEN);
// Чтение аргументов командной строки в стиле Си
int opt;
while ((opt = getopt(argc, argv, "hu:")) != -1)
{
switch (opt)
{
case 'u':
{
// Использование strncpy вместо strcpy дает защиту от переполнения буфера
strncpy(urlBuffer, optarg, MAX_URL_LEN);
break;
}
case 'h':
default:
{
// Напечатать помощь и выйти
printf(usageInfo);
exit(EXIT_FAILURE);
}
}
}
// если конфигурация после прочтения всех опций неполна - напечатать помощь и выйти
if (strnlen(urlBuffer, MAX_URL_LEN) == 0)
{
printf(usageInfo);
return EXIT_FAILURE;
}
// Данные для разбора URL в стиле Си
char urlHost[MAX_URL_LEN]; // Хост
memset(urlHost, 0, MAX_URL_LEN);
char urlPage[MAX_URL_LEN]; // Адрес страницы
memset(urlPage, 0, MAX_URL_LEN);
strncpy(urlPage, "index.html", MAX_URL_LEN); // Предопределенное значение адреса страницы index.html
int urlPort = 80; // Порт, предопределенное значение 80
// Пропустить http://
char* pBuffer = strstr(urlBuffer, "http://");
if (pBuffer != 0)
pBuffer = urlBuffer + strnlen("http://", MAX_URL_LEN);
else
pBuffer = urlBuffer;
// Разбиение строки в разных кобминациях имени хоста, порта и адреса страницы на сервере
if (sscanf(pBuffer, "%99[^:]:%i/%199[^\n]", urlHost, &urlPort, urlPage) == 3) {}
else if (sscanf(pBuffer, "%99[^/]/%199[^\n]", urlHost, urlPage) == 2) {}
else if (sscanf(pBuffer, "%99[^:]:%i[^\n]", urlHost, &urlPort) == 2) {}
else if (sscanf(pBuffer, "%99[^\n]", urlHost) == 1) {}
else
{
printf("Не удалось разобрать строчку URL");
printf(usageInfo);
return EXIT_FAILURE;
}
// Данные необходимые для соединения и обмена данными
// Структуры для адресов
struct addrinfo hints;
struct addrinfo *result;
struct addrinfo *rp;
// Сокет для обмена данными
int sfd; // socket file descriptor
// код ошибки
int s;
static const int BUF_SIZE = 256*1024;
// Буфер памяти для сетевого обмена, фиксированной длинны на стеке
char buf[BUF_SIZE];
// Инициализация структур для получения ip адреса по имени хоста
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(buf, BUF_SIZE, "%d", urlPort);
// Получение набора адресов соответствующих имени хоста urlHost
s = getaddrinfo(urlHost, buf, &hints, &result);
if (s != 0)
{
fprintf(stderr, "Ошибка getaddrinfo: %s\n", gai_strerror(s));
perror(strerror(errno));
return EXIT_FAILURE;
}
// getaddrinfo() Возвращает набор структур с адресами
// Будем пробовать все подряд пока не соединимся
for (rp = result; rp != NULL; rp = rp->ai_next)
{
// Создание TCP сокета
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
// Попытка соединения, выход из цикла если удачно
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
break;
// Закрытие сокета
close(sfd);
}
// Если ни один адрес не подошел
if (rp == NULL)
{
fprintf(stderr, "Неудается установить соединение\n");
return EXIT_FAILURE;
}
// Освобождение структур содержащих разрезолвленные адреса
freeaddrinfo(result);
// Подготовка простейшего HTTP запроса
char requestPattern[] = "GET /%s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"Accept: text/html;q=1.0\r\n"
"Accept-Language: en-US,en;q=1.0\r\n\r\n";
snprintf(buf, BUF_SIZE, requestPattern, urlPage, urlHost);
// Отправка запроса
// total - количество байт к отправке
int total = strnlen(buf, BUF_SIZE);
// sent - общее количество отправленных байт
int sent = 0;
// bytes - количество байт отправленных за последний заход
int bytes;
do
{
// попытка записи в сокет с текущего места и до конца
bytes = write(sfd, buf + sent, total - sent);
if (bytes < 0)
{
printf("Ошибка при записи в сокет");
perror(strerror(errno));
return EXIT_FAILURE;
}
if (bytes == 0)
break;
sent += bytes;
} while (sent < total); // Отсылка данных пока есть, что посылать
// Очистка буфера сетевого обмена
memset(buf, 0, BUF_SIZE);
// Максимальный размер получаемых данных равен размеру буфера
total = BUF_SIZE;
// Общее количество полученных байт
int received = 0;
do
{
// Чтение в буфер поэтапно
bytes = read(sfd, buf + received, total - received);
if (bytes < 0)
{
printf("Ошибка при чтении из сокета");
perror(strerror(errno));
return EXIT_FAILURE;
}
// Получение ноля байт говорит о том, что противополжная сторона закрыла соединение
if (bytes == 0)
break;
received += bytes;
} while (received < total); // Получение данных пока есть место в буфере
// Если буфер закончился и нехватает места на терминирующий ноль - случилось переполнение
if (received == total)
{
printf("Ошибка при чтении из сокета - буфер переполнен");
exit(EXIT_FAILURE);
}
// Нультерминируем буфер после чтения
buf[received] = 0;
// Закрываем сокет
close(sfd);
// Выводим ответ сервера
printf("Ответ:\n%s\n", buf);
// Успешное завершение
return EXIT_SUCCESS;
}
Примечание. Данная клиентская программа по заданному URL определяет у Web-сервера его IP-адреса и по первому адресу, содержащемуся в полученной структуре, запрашивает у сервера его главную страницу. В программе используется функция perror() которая записывает в переменную errno код ошибки.
Приложение А.
Основные функции библиотеки Sockets API.
accept()
Принимает входные подключения на слушающем сокете.
Прототип
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
Описание
accept() вызываетcя чтобы получить новый дескриптор сокета для последующего общения с только что подключённым клиентом.
Возвращаемый accept()-ом дескриптор определяет уже открытый и подключённый к
.
Возвращаемое значение
accept() возвращает дескриптор только что подключённого сокета, или -1 при ошибке, при этом соответствующим образом установив errno.
Пример
struct sockaddr_storage their_addr; socklen_t addr_size;
struct addrinfo hints, *res; int sockfd, new_fd;
// сначала заполняем адресные структуры с помощью getaddrinfo(): memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // использовать либо IPv4 либо IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // заполнить мой IP для меня getaddrinfo(NULL, MYPORT, &hints, &res);
// создать сокет, связать и слушать:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);
// теперь принять входящие подключения: addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
// можно беседовать по дескриптору сокета new_fd!
bind()
Связывает сокет с IP адресом и номером порта.
