W. R. Stevens : Глава 3
Введение в сокеты
Сокет можно передавать в 2-х направлениях : от процесса к ядру и от ядра к процессу.
Текстовое представление адреса сокета в двоичное выполняют функции inet_addr и inet_ntoa,
эти 2 функции работают в IPv4. Еще 2 функции - inet_pton и inet_ntop - работают как
в IPv4 , так и в IPv6.
Большинство функций сокетов используют в качестве аргумента указатель на структуру адреса сокета.
Каждый набор протоколов определяет свою собственную структуру адреса сокета.
Имена этих структур начинаются с sockaddr_ и заканчиваются уникальным суффиксом.
Структура адреса сокета IPv4 - sockaddr_in :
struct in_addr {
in_addr_t s_addr;
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Например , в 10-й SUSE это прописано в /include/linux/in.h :
struct in_addr {
__u32 s_addr;
};
struct sockaddr_in {
sa_family_t sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
Поле sin_len здесь просто не поддерживается. Если даже оно и присутствует , его не обязательно инициализировать.
Минимальный размер сокета для любой реализации - 16 байт.
in_addr_t - 4 байта
in_port_t - 2 байта
sa_family_t - 1 или 2 байта
И адрес , и порт в этой структуре хранятся в том порядке байт , который принят в интернете .
Есть 4 функции , передающие сокет от процесса к ядру :
bind
connect
sendto
sendmsg
Есть 5 функций , передающих сокет обратно :
accept
recvfrom
recvmsg
getpeername
getcockname
Во всех функциях сокет всегда передается по ссылке.
Тип передаваемого указателя формируется с помощью структуры адреса сокета :
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
Пример использования :
struct sockaddr_in serv;
bind(fd , (struct sockaddr * ) &serv , sizeof(ser))
Яковлев С: В 10-й SUSE это прописано в /include/linux/socket.h :
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
Обьявление функции выглядит так :
int bind(int , struct soxkaddr *, socklen_t);
Реализация функции выглядит так :
struct sockaddr_in serv;
int bind(sockfd , (struct soxkaddr *) &serv, sizeof(serv));
Структура сокета IPv6 :
struct in6_addr
{
uint8t s6_addr[16];
};
#define SIN6_LEN
struct sockaddr_in6 {
uint8t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
};
Яковлев С: В 10-й SUSE это прописано в /include/linux/in6.h :
struct in6_addr
{
union
{
__u8 u6_addr8[16];
__u16 u6_addr16[8];
__u32 u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
__u16 sin6_port; /* Transport layer port # */
__u32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
IPv6 относится к AF_INET6 , IPv4 - AF_INET.
Сокет IPv6 имеет фиксированную длину в 24 байта , т.е. он в полтора раза больше IPv4.
В функцию , кроме того , что сам сокет передается по ссылке ,
также передается длина сокета. Имеется 2 варианта способа передачи второго параметра - длины сокета :
1. Передача от процесса к ядру :
bind
connect
sendto
Здесь способ передачи 2-го параметра - по значению :
struct sockaddr_in serv;
connect(sockfd,(SA *) &serv,sizeof(serv));
2. Передача от ядра к процессу :
accept
recvfrom
getsockname
getpeername
Здесь 2-й аргумент передается по ссылке , в не по значению - это говорит о том ,
что размер может быть изменен ядром :
struct sockaddr_un cli;
socklen_t len;
len = sizeof(cli);
getpeername(unixfd,(SA *) &cli , &len);
Есть сокеты , которые имеют переменную , а не фиксированную длину.
Определение порядка байтов
2 байта в памяти можно положить 2-мя способами :
1. little-endian - первым идет младший байт
2. big-endian - первым идет старший байт
Способ упорядочивания байтов в системе - host byte order - определяется в следующей программе :
// intro/byteorder.c
#include "unp.h"
int main(int argc, char **argv)
{
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102;
printf("%s: ", CPU_VENDOR_OS);
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
} else
printf("sizeof(short) = %d\n", sizeof(short));
exit(0);
}
2-байтное значение 0x0102 помещается в переменную типа short и проверяем значения 2-х байтов этой переменной -
c[0] и c[1].
Пример можно взять тут
В сетевых протоколах используется обратный порядок байтов - network byte order.
И задача программиста - привести порядок байтов узла в сетевой , а потом обратно.
Для этого используются 2 функции , возврающие значение , записанное в сетевом порядке байтов :
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
2 функции , возврающие значение , записанное в порядке байтов узла :
uint16_t ntohs(uint16_t host16bitvalue);
uint32_t ntohl(uint32_t host32bitvalue);
По определению байт - это 8 бит . Но 8 бит - это также октет .
Функции управления байтами
Существуют 2 группы функций для работы с многобайтовыми полями.
1. Первая группа функция взята из стандарта BSD.
void bzero(void *dest,size_t nbytes);
void bcopy(const void *src,void *dest,size_t nbytes);
int bcmp(const void *ptr1,const void *ptr2,size_t nbytes);
bzero - обнуляет заданное число байтов в указанной облачти памяти.
bcopy - копирует заданное число байтов из источника в приемник.
bcmp - сравнивает 2 байтовых последовательности.
2. Вторая группа функций взята из стандарта ANSI C.
void *memset(void *dest,int c , size_t len);
void *memcpy(void *dest,int c , size_t len);
int memcmp(const void *ptr1,const void *ptr2,size_t nbytes);
Есть некоторые различия в работе этих функций и 1-й группы.
Функции преобразования адресов
Существуют 2 группы таких функций :
1. Функции , работающие только с IPv4 :
inet_aton
inet_ntoa
inet_addr
Они преобразуют адрес из точечно-десятичной (10.10.11.12) в 4-х байтную двоичную или наоборот.
inet_addr считается устаревшей.
2. Функции , работающие и с IPv4 , и с IPv6 :
inet_pton
inet_ntop
|