Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Zemskov_compnets.pdf
Скачиваний:
133
Добавлен:
18.04.2015
Размер:
705.68 Кб
Скачать

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

5. Программирование сокетов

Сокет´ (socket) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться на одном компьютере или на различных компьютерах, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Интерфейс сокетов впервые появился в BSD Unix. Программный интерфейс сокетов описан в стандарте POSIX и в той или иной мере поддерживается всеми современными операционными системами.

5.1. Использование классических блокирующих сокетов

5.1.1.Алгоритм работы сервера и клиента

Сустановлением соединения. Порядок использования блокирующих сокетов с установлением соединения:

На стороне сервера:

1.Инициализировать библиотеку Winsock (WSAStartup, только для платформы Windows).

2.Создать сокет (socket).

3.Связать сокет (bind).

4.Перевести его в слушающий режим (listen).

5.Принять запрос на соединение (accept).

6.Получить (recv) или отправить (send) данные.

7.Отключить сокет (shutdown).

8.Закрыть сокет (close, для Windows — closesocket).

9.Закрыть библиотеку Winsock (WSACleanup, только для платформы Windows).

На стороне клиента:

1.Инициализировать библиотеку Winsock (WSAStartup, только для платформы Windows)

2.Создать сокет (socket).

3.Послать запрос на соединение (connect).

4.Отправить (send) или получить (recv) данные.

5.Отключить сокет (shutdown).

6.Закрыть сокет (close, для Windows — closesocket).

7.Закрыть библиотеку Winsock (WSACleanup, только для платформы Windows).

Без установления соединения. Порядок использования блокирующих сокетов без установления соединения (одинаково для сервера и клиента):

1. Инициализировать библиотеку Winsock (WSAStartup, только для платформы Windows).

— 30 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

2.Создать сокет (socket).

3.Связать сокет (bind).

4.Получить (recvfrom) или отправить (sendto) данные.

5.Закрыть сокет (close, для Windows — closesocket).

6.Закрыть библиотеку Winsock (WSACleanup, только для платформы Windows).

5.1.2.Функции API сокетов

Для использования сокетов в системе Windows необходимо подключать заголовочный файл <winsock2.h>, в Linux — <sys/socket.h>. Если приложение разрабатывается в системе Microsoft Visual C++, то к проекту надо подключить библиотеку ws2_32.lib. Для этого надо выполнить команду главного меню í Project í Settings Link Category: sGeneral

и дописать имя библиотечного файла в поле «Object/library modules» (рис. 5.1).

Рис. 5.1. Подключение библиотеки ws2_32.lib в Microsoft Visual C++

Библиотека Winsock поддерживает два вида сокетов: синхронные (блокирующие) и асинхронные (неблокирующие). Синхронные сокеты задерживают управление на время выполнения операции, а асинхронные сокеты возвращают его немедленно, продолжая выполнение в фоновом режиме, а закончив работу, уведомляют об этом вызывающий код. В данном разделе рассмотрим только блокирующие сокеты.

Создание серверного или клиентского сокета. Дескриптор (описатель) сокета — это переменная типа SOCKET. Сокет создаётся с помощью функции

SOCKET socket(int domain, int type, int protocol);

Возвращаемое значение: дескриптор сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка. Входной параметр domain — константа, указывающая, какой домен нужен сокету. Обычно используются домены AF_INET (т. е. Internet) и AF_LOCAL (применяется для межпроцессного взаимодействия (IPC) на одной и той же машине).

— 31 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

С помощью параметра type задаётся тип создаваемого сокета. Чаще встречаются следующие значения:

SOCK_STREAM — обеспечивают надёжный дуплексный протокол на основе установления логического соединения. Если говорится о семействе протоколов TCP/IP, то это TCP;

SOCK_DGRAM — обеспечивают ненадежный сервис доставки датаграмм. В рамках TCP/IP это протокол UDP;

SOCK_RAW — предоставляют доступ к некоторым датаграммам на уровне протокола IP. Они используются в особых случаях, например для просмотра всех ICMP-сообщений.

Параметр protocol показывает, какой протокол следует использовать с данным сокетом. В контексте TCP/IP он обычно неявно определяется типом сокета, поэтому в качестве значения задают 0. Иногда, например, в случае rawсокетов, имеется несколько возможных протоколов, тогда данный параметр необходимо задавать явно.

Система не освобождает ресурсы, выделенные при вызове socket(), пока не произойдет вызова closesocket(). Каждый вызов socket() должен иметь соответствующий вызов closesocket() во всех возможных путях исполнения. Результатом выполнения системного вызова closesocket() является только обращение к интерфейсу для закрытия сокета, а не закрытие самого сокета. Другими словаи, это всего лишь команда для операционной системы закрыть сокет, а само освобождение ресурсов будет выполнено системой позже, в соответствии с алгоритмами её работы.

Подключение клиента к серверу. Соединение с удалённым сокетом устанавливается с помощью функции

int connect(SOCKET s, struct sockaddr *peer, int peer_len);

Возвращаемое значение: 0 — нормально; −1 (Linux) или не 0 (Windows) — ошибка. Параметр s — дескриптор сокета, который получен в результате вызова функции socket(). Параметр peer указывает на структуру, в которой хранится адрес удаленного хоста и некоторая дополнительная информация. Для домена AF_INET это структура типа sockaddr_in. Параметр peer_len содержит размер структуры (в байтах), на которую указывает peer.

После установления соединения можно передавать данные.

Передача и приём данных. В системе Linux передача и приём информации выполняется с помощью функций read() и write(), которым передаётся дескриптор сокета. В системе Windows используются функции recv() (приём) и send() (передача данных):

int recv(SOCKET s, void *buf,

— 32 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

size_t len, int flags); int send(SOCKET s, const void *buf,

size_t len, int flags);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 в случае ошибки.

Параметры buf и len — адрес буфера для приёма или отправки информации и его длина. Значение параметра flags зависит от системы, но и Linux,

иWindows поддерживают следующие флаги:

MSG_OOB — следует послать или принять срочные данные;

MSG_PEEK — используется для просмотра поступивших данных без их удаления из приёмного буфера. После возврата из системного вызова данные еще могут быть получены при последующем вызове read()

или recv();

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

При работе с UDP нужны еще системные вызовы recvfrom() и sendto(). Они очень похожи на recv() и send(), но позволяют при отправке датаграммы задать адрес назначения, а при приёме — получить адрес источника:

int recvfrom(SOCKET s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);

int sendto(SOCKET s, const void *buf, size_t len, int flags, const struct sockaddr *to, int tolen);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 при ошибке.

Первые четыре параметра: s, buf, len и flags — такие же, как в вызовах recv() и send(). Параметр from в вызове recvfrom() указывает на структуру, в которую ядро помещает адрес источника пришедшей датаграммы. Длина этого адреса хранится в целом числе, на которое указывает параметр fromlen. Обратите внимание, что fromlen — это указатель на целое. Аналогично параметр to в вызове sendto() указывает на адрес структуры, содержащей адреса назначения датаграммы, а параметр tolen — длина этого адреса. Заметьте, что to — это целое, а не указатель.

Функции send() и recv() не являются атомарными и нет никакой rарантии, что затребованные данные будут действительно отправлены или получены в результате одного вызова этих функций. Приходится проверять возвращаемое значение (фактическое количество принятых или отправленных байт)

— 33 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

и повторно принимать или отправлять данные до тех пор, пока все они не будут приняты или переданы.

Связывание серверного сокета. На стороне сервера необходимо привязать адрес интерфейса и номер порта к прослушивающему сокету. Для этого предназначен вызов bind():

int bind(SOCKET s, const struct sockaddr *name, int namelen);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор слушающего сокета. С помощью параметров name и namelen передаются порт и сетевой интерфейс, которые нужно прослушивать. Обычно в качестве адреса задается константа INADDR_ANY. Это означает, что будет принято соединение, запрашиваемое по любому интерфейсу. Если хосту с несколькими сетевыми адресами нужно принимать соединения только по одному интерфейсу, то следует указать IP-адрес этого интерфейса. Параметр namelen — длина структуры sockaddr_in.

Перевод серверного сокета в слушающим режим. После привязки локального адреса к сокету нужно перевести сокет в режим прослушивания входящих соединений с помощью системного вызова listen(). Его единственная задача — пометить сокет как прослушивающий. Когда хосту поступает запрос на установление соединения, ядро ищет в списке прослушивающих сокетов тот, для которого адрес назначения и номер порта соответствуют указанным в запросе.

int listen(SOCKET s, int backlog);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор сокета, который нужно перевести в режим прослушивания. Параметр backlog — максимальное число ожидающих, но еще не принятых соединений. Следует отметить, что это не максимальное число одновременных соединений с данным портом, а лишь максимальное число частично установленных соединений, ожидающих в очереди, пока приложение их примет (описание системного вызова accept() дано ниже). Традиционно значение параметра backlog() — не более пяти соединений, но в современных реализациях, которые должны поддерживать приложения с высокой нагрузкой, например, Web-сервера, оно может быть намного больше. Поэтому, чтобы выяснить его истинное значение, необходимо изучить документацию по конкретной системе. Если задать значение, большее максимально допустимого, то система уменьшит его, не сообщив об ошибке.

Слушающие соединения (listening connections) — это пассивные серверные сокеты, ожидающие клиента. Как только клиент делает новый запрос, сервер

— 34 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

активизирует новый сокет, выделенный для этого конкретного соединения, и затем возвращается к слушанию. Слушающие серверные сокеты должны быть предварительно связаны с некоторым портом, представляющим реализуемую ими службу (фактически клиент собирается подключаться через этот порт).

Приём сервером запроса от клиента на установку соединения. И последний вызов, который будет здесь рассмотрен, — это accept(). Он служит для приёма соединения, ожидающего во входной очереди. После того как соединение принято, его можно использовать для передачи данных, например, с помощью вызовов recv() и send(). В случае успеха accept() возвращает дескриптор нового сокета, по которому и будет происходить обмен данными. Номер локального порта для этого сокета такой же, как и для прослушивающего сокета. Адрес интерфейса, на который поступил запрос о соединении, называется локальным. Адрес и номер порта клиента считаются удаленными.

Обратите внимание, что оба сокета имеют один и тот же номер локального порта. Это нормально, поскольку TCP-соединение полностью определяется четырьмя параметрами: локальным адресом, локальным портом, удалённым адресом и удалённым портом. Поскольку удалённые адрес и порт для этих двух сокетов различны, то ядро может отличить их друг от друга.

int accept(SOCKET s, struct sockaddr *addr, int *addrlen);

Функция accept() блокируется до тех пор, пока от клиента не поступит запрос соединения, после чего она возвращает новый сокет. Возвращаемое значение: дескриптор этого нового сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка.

Параметр s — дескриптор слушающего сокета. Вызов accept() возвращает адрес подключившегося к серверу клиента в структуре sockaddr_in, на которую указывает параметр addr. Целому числу, на которое указывает параметр addrlen, ядро присваивает значение, равное длине этой структуры. Часто нет необходимости знать адрес клиента, тогда в качестве addr и addrlen передаётся NULL.

5.1.3.Пример использования блокирующих TCP-сокетов в однопоточном сервере и клиенте

Рассмотрим пример сервера и клиента Winsock (листинги 2 и 3), написанные на языке C (консольные приложения). Здесь клиент соединяется с сервером и посылает ему сообщение, состоящее из пяти символов. Сервер принимает сообщение и выводит его на экран. Сервер является однопоточным приложением, т. е. может одновременно работать только с одним клиентом.

Листинг 2: Пример сервера WinSock на C

— 35 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

//Пример использования классических блокирующих TCP-сокетов в MS Visual C //======= server.c (консольное приложение) ============

#include<Winsock2.h> // к проекту надо подключить библиотеку Ws2_32.lib

//(Project | Settings, вкладка Link, Category: General, поле Object/library modules) #include<stdio.h>

#include<winbase.h> // Для Sleep #define BUFNUM 5 // Длина буфера приёма

#define HOSTNAME "localhost" // Имя сервера ( используем локальную машину) #define PORT 1000 // Номер порта

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

SOCKET mysock, mysock1; // две переменные типа <<сокет>>

WSADATA wsaData; // Данные, возвращаемые функцией инициализации библиотеки

struct sockaddr_in myaddr, myaddr1; // Адреса сокетов

int err,len1,len2; // Код ошибки, длина сообщения

// сначала в буфере приёма только подчерки и 0 в конце:

char buf[BUFNUM+1]={’_’,’_’,’_’,’_’,’_’,’\0’};

struct hostent FAR *hp;

err=WSAStartup(0x0101,&wsaData); // Инициализируем библиотеку Winsock

//(вместо версии 1.1 можно использовать 2.2). if(err<0){ // если была ошибка, то выведем её код и закончим

printf("Winsock starting error %i",WSAGetLastError()); exit(1);

}

mysock=socket(AF_INET,SOCK_STREAM,0); // создали сокет,

// область имён - INET, тип - TCP (с установлением соединения), протокол по умолчанию

if(mysock==INVALID_SOCKET){ // если ошибка, то выведем её код, закроем

// библиотеку Winsock и закончим

printf("\nCannot create socket, error %i\n",WSAGetLastError());

WSACleanup(); exit(2);

}

hp = gethostbyname(HOSTNAME); // перевели имя хоста в адрес

memcpy(&myaddr.sin_addr, hp->h_addr, hp->h_length);

myaddr.sin_family = hp->h_addrtype;

//myaddr.sin_addr.s_addr = INADDR_ANY; // если хотим устанавливать

//соединение с любой сетевой службой, то последние две строки

//надо заменить данной функцией.

myaddr.sin_port=htons(1000); // номер порта, переведённый в ’сетевой порядок’

err=bind(mysock, (struct sockaddr *) &myaddr,

sizeof(myaddr)); // связываем сокет

if(err<0){ // если была ошибка

printf("\nCannot bind socket, error %i\n",WSAGetLastError());

closesocket(mysock); WSACleanup(); exit(3);

}

ioctlsocket(mysock, FIONBIO, 0); // устанавливаем блокирующий режим

// (данный режим устанавливается по умолчанию, поэтому можно опустить)

printf("\nListening...\n");

err=listen(mysock, 1); // переводим сокет в прослушивающий режим,

//длина очереди - только один вызов if (err<0){ // если была ошибка

printf("\nCannot listen socket, error %i\n", WSAGetLastError());

closesocket(mysock); WSACleanup(); exit(4);

}

len1=sizeof(myaddr1);

mysock1=accept(mysock, (struct sockaddr FAR*)&myaddr1, &len1);

//приняли запрос на соединение

//(accept ждёт, пока не будет выполнен connect клиента) if(mysock1==INVALID_SOCKET){ // если ошибка

printf("\nError accepting connection, error %i\n",WSAGetLastError()); closesocket(mysock); WSACleanup(); exit(5);

36 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

62}

63printf("\nAccepted %i.%i.%i.%i\n",myaddr1.sin_addr.S_un.S_un_b.s_b1,

64

 

myaddr1.sin_addr.S_un.S_un_b.s_b2,

65

 

myaddr1.sin_addr.S_un.S_un_b.s_b3,

66

 

myaddr1.sin_addr.S_un.S_un_b.s_b4); // кто нас вызвал

67

Sleep(1000); printf("\nWaiting message...\n"); len1=0;

68

while(len1<BUFNUM){ // принимаем символ за символом сообщение длиной BUFNUM

69

 

len2=recv(mysock1,&buf[len1],BUFNUM-len1,0); len1+=len2;

70

 

printf("\nReceived message:\t%s,\tlength is %i\n", buf, len2);

71

}

// (принять всю строку нельзя, т.к. сообщения в сети могут

72

 

//

дробиться на пакеты и наоборот, слипаться)

73

buf[BUFNUM]=’\0’; // конец строки

74

Sleep(1000); // чтобы на экране всё менялось не слишком быстро

 

 

75

printf("\nReceived:\t%s;\tlength is %i\n",buf,len1);

76

shutdown(mysock1, 2); // 2 - закрыли сокет и для чтения, и для записи

77

closesocket(mysock1);

 

78

closesocket(mysock);

 

79

WSACleanup(); // закрыли библиотеку Winsock (в Unix этого нет)

80

Sleep(5000); // подождали 5 сек

 

 

 

81

return 0;

 

82

}

 

 

 

 

 

 

После того, как сервер принял запрос клиента на установку соединения, сервер ждёт получения от клиента нужного количества символов (параметр BUFNUM). Если серверу не было бы известно заранее, сколько символов хочет передать клиент, то нам бы пришлось усложнить протокол взаимодействия. Например, можно было бы установить следующее правило: первые 4 (или 8) байт, которые клиент отсылает серверу, должны содержать длину всего сообщения.

Пока не написан клиент, программу-сервер можно отлаживать с помощью Telnet (Windows system32 telnet.exe) или HyperTerminal (Пуск | Программы | Стандартные | Связь). Первая программа работает в режиме командной строки, вторая — поддерживает графический интерфейс. После запуска HyperTerminal надо создать новое соединение (с любым именем), в качестве параметров соединения ввести адрес сервера (если сервер запущен на том же компьютере, то localhost) и номер порта (рис. 5.2). Затем можно вводить с клавиатуры любые символы, они будут пересылаться серверу и отображаться в его консольном окне.

Рис. 5.2. Проверка работы приложения-сервера с помощью HyperTerminal

Листинг 3: Пример клиента Winsock на C

— 37 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

//========== client.c (консольное приложение) ============

#include<Winsock2.h> #include<stdio.h> #include<winbase.h>

#define HOSTNAME "localhost" // Имя машины, к которой будем подключаться #define PORT 1000 // Номер порта

#define BUFNUM 5

char buf[BUFNUM+1]={’a’,’b’,’c’,’d’,’e’,0};

//это сообщение, которое будем посылать серверу

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

SOCKET mysock;

struct sockaddr_in myaddr;

int err,len;

WSADATA wsaData;

struct hostent FAR *hp;

err=WSAStartup(0x0101,&wsaData); // инициализация Winsock

mysock=socket(AF_INET,SOCK_STREAM,0); // создание TCP-сокета

if(mysock==0)

printf("\nCannot create socket\n");

hp = gethostbyname(HOSTNAME);

if (hp == NULL){

printf("\nInvalid host name\n");

WSACleanup();

exit(1);

}

memcpy(&myaddr.sin_addr, hp->h_addr, hp->h_length);

myaddr.sin_family = hp->h_addrtype;

myaddr.sin_port=htons(1000);

printf("Connecting to the sever...");

err=-1;

ioctlsocket(mysock, FIONBIO, 0); // блокирующий режим

while(err<0){ // попытка соединения с сервером:

err=connect(mysock,(struct sockaddr*)&myaddr,sizeof(myaddr));

//connect ждёт, пока запрос не попадёт в очередь сервера

//или пока сервер не ответит, что связь невозможна

if(err==SOCKET_ERROR)

printf("\nCannot connecting, error %i\n", WSAGetLastError()); Sleep(3000);

}

printf("\nConnecting OK\n"); Sleep(1000);

len=send(mysock,buf,sizeof(char)*BUFNUM,0); // отправка даных серверу

if(len==0)

printf("\nError sending\n"); // ошибка передачи

else

printf("\nSending: %s, actual sended length is %i\n", buf, len);

Sleep(4000);

shutdown(mysock,2);

closesocket(mysock); // закрытие сокета

WSACleanup();

// деинициализация Winsock

 

return 0;

 

 

}

 

 

 

 

 

Обе программы компилируются как в Microsoft Visual C++, так и в C++ Builder.

Поскольку используются блокирующие сокеты, то клиент и сервер должн работать строго синхронно: если сервер ожидает прихода сообщения, то кли-

— 38 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

ент обязательно должен это сообщение отправить, иначе цикл ожидания на стороне сервера никогда не закончится.

Сначала надо запустить приложение-сервер, дождаться вывода на экран сообщения Listening (Слушаю), затем запустить приложение-клиент (рис. 5.3).

Рис. 5.3. Проверка работы сервера и клиента

5.1.4.Передача сообщений без установления соединения с помощью UDPдейтаграмм

Дейтаграмма (datagram) — блок информации, посланный как пакет сетевого уровня без предварительного установления соединения и создания виртуального канала. Дейтаграмма представляет собой единицу информации (protocol data unit, PDU) в протоколе для обмена информацией на сетевом (в случае протокола IP — IP-дейтаграммы) и транспортном (в случае протокола UDP — UDP-дейтаграммы) уровнях эталонной модели OSI.

UDP (User Datagram Protocol, протокол пользовательских датаграмм) — это транспортный протокол для передачи данных в сетях IP без установления соединения. Он является одним из самых простых протоколов транспортного уровня модели OSI. В отличие от TCP, UDP не гарантирует доставку пакета, поэтому аббревиатуру иногда расшифровывают как Unreliable Datagram Protocol (протокол ненадёжных дейтаграмм). При передаче пакетов может нарушаться порядок доставки, некоторые пакеты могут теряться, а некоторые дублироваться. Для обнаружения ошибок при передаче пакетов протокол UDP использует контрольные суммы в заголовках пакетов. Протокол UDP позволяет гораздо быстрее и эффективнее доставлять данные для приложений, которым требуется большая пропускная способность линий связи, либо требуется малое время доставки данных.

В листинге 4 приведён пример консольного приложения, которое ожидает получения UDP-дейтаграммы, а в листинге 5 — консольное приложение, кото-

— 39 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

рое отправляет UDP-дейтаграмму по адресу, указанному в командной строке. На рис. 5.4 показано, как выглядит результат работы обеих программ.

Рис. 5.4. Проверка работы обоих приложений

Листинг 4: Пример приложения Winsock, принимающего дейтаграмму

1

 

#include <winsock2.h>

 

 

 

2

 

#include <stdio.h>

 

 

3

 

#include <conio.h>

// для _getch()

 

4

 

 

 

 

 

5

 

int main(void) {

 

 

6

 

 

 

 

 

 

 

 

 

 

7

 

WSADATA wsaData;

 

 

8

 

SOCKET ReceivingSocket;

 

9

 

struct sockaddr_in

ReceiverAddr, SenderAddr;

 

10

 

unsigned short Port = 1000;

 

11

 

char ReceiveBuf[1024];

 

12

 

int

BufLength = 1024;

 

 

 

 

 

 

13

 

int

SenderAddrSize = sizeof(SenderAddr);

 

14

 

int

Ret;

 

 

15

 

 

 

 

 

16

 

system("chcp 1251"); // кодовая страница для вывода символов кириллицы.

 

17

 

 

 

 

 

18

 

// Инициализация библиотеки Winsock:

 

 

 

 

 

 

19

 

if

((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0) {

 

20

 

 

printf("Ошибка инициализации winsock %i", WSAGetLastError()); exit(1);

 

21

 

}

 

 

 

22

 

 

 

 

 

23

 

// Создаём новый сокет для приёма дейтаграмм:

 

 

 

 

 

 

24

 

if

((ReceivingSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))

 

25

 

 

== INVALID_SOCKET) {

 

26

 

 

printf("Ошибка создания сокета %i", WSAGetLastError());

 

27

 

 

WSACleanup(); exit(2);

 

28

 

}

 

 

 

29

 

 

 

 

 

 

 

 

 

30

 

// Мы хотим получать дейтаграммы со всех интерфейсов:

 

31

 

ReceiverAddr.sin_family = AF_INET;

 

32

 

ReceiverAddr.sin_port = htons(Port);

 

33

 

ReceiverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

 

34

 

 

 

 

 

35

 

// Связываем сокет:

 

 

 

 

 

 

 

36

 

if

(bind(ReceivingSocket, (struct sockaddr *)&ReceiverAddr, sizeof(ReceiverAddr))

 

37

 

 

== SOCKET_ERROR) {

 

38

 

 

printf("Ошибка связывания сокета %i", WSAGetLastError());

 

39

 

 

closesocket(ReceivingSocket); WSACleanup(); exit(3);

 

40

 

}

 

 

 

41

 

 

 

 

 

 

 

 

 

42

 

printf("\nМы готовы получить 1 дейтаграмму с любого интерфейса на порту %d...\n",

 

43

 

 

Port);

 

 

44

 

 

 

 

 

 

 

 

 

 

 

— 40 —

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации, 2012

45// Получаем дейтаграмму:

46if ((Ret = recvfrom(ReceivingSocket, ReceiveBuf, BufLength, 0,

47

(struct sockaddr *)&SenderAddr, &SenderAddrSize)) == SOCKET_ERROR) {

48

printf("Ошибка recvfrom %s\n", WSAGetLastError());

49

closesocket(ReceivingSocket);

50

WSACleanup(); exit(4);

51

}

52

 

 

53

ReceiveBuf[Ret] = ’\0’;

54

printf("Получено: %d байт с адреса %s. Сообщение: ’%s’\n",

55

Ret, inet_ntoa(SenderAddr.sin_addr), ReceiveBuf);

56

 

57

// Закрываем сокет:

 

 

58

closesocket(ReceivingSocket);

59

 

60

// Выгружаем библиотеку Winsock:

61

WSACleanup();

62

 

63

printf("Нажмите любую клавишу...\n");

 

 

64

_getch();

65

return 0;

66

}

 

 

Листинг 5: Пример приложения Winsock, отсылающего дейтаграмму

1

#include <winsock2.h>

2

#include <stdio.h>

 

3

#include <conio.h>

// для _getch()

4

 

 

 

 

 

5

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

6

 

 

 

7

WSADATA wsaData;

 

8

SOCKET SendingSocket;

9

struct sockaddr_in

ReceiverAddr;

10

unsigned short Port = 1000;

 

 

 

 

11

int

Ret;

 

12

char *msg = "Привет";

13

int

lmsg = strlen(msg);

14

 

 

 

15

system("chcp 1251"); // кодовая страница для вывода символов кириллицы.

16

 

 

 

 

 

 

17

if

(argc <= 1) { // если программа вызвана без параметров:

18

 

printf("Использование: udp_sender <IP>\n"); exit(1);

19

}

 

 

20

 

 

 

21

// Инициализация библиотеки Winsock:

22

if

((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0) {

 

 

 

23

 

printf("Ошибка WSAStartup %i\n", WSAGetLastError());

24

 

exit(2);

 

25

}

 

 

26

 

 

 

27

// Создаём новый сокет для отсылки дейтаграмм:

28

if

((SendingSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))

29

 

== INVALID_SOCKET) {

30

 

printf("Ошибка создания сокета %i\n", WSAGetLastError());

31

 

WSACleanup(); exit(3);

32

}

 

 

33

 

 

 

34

// Адресная информация о получателе дейтаграмм:

 

 

35

ReceiverAddr.sin_family = AF_INET;

36

ReceiverAddr.sin_port = htons(Port);

 

 

 

 

— 41 —

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]