Почти все приложения которые взаимодействуют с сетью используют библиотеку, поддерживаемую операционной системой, - сокеты (sockets). Ее реализации бывают разными, но в целом принцип работы везде одинаков.
Далее в качестве примера рассмотрим сервер получения времени и клиент к нему. Скорее всего, вам уже известно, что такое клиент и сервер, но вкратце объясню. Грубо говоря, клиент - сетевое приложение которое обращается к серверу с каким то запросом. А сервер - сетевое приложение, которое обрабатывает запросы от клиентов. Зачастую, сервер - приложение которое запущено как демон, то есть после загрузки в память операционной системы, не завершается, а ждет какого-то события и потом его обрабатывает. Так же следует знать, что обычно один клиент обращается всего лишь к одному серверу, но сервер, может одновременно обрабатывать запросы нескольких клиентов, хотя в этой схеме бывают и исключения (например, adc-клиент).
Сервер времени и даты использует протокол TCP. Вот код такого простого сервера:
#include <sys/socket.h> // собственно, сокеты#include <string.h> // отсюда мы берем memset()#include <resolv.h> // тут объявлены структуры sockaddr#include <stdio.h> // snprintf()#define BUFSIZE 128 // максимальный размер буфера под выводимую строку#define LISTENQ 256 // максимальный размер очереди клиентов#define bzero(x,y) memset((x),0,(y)) // макрос позволяющий пользоваться bzero вместо memsetint main(){int listenfd, connfd; // дескрипторы сокетовstruct sockaddr_in servaddr; // структура, которой задается адрес сокетаchar buff[BUFSIZE]; // массив чаров. Туда будет помещаться строка, которую сервер отдаст клиентуtime_t ticks; // сюда будет помещено время в секундахlistenfd = socket(AF_INET, SOCK_STREAM, 0); // просим операционную систему создать для нас сокет протокола IPv4 (AF_INET) / TCP (SOCK_STREAM). Эта функция вернет дескриптор сокета.bzero(&servaddr, sizeof(servaddr)); // зануляем память структуры, это необходимо. Использовать bzero вместо memset сложилось исторически, еще с появления сокетов на BSD. Хотя разницы нет.servaddr.sin_family = AF_INET; // структура для IPv4servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // задаем адресservaddr.sin_port = htons(1313); // задаем порт. Стандартно для сервера времени - 13, но его от обычного пользователя мы не сможем использовать.bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); // "привязываем" сокет к адресу и портуlisten(listenfd, LISTENQ); // говорим операционной системе ожидать подключений к нашему сокетуfor(;;) {connfd = accept(listenfd, (struct sockaddr*) NULL, NULL); // когда клиент подключается к серверу, то операционная система создаст сокет. Собственно этой функцией и задаем подключение клиентов, она возвращает дескриптор на новый сокет клиентаticks = time(NULL); // получаем текущее время в секундахsnprintf(buff, sizeof(buff), "%.24s\er\en\n", ctime(&ticks)); // переводим секунды в удобный для восприятия форматsend(connfd, buff, strlen(buff), 0); // отправляем данные (на самом деле ничего не отправляется тут)close(connfd); // закрываем соединение}return 0;}
Предупреждаю, что возвращаемые значения функций там не обрабатываются, а они возвращают 0 или номер ошибки. Поэтому, если у вас эта программа не будет работать, то скорее всего в работе какой-то функции возникает ошибка. Хорошим тоном, будет создать для всех функций обвертки и проверять в них возвращаемые значения, если функция вернет не ноль, то выводить ошибку и завершать программу.
Еще хочу сказать про функцию send, казалось бы, по названию можно подумать что она что-то там куда-то отправляет, но это не так. На самом деле она всего лишь помещает данные в буфер TCP (этот буфер уникальный для каждого порта) операционной системы, а уже она будет что-то там пытаться отправить. То есть, ничто не может гарантировать успешную отправку данных.
Ну что, компилируем (обратите внимание, никакие динамические библиотеки подключать не надо), запускаем, программа "висит". Хорошо. Пробуем подключиться с помощью telnet:
$ telnet 127.0.0.1 1313Trying 127.0.0.1...Connected to 127.0.0.1.Escape character is '^]'.Sat Feb 26 15:46:12 2011Connection closed by foreign host.
Все работает!
Далее напишем клиент:
#include <sys/socket.h>#include <string.h>#include <resolv.h>#include <stdio.h>#define BUFSIZE 128#define bzero(x,y) memset((x),0,(y))int main(){int connfd;struct sockaddr_in servaddr;char buff[BUFSIZE];char servIP[] = "127.0.0.1"; // задаем IP адрес сервераconnfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, servIP, &servaddr.sin_addr); // заполняем поле адреса (32 бита) с помощью функции inet_ptonservaddr.sin_port = htons(1313);connect(connfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); // подключаемся к серверуrecv(connfd, buff, BUFSIZE, 0); // получаем данные от сервера (вернее получаем данные из буфера TCP)printf("%s", buff);close(connfd);return 0;}
Здесь, так же, обработки ошибок нет. Я не стал их делать, чтобы не загромождать код, ведь, это всего лишь примеры.
Кстати, IP адрес и порт которые помещаются в struct sockaddr_in должны иметь сетевой порядок следования байтов. Для этого пользуются специальными функциями преобразования:
htons (host TO net short) - для преобразования порта.
htonl (host TO net long) - для преобразования адресов.
Функция inet_pton преобразует массив символов (строку) в адрес (32 бита для IPv4) с сетевым порядком.
В функциях connect, bind и accept делается преобразование типа к struct sockaddr для их совместимости с различными протоколами IP. Подробно об этом не буду говорить, скажу лишь, то, что это преобразование нужно делать.
Возможно, вы заметили что в коде клиента нет функции bind. Но ведь клиенту тоже нужен порт, с которого он будет отправлять данные? Дело в том, что bind можно и не делать, если так, то операционная система назначит порт для клиента автоматически.
Кстати, полное описание, прототипы, возвращаемые и значения всех функций можно найти в man.
Кстати, полное описание, прототипы, возвращаемые и значения всех функций можно найти в man.
Это были всего лишь основы. Если вам интересно сетевое программирование, то очень советую почитать книгу UNIX Network Programming (рус. UNIX. Разработка сетевых приложений), автор W. R. Stevens. В ней все очень подробно расписывается, как про сокеты так и про устройство основных протоколов интернета.