Coding — Создание сокетов в Linux
Wikipedia гласит, что сокет - «название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.».
Рассмотрим простой пример применения сокетов:
client.c:
server.c:
Итак рассмотрим, что представляет из себя структура struct sockaddr_in. Данная структура определена следующим образом:
/usr/include/netinet/in.h:
/usr/include/bits/sockaddr.h:
Полю sin_family присваивается значение AF_INET, что соответствует протоколу TCP/IP версии 4 (AF_INET6 – версия 6), sin_port и sin_addr номер порта и адрес сервера соответственно. Данные этой структуры используются при установлении соединения. После заполнения структуры происходит вызов функции socket():
/usr/include/sys/socket.h:
Итак, как видно из комментария, данная функция создает новый сокет и при успешном выполнении возвращает дескриптор на созданный сокет, при возникновении ошибки возвращает значение меньше 0 (аналогично работе функции open()). Теперь рассмотрим, что происходит после ее вызова в ядре.
Запускаем клиентскую часть, как и ранее (Механизмы создания процессов в Linux) выполняя трассировку программы:
Примечание: strace раскрывает «непонятные» цифры 2, 1, 0.
Как видно происходит системный вызов sys_socketcall:
net/socket.c:
Данная функция представляет собой в некотором роде «свитч» для системных вызовов. В переменной call устанавливается номер требуемой функции. Данные номера определены в файле include/linux/net.h:
Как видно на данный момент (версия ядра 2.6.28) их всего 18, что соответствует количеству операторов case в sys_socketcall. В нашем случае в переменной call находится значение равное 1, что соответствует вызову SYS_SOCKET, то есть созданию сокета (при установлении соединения, как нетрудно догадаться, это значение будет соответствовать 3 — SYS_CONNECT).
Как выяснилось происходит вызов системного вызова sys_socket() с передачей трех аргументов, которые мы задали в функции soket() (о параметрах можно почитать на страницах справочного руководства man 2 socket или на сайте opennet). Функция sys_socket() определена в файле net/socket.c:
Значение retval является тем самым дескриптором, который возвращает пользовательская функция soket(). Структура struct socket, описывающая сокет, определена следующим образом:
include/linux/net.h:
Итак в функции sys_soсket() происходит вызов sock_create(), в которой заполняется структура struct soсket и выделяется память под сокет (sock_alloc). Функция sock_map_fd() связывает созданный сокет с дескриптором (тот, который мы используем в пользовательском приложении).
Таким образом выстраивается следующая (обобщенная) схема создания сокета:
soket() → sys_socketcall() → sys_socket() → sock_create() → sock_alloc()
P.S. Во-первых может возникнуть вопрос зачем приводится код клиента и сервера, когда для данной статьи можно было
обойтись всего лишь конструкцией вида:
Да можно было поступить и так, но мне показалось, что если статья не будет интересной (надеюсь это не так), то вы сможете
поиграться хотя бы с программой.
Во-вторых. Рассмотрено конечно же не все и не так подробно, как хотелось бы, но надеюсь, что для дальнейшего самостоятельного изучения этого достаточно.
И последнее, вот pdf (и тут).
Желаю успехов!
Рассмотрим простой пример применения сокетов:
client.c:
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 |
#include <stdio.h> |
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 |
#include <stdio.h> |
Итак рассмотрим, что представляет из себя структура struct sockaddr_in. Данная структура определена следующим образом:
/usr/include/netinet/in.h:
1 2 3 4 5 6 7 8 9 10 |
struct sockaddr_in |
/usr/include/bits/sockaddr.h:
1 2 3 4 5 |
... |
Полю sin_family присваивается значение AF_INET, что соответствует протоколу TCP/IP версии 4 (AF_INET6 – версия 6), sin_port и sin_addr номер порта и адрес сервера соответственно. Данные этой структуры используются при установлении соединения. После заполнения структуры происходит вызов функции socket():
/usr/include/sys/socket.h:
1 2 3 4 5 6 |
/* |
Итак, как видно из комментария, данная функция создает новый сокет и при успешном выполнении возвращает дескриптор на созданный сокет, при возникновении ошибки возвращает значение меньше 0 (аналогично работе функции open()). Теперь рассмотрим, что происходит после ее вызова в ядре.
Запускаем клиентскую часть, как и ранее (Механизмы создания процессов в Linux) выполняя трассировку программы:
1 2 3 4 5 6 7 8 9 10 11 |
$ ltrace -S ./client |
Примечание: strace раскрывает «непонятные» цифры 2, 1, 0.
Как видно происходит системный вызов sys_socketcall:
net/socket.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
asmlinkage long sys_socketcall(int call, unsigned long __user *args) |
Данная функция представляет собой в некотором роде «свитч» для системных вызовов. В переменной call устанавливается номер требуемой функции. Данные номера определены в файле include/linux/net.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
... |
Как видно на данный момент (версия ядра 2.6.28) их всего 18, что соответствует количеству операторов case в sys_socketcall. В нашем случае в переменной call находится значение равное 1, что соответствует вызову SYS_SOCKET, то есть созданию сокета (при установлении соединения, как нетрудно догадаться, это значение будет соответствовать 3 — SYS_CONNECT).
Как выяснилось происходит вызов системного вызова sys_socket() с передачей трех аргументов, которые мы задали в функции soket() (о параметрах можно почитать на страницах справочного руководства man 2 socket или на сайте opennet). Функция sys_socket() определена в файле net/socket.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
asmlinkage long sys_socket(int family, int type, int protocol) |
Значение retval является тем самым дескриптором, который возвращает пользовательская функция soket(). Структура struct socket, описывающая сокет, определена следующим образом:
include/linux/net.h:
1 2 3 4 5 6 7 8 9 10 |
struct socket { |
Итак в функции sys_soсket() происходит вызов sock_create(), в которой заполняется структура struct soсket и выделяется память под сокет (sock_alloc). Функция sock_map_fd() связывает созданный сокет с дескриптором (тот, который мы используем в пользовательском приложении).
Таким образом выстраивается следующая (обобщенная) схема создания сокета:
soket() → sys_socketcall() → sys_socket() → sock_create() → sock_alloc()
P.S. Во-первых может возникнуть вопрос зачем приводится код клиента и сервера, когда для данной статьи можно было
обойтись всего лишь конструкцией вида:
1 2 3 4 |
#include <...> |
Да можно было поступить и так, но мне показалось, что если статья не будет интересной (надеюсь это не так), то вы сможете
поиграться хотя бы с программой.
Во-вторых. Рассмотрено конечно же не все и не так подробно, как хотелось бы, но надеюсь, что для дальнейшего самостоятельного изучения этого достаточно.
И последнее, вот pdf (и тут).
Желаю успехов!