1   2   3
Ім'я файлу: ЛАБОРАТОРНА РОБОТА №1_18_21.doc
Розширення: doc
Розмір: 298кб.
Дата: 14.12.2021
скачати

Лістинг А.2. Простий ТСР-сервер

---------------------------------------------------------------------------simples. с

1 #include

2 #include

3 #include

4 #include

5 int main(void)

6 {

7 struct sockaddr_in local;

8 int s;

9 int s1;

10 int rc;

11 char buf[1];

12 local.sin_family = AF_INET;

13 local.sin_port = htons(7500);

14 local.sin_addr.s_addr - htonl(INADDR_ANV);

15 s = socket(AF_INET, SQCK_STREAM, 0);

16 if (s < 0)

17 {

18 perror("ошибка вызова socket”); 19 exit(1);

20 }

21 rc = bind(s, (struct sockaddr *)&local, sizeof(local));

22 if (rc < 0)

23 {

24 perror("ошибка вызова bind");

25 exit(1);

26 }

27 rc = listen f(s, 5);

28 if (rc)

29 {

30 perror("ошибка вызова listen");

31 exit(1);

32 }

33 s1 = accept(s, NULL, NULL);

34 if (sl < 0)

35 {

36 perror("ошибка вызова accept");

37 exit(1);

38 }

39 rc = recv(s1, buf, 1, 0);

40 if (rc <= 0)

41 {

42 perror("ошибка вызова recv");

43 exit(1);

44 }

45 printf("%c\n", buf[0]);

46 rc = send(sl, “2", 1, 0);

47 if (rc <= 0)

48 perror("ошибка вызова send");

49 exit(0);

50 }

-----------------------------------------------------simples. с

Заповнення адресної структури і отримання сокета

12-20 Заповнюємо структуру sockaddr_in, записуючи в її поля відомі адресу і номер порту, отримуємо сокет типу SOCK_STREAM, який і буде прослуховувати.

Прив'язка відомого порту і виклик listen

21-32 Прив'язуємо відомі порт і адресу, записані в структуру local, до отриманого сокета. Потім викликаємо listen, щоб помітити сокет як той, що прослуховує.

Прийняття з'єднання

33-39 Викликаємо accept для прийому нових з'єднань. Виклик accept блокує виконання програми до тих пір, поки не знадійде запит на з'єднання, після чого повертає новий сокет для цього з'єднання.

Обмін даними

39-49 Спочатку читаємо і друкуємо байт із значенням 1, отриманий від клієнта. Потім посилаємо один байт із значенням 2 назад клієнтові і завершуємо програму.

Тепер можна протестувати клієнт і сервер, запустивши сервер в одному вікні, а клієнт-в іншому. Зверніть увагу, що сервер повинен бути запущений першим, інакше клієнт аварійно завершиться з повідомленням Connection refused (у з'єднанні відмовлено),

bsd: $ simplec

помилка виклику connect: Connection refused

bsd: $

Помилка сталася тому, що при спробі клієнта встановити з'єднання не було сервера, що прослуховує порт 7500.

Тепер слід поступити правильно, тобто запустити сервер до запуску клієнта;

bsd: $ simples

bsd: $ simplec

1 2

bsd: $

bsd: $

Після розгляду елементів API сокетів, можна перейти до більш складного матеріалу.

Лістинг А.7. Функція tcp_client

----------------------------------------— tcp_client, с

1 SOCKET tcp_client(char *hname, char *sname)

2 {

3 struct sockaddr_in peer;

4 SOCKET s;

5 set_address(hname, sname, &peer, "tcp");

6 s = socket (AF_INET, SOCK_STREAM, 0);

7 if (!isvalidsock(s))

8 error (1, errno, " ошибка вызова, socket");

9 if (connect(s, (struct sockaddr *)&peer,

10 sizeof(peer)))

11 error(1, errno, "ошибка вызова connect");

12 return s;

13 }

------------------—---------------------—-—tcp_client.с

Каркас UDP-сервера

Каркас UDP-сервера в основному схожий на каркас TCP-сервера, його відмінна риса - не потрібно встановлювати опцію сокета SO_REUSEADDR і звертатися до системних викликів accept і listen, оскільки UDP - це протокол, що не вимагає логічного з'єднання. Функція main із каркаса приведена в лістингу А.8.

Лістинг А.8. Функція main из каркаса udpserver.skel

-----------------------------------------------udpserver.skel

1 int main(int argc, char **argv)

2 {

3 struct sockaddr_in local;

4 char *hrmme;

5 char *sname;

6 SOCKET s;

7 INIT();

8 if (argc == 2)

9 {

10 hname = NULL;

11 sname = argv[1];

12 }

13 else

14 {

15 hname = argv[1];

16 sname = argv[2];

17 }

18 set_address (hname, sname, &local, "udp");

19 s = socket(AF_INET, SOCK_DGRAM, 0);

20 if (!isvalidsock(s))

21 error(1, errno, "ошибки вызова socket");

22 if (bind(s, (struct sockaddr *) &local,

23 sizeof(local)))

24 error(1, errno, "ошибка вызова bind");

25 server(s, &local);

26 exit(0);

27 }

-----------------—--------------—-------------udpserver.skel

18 Викликаємо функцію set_address для запису в поля змінної local типу sockaddr_in адреси і номера порту, по якому сервер прийматиме датаграми. Зверніть увагу, що замість "tcp" задається третім параметром "udp".

19-24 Отримуємо сокет типу SOCK_DGRAM і прив'язуємо до нього адресу і номер порту, що зберігаються в змінній local.

25 Викликаємо заглушку server, яка буде очікувати на вхідні датаграми.

Щоб створити UDP-версію програми, яка друкує «hello world», слід скопіювати каркас у файл udphelloc.c і замість заглушки вставити наступний код:

static void server(SOCKET s, struct sockaddr_in *localp)

{

struct sockaddr_in peer;

int peerlen;

char buf[1];

for (;;)

{

peerlen = sizeof(peer);

if (recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&peer, &peerlen) < 0)

error(1, errno, "ошибка вызова recvfrom");

if (sendto(s, "hello, world\n", 13, 0, (struct sockaddr *)&peer, peerlen) < 0)

error(1, errno, "ошибка вызова sendto");

}

}

Перед тим, як тестувати сервер, необхідно розробити каркас UDP-клієнта (лістинг А.10). Але спочатку треба вивести останню частину main в бібліотечну функцію udp_server:

#include "etcp.h"

SOCKET udp_server(char *host, char *port);

повертає значення; UDP-сокет, прив'язаний до хосту host і порту port (у разі помилки завершує програму).

Як звичайно, параметри host і port вказують на рядки, що містять відповідно ім'я або IP-адреса хоста і ім'я сервісу або номер порту у вигляді ASCII-рядка.

Лістинг 2.9. Функція udp_server

-—-------------------------------------— udp_server.с

1 SOCKET udp_server(char *hname, char *sname)

2 {

3 SOCKET s;

4 struct sockaddr_in local;

5 set_address (hname, sname, &local, "udp");

6 s = socket(AF_INET, SOCK_DGRAM, 0);

7 if (!isvalidsock(s))

8 error(1, errno, "ошибка вызова socket");

9 if < bind(s, (struct sockaddr *) &local,

10 sizeof(local)))

11 error(1, errno, "ошибка вызова bind");

12 return s;

13 }

—----------------------------—----------------------udp_server. с

Каркас UDP-клієнта

Функція main в каркасі UDP-клієнта виконує в основному запис в поля змінної peer зазначених адреси і номера порту сервера і отримує сокет типу SOCK_DGRAM. Вона показана в лістингу А.10. Весь інший код каркаса такий самий, як для udpserver.skel.

Листинг А.10. Функція main з каркасу udpclient.skel

----------------------—---------------------------—udpclient.skel

1 int main( int argc, char **argv)

2 {

3 struct sockaddr_in peer;

4 SOCKET s;

5 INITO;

6 set_address(argv[1], argv[2], &peer, "udp");

7 s = socket(AFJENET, SOCK_DGRAM, 0);

8 if (!isvalidsock(s))

9 error(1, errno, "ошибка вызова socket");

10 client(s, &peer);

11 exit (0);

12 }

---------------------------------------------------udpclient.skel

Тепер можна протестувати одночасно цей каркас і програму udphello, для чого необхідно скопіювати udpclient.skel в файл udphelloc.c і замість клієнтської заглушки підставити такий код:

static void client(SOCKET s, struct sockaddr_in *peerp)

{

int rc;

int peerlen;

char buf[120];

peerlen = sizeof(*peerp);

if (sendto(s, "", 1, 0, (struct sockaddr *)peerp, peerlen) < 0)

error(1, errno, "ошибка вызова sendto");

rc = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)peerp, &peerlen);

if (rс >= 0)

writef 1, buf, re);

else

error(1, errno, "ошибка вызова recvfrom");

}

Функція client посилає серверу нульовий байт, читає повернену датаграму, виводить її в стандартний пристрій виводу і завершує програму. Функції recvfrom в коді і udphello цілком достатньо одного нульового байта. Після його прийому вона повертає управління основній програмі, яка і посилає у відповідь датаграму.

При одночасному запуску обох програм виводиться звичайне вітання:

bsd: $ udphello 9000 &

[1] 443

bsd: $ updhelloc localuost 9000

hello, world

bsd: $

Як завжди, слід винести стартовий код з main у бібліотеку. Зверніть увагу, що бібліотечній функції на ім'я udp_client (лістинг А.11) передається третій аргумент - адреса структури sockaddr_in; в якій будуть розміщені адреса і номер порту, що були передані в двох перших аргументах.

#include "etcp.h"

SOCKET udp_client( char *host, char *port, struct sockaddr_in *sap);

Значення, що повертається: UDP-сокет и заповнена структура sockaddr_in, (при помилці завершає роботу програми).

Лістинг 2.1 1. Функція udp_client

--------------------------------------------------------udp_client.c

1 SOCKET udp_client(char *hname, char *sname,

2 struct sockaddr_in *sap)

3 {

4 SOCKET s;

5 set_address (hname, sname, sap, "udp");

6 s = socket (AF_INET, SOCK_DGRAM, 0);

7 if (!isvalidsock(s))

8 error(1, errno, "ошибка вызова socket");

9 return s;

10 }

---------------------------------------------------------udp_client.с

Резюме

Таким чином, щоб створити декілька елементарних клієнтів і серверів, знадобилося лише вставити змістовний код замість заглушок.

Використання каркасів і написання бібліотечних функцій закладає той фундамент, на якому далі легко будувати додатки і невеликі тестові програмки для їх перевірки.

Далі звернемося до функції set_address. Вона буде використана в усіх каркасах. Це природна кандидатура на вміщення у бібліотеку стандартних функцій.

Лістинг 2.3. Функція set_address

---------------------------------------------------tcpserver.skel

1 static void set_address(char *hname, char *sname,

2 struct sockaddr_in *sap, char *protocol)

3 {

4 struct servent *sp;

5 struct hostent *hp;

6 char *endptr;

7 short port;

8 bzero(sap, sizeof(*sap));

9 sap->sin_family = AF_INET;

10 if (hname != NULL)

11 {

12 if (!inet_aton(hname, &sap->sin_addr))

13 {

14 hp = gethostbyname(hname); IB if (hp =" NULL)

16 error(1, 0, "неизвестный хост: %s\n", hname);

17 sap->sin_addr = *(struct in_addr *)hp->h_addr;

18 }

19 }

20 else

21 sap->sin_addr, s_addr = htonl(INADDR_ANY);

22 port = strtol(sname, uendptr, 0);

23 if (*endptr == '\0')

24 sap->sin_port = htons(port);

25 else

26 (

21 sp = getservbyname(sname, protocol); 26 if (sp == NULL)

29 error(1, 0, "неизвестный сервис: %s\n", sname);

30 sap->sin_port = sp->s_port;

31 }

32 } -----------------------------------------------------tcpserver.skel

set_address

8-9 обнулити структуру sockaddr_in, в поле адресного сімейства записуємо AF_INET.

10-19 Якщо hname не NULL, то припускаємо, що це числова адреса в стандартній десятковій нотації. Перетворюємо його за допомогою функції inet_aton, якщо inet_aton повертає код помилки, - намагаємося перетворити hname на адресу за допомогою gethostbyname. Якщо і це не виходить, то друкуємо діагностичне повідомлення і завершуємо програму.

20-21 Якщо зухвала програма не вказала ні імені, ні адреси хоста, встановлюємо адресу INADDR_ANY.

22-24 перетворювати sname в ціле число. Якщо це вдалося, то записуємо номер порту в мережевому порядку.

27-30 В іншому випадку припускаємо, що це символічна назва сервісу і викликаємо getservbyname для отримання відповідного номера порту. Якщо сервіс невідомий, друкуємо діагностичне повідомлення і завершуємо програму. Зауважте, що getservbyname вже повертає номер порту в мережевому порядку.

Оскільки іноді доводиться викликати функцію set_address безпосередньо, тут наводиться її прототип:

 # Include "etcp.h"

 void set_address (char * host, char * port, struct sockaddr_in * sap, char * protocol);

Остання функція - error - показана в лістингу А.4. Це стандартна діагностична процедура.

# Include "etcp.h"

void error (int status, int err, char * format, ...);

Якщо status не дорівнює 0, то error завершує програму після друку діагностичного повідомлення, в іншому випадку вона повертає керування. Якщо err не дорівнює 0, то вважається, що це значення системної змінної errno. При цьому на кінець повідомлення дописується відповідний цьому значенню рядок і числове значення коду помилки.

Функцію error також слід додати в бібліотеку функцій. Таким чином, можна створити цілий арсенал каркасів і бібліотечних функцій.
1   2   3

скачати

© Усі права захищені
написати до нас