Вы здесь

API Сокетов

Создание сокета

#include <sys/types.h>
#include <sys/socket.h>

int s = socket(int domain, int type, int protocol);

domain - семейство протоколов, которое будет использоваться для передачи данных. Имена макросов, задающих домен, начинаются с PF - protocol family/

  • PF_UNIX - внутреннее межпроцессное взаимодействие
  • PF_INET - стек TCP/IP

type - тип сокета

  • SOCK_DGRAM - ненадежная передача данных с сохранением границ сообщений (соответствует протоколу UDP),
  • SOCK_STREAM - надежная передача данных без сохранения границ сообщений (соответствует протоколу TCP),
  • SOCK_SEQ - надежная передача данных с сохранением границ сообщений (в стеке TCP/IP не поддерживается),
  • SOCK_RAW - низкоуровневый доступ к протоколу (уровень IP, ICMP).

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

В домене Unix возможно создание пары соединённых между собой безымянных сокетов, которые буду вести себя подобно неименованному каналу pipe. В отличие от неименованных каналов, оба сокета открыты и на чтение и на запись.

int result;
int sockfd[2];
result=socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);

Назначение имени

Для того, чтобы клиенты могли подключаться к серверу, сервер должен иметь заранее известное имя. Вызов bind() обеспечивает назначение имени серверному сокету. Сервер получит имя клиентского сокета в момент соединения (stream) или получения сообщения (datagram), поэтому на клиентской стороне имя сокету, как правило, назначается ядром ОС, хотя и явное присвоение с помощью bind() остаётся доступным.

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *localaddr, int addrlen);

Второй параметр функции bind() - адрес - формально описан как указатель на структуру sockaddr с удобным размером 16 байт. sockaddr можно рассматривать как суперкласс (без методов) от которого наследуются реально используемые классы sockaddr_un, sockaddr_in и т.д. Все они наследуют поле sa_family - тип адреса, благодаря которому bind() корректно интерпретирует переданную ему структуру данных. Для того, чтобы избежать предупреждений компилятора, рекомендуется явно преобразовывать тип второго параметра к struct sockaddr *.

Макросы, которые присваиваются полю sa_family по своему числовому значению совпадают с соответствующими макросами определяющими семейство протоколов, но начинаются с AF - address family.

struct sockaddr {
  u_short   sa_family;
  char      sa_data[14];
};

Имя в домене Unix - строка с именем сокета в файловой системе.

struct sockaddr_un {
  short sun_family; /* AF_UNIX */
  char  sun_path[108];
};

Имя в домене Internet - IP-адрес и номер порта, которые хранятся в виде целых числе в формате BIG ENDIAN. Для заполнения структуры они должны быть преобразованы из локального представления в сетевое функциями htonl() и htons() для длинных и коротких целых соответственно. Упаковка IP-адреса в дополнительную структуру связана, скорее всего, с какими-то историческими причинами.

struct sockaddr_in {
  short         sin_family; /* AF_INET */
  u_short       sin_port;       /* Port Number */
  struct in_addr    sin_addr;/* Internet address */
  char          sin_zero[8];    /*Not used*/
}

struct in_addr {
  unsigned long int s_addr;
}

Соединение с сервером (в основном Stream)

Для сокета типа Stream вызов connect() соединяет сокет клиента с сокетом сервера, создавая поток передачи данных. Адрес сервера servaddr заполняется по тем же правилам, что и адрес, передаваемый в bind().

Для сокета типа Datagram вызов connect() запоминает адрес получателя, для отправки сообщений вызовом send(). Можно пропустить этот вызов и отправлять сообщения вызовом sendto(), явно указывая адрес получателя для каждого сообщения.

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

Прослушивание сокетов сервером (только Stream)

Вызов listen() на стороне сервера превращает сокет в фабрику сокетов, которая будет с помощью вызова accept() возвращать новый транспортный сокет на каждый вызов connect() со стороны клиентов.

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

backlog - количество запросов клиентов connect(), которые будут храниться в очереди ожидания, пока сервер не вызовет accept().

Обработка запроса клиента.

Клиентский connect() будет заблокирован до тех пор, пока сервер не вызовет accept(). accept() возвращает транспортный сокет, который связан с сокетом для которого клиент вызвал connect(). Этот сокет используется как файловый дескриптор для вызовов read(), write(), send() и recv().

В переменную clntaddr заносится адрес подключившегося клиента.

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *clntaddr, int *addrlen);

Чтение/запись данных

Для операций чтения-записи данных через сокеты могут применяться стандартные вызовы read() и write(), однако существуют и более специализированные вызовы:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);
ssize_t send(int sockfd, const char *msg, int len, int flags);
ssize_t sendto(int sockfd, const char *msg, int len, int flags,const struct sockaddr *toaddr, int tolen) ;
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t read(int fildes, const void *buf, size_t nbyte);
ssize_t recv(int sockfd, char *buf, int len, int flags);
ssize_t recvfrom(int sockfd, char *buf, int len, int flags, struct sockaddr *fromaddr, int *fromlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

Все вызовы применимы и к потоковым сокетам и к сокетам датаграмм. При попытке прочитать датаграмму в слишком маленький буфер, её хвост будет утерян.

write(fd,buf,size) == send(fd,buf,size,0) == sendto(fd,buf,size,0,NULL,0)

send() может применяться только к тем сокетам, для которых выполнен connect().

При использовании sendto() с потоковым сокетом адрес toaddr игнорируется если был выполнен connect(). Если же connect() не был выполнен - в errno возвращается ошибка ENOTCONN.

sendmsg() и recvmsg() близки к вызовам writev() и readv(), поскольку позволяют одним вызовом отправить/принять несколько буферов данных.

Флаги send():

  • MSG_DONTWAIT - неблокирующая отправка. В случае невозможности отправить порцию данных возвращается -1, а переменная errno выставляется в EAGAIN.
  • MSG_OOB - отправка внеочередных данных (out-of-band) если они поддерживаются протоколом

Флаги recv():

  • MSG_DONTWAIT - неблокирующее чтение
  • MSG_OOB - приём внеочередных данных
  • MSG_PEEK - "подглядывание" - чтение данных без удаления их из канала

Управление окончанием соединения (в основном Stream)

Вызов close() закрывает сокет и освобождает все связанные с ним структуры данных.

Для контроля над закрытием потоковых сокетов используется вызов shutdown(), который позволяет управлять окончанием соединения.

int shutdown () (int sock, int cntl);

Аргумент cntl может принимать следующие значения:

  • 0: больше нельзя получать данные из сокета;
  • 1: больше нельзя посылать данные в сокет;
  • 2: больше нельзя ни посылать, ни принимать данные через этот сокет.
Яндекс.Метрика