Перевод с дополнениями. Оригинал — тут>>>.
Как правило — два процесса общаются друг с другом с помощью одного из Inter Process Communication (IPC) механизма ядра, таких как:
- pipe
- очереди сообщений (Message queues)
- общая память (shared memory)
Кроме перечисленных IPC — в ядре присутствует много других возможностей, но что если процессам необходимо обмениваться данными по сети?
Тут используется ещё один механизм IPC — сокеты.
Содержание
Что такое сокет?
Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Кратко говоря — существует два типа сокетов — UNIX-сокеты (или сокеты домена UNIX — Unix domain sockets) и INET-сокеты (IP-сокеты, network sockets).
UNIX-сокеты чвляются частью механизма IPC и позволяют обмен данными в обоих направлениях между процессами, работающими на одной машине.
INET-сокеты в свою очередь представляют собой механизм, позволяющий выполнять коммуникацию между процессами по сети.
Грубо говоря — если UNIX-сокет использует файл в файловой системе, то INET-сокет — требует присваивания сетевого адреса и порта.
Больше про сокеты:
- Unix domain socket
- Network socket
- Что такое сокет?
- Сокеты
- What is a socket?
Коммуникация в среде TCP/IP происходит по клиент-серверной модели, т.е. — клиент инициализирует связь, а сервер его принимает.
Ниже — пример сервера, который будет работать как демон и ожидать подключения клиента, а при инициализации клиентом соединения — передаст ему дату и время.
Socket сервер
Наш сервер будет выглядеть следующим образом:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <time.h> int main(int argc, char *argv[]) { int listenfd = 0, connfd = 0; struct sockaddr_in serv_addr; char sendBuff[1025]; time_t ticks; listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, '0', sizeof(serv_addr)); memset(sendBuff, '0', sizeof(sendBuff)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(5000); bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(listenfd, 10); while(1) { connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); ticks = time(NULL); snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks)) write(connfd, sendBuff, strlen(sendBuff)); close(connfd); sleep(1); } }
Собираем, запускаем:
[simterm]
$ gcc server.c -o server $ ./server & [1] 26182
[/simterm]
Проверяем:
[simterm]
$ netstat -anp --tcp | grep server (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (servers and established) tcp 0 0 0.0.0.0:5000 0.0.0.0:*
[/simterm]
Флаг --tcp
для netstat
указывает на то, что требуется вывести информацию только по INET-сокетам.
Самый простой способ получить данные от нашего сервера — с помощью telnet
, проверяем ещё раз:
[simterm]
$ telnet localhost 5000 Trying ::1... Connection failed: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Tue May 16 12:43:24 2017 Connection closed by foreign host.
[/simterm]
Данные получены:
…
Tue May 16 12:43:24 2017
…
Теперь — давайте рассмотрим сам код сервера.
... listenfd = socket(AF_INET, SOCK_STREAM, 0); ...
- с помощью вызова функции
socket()
в области ядра создаётся неименованный сокет, и возвращается его socket descriptor - первым аргументом этой функции передаётся тип домена. Т.к. мы будем использовать сеть — то используем тип сокета
AF_INET
(IPv4). - вторым аргументом —
SOCK_STREAM
, который указывает на тип протокола. Для TCP — это будетSOCK_STREAM
, для UDP —SOCK_DGRAM
- третий аргумент оставляем по умолчанию — тут ядро само решит какой тип протокола использовать (т.к. мы указали
SOCK_STREAM
— то будет выбран TCP)
Далее — вызывается функция bind()
:
... bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); ...
bind()
создаёт сокет используя параметры из структурыserv_addr
(протокол, IP-адрес и порт)
... listen(listenfd, 10); ...
- вызов функции
listen()
со вторым аргументом 10 указывает на макс. допустимое кол-во подключений. Первым аргументом — передаётся дескриптор сокета, который необходимо прослушивать.
Далее — accept()
:
... connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); ...
- сервер запускает бесконченый цикл, ожидая входящего соединения, и вызывает
accept()
, как только соединение установлено. В свою очередьaccept()
создаёт новый сокет для каждого соединения, вовзращает дескриптор сокета - как только соединение установлено (т.е. сокет создан) — функция
snprintf()
вписывает время и дату в буфер, после чего вызываетсяwrite()
, которая вписывает данные из буфера в сокет
Socket клиент
Перейдём ко второй программе — клиенту.
Код её будет выглядеть следующим образом:
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sockfd = 0, n = 0; char recvBuff[1024]; struct sockaddr_in serv_addr; if(argc != 2) { printf("\n Usage: %s <ip of server> \n",argv[0]); return 1; } memset(recvBuff, '0',sizeof(recvBuff)); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Error : Could not create socket \n"); return 1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(5000); if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0) { printf("\n inet_pton error occured\n"); return 1; } if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\n Error : Connect Failed \n"); return 1; } while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) { recvBuff[n] = 0; if(fputs(recvBuff, stdout) == EOF) { printf("\n Error : Fputs error\n"); } } if(n < 0) { printf("\n Read error \n"); } return 0; }
Кратко рассмотрим его:
... if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ...
- аналогично серверу — создаём сокет
... serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(5000); if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0) ...
- в структуру
sockaddr_in
с именемserv_addr
заносятся протокол, порт (5000) и адрес сервера (первый аргумент —argv[1]
)
... if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) ...
- функция
connect()
пытается установить соединение с хостом, используя данные из структурыserv_addr
... while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) ...
И в конце-концов — клиент с помощью read()
получает данные из своего сокета, в который поступают данные от сокета на сервере.
Собираем клиент, и пробуем подключиться к нашему серверу:
[simterm]
$ gcc client.c -o client $ ./client Usage: ./client <ip of server>
$ ./client 127.0.0.1 Tue May 16 12:13:54 2017
[/simterm]
Готово.
Ссылки по теме
Interprocess Communication Mechanisms
Beej’s Guide to Network Programming Using Internet Sockets