Вы находитесь на странице: 1из 4

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

Wikipedia гласит, что сокет - «название программного интерфейса для


обеспечения обмена данными между процессами. Процессы при таком обмене могут
исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой
сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.».

Рассмотрим простой пример применения сокетов:


client.c:
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv) {
char* echo_host = "127.0.0.1";
int echo_port = 7777;
int sockfd;
struct sockaddr_in *server =
(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
server->sin_family = AF_INET;
server->sin_port = htons(echo_port);
server->sin_addr.s_addr = inet_addr(echo_host);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("Connecting to %s \n", echo_host);
printf("Numeric: %u\n", server->sin_addr.s_addr);
connect(sockfd, (struct sockaddr*)server, sizeof(*server));
char* msg = "Yeah, it's works!";
printf("\nSend: ’%s’\n", msg);
write(sockfd, msg, strlen(msg));
char* buf = (char*)malloc(1000);
int bytes = read(sockfd, (void*)buf, 1000);
printf("\nBytes received: %u\n", bytes);
printf("Text: ’%s’\n", buf);
close(sockfd);
return 0;
}

server.c:
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv) {
char* echo_host = "127.0.0.1";
int echo_port = 7777;
int sockfd;
struct sockaddr_in *server =
(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
server->sin_family = AF_INET;
server->sin_port = htons(echo_port);
server->sin_addr.s_addr = inet_addr(echo_host);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (bind(sockfd, (struct sockaddr*)server, sizeof(*server))) {
printf("bind failed\n");
}
listen(sockfd, SOMAXCONN);
int clientfd;
struct sockaddr_in* client =
(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
int client_size = sizeof(*client);
char* buf = (char*)malloc(1000);
int bytes;
printf("Wait for connection to port %u\n", echo_port);
clientfd = accept(sockfd, (struct sockaddr*)client, &client_size);
printf("Connected to %s:%u\n\n", inet_ntoa(client->sin_addr),
ntohs(client->sin_port));
printf("Numeric: %u\n", ntohl(client->sin_addr.s_addr));
while(1) {
bytes = read(clientfd, (void*)buf, 1000);
if (bytes <= 0) {
close(clientfd);
printf("Connection closed.\n");
exit(0);
}
printf("Bytes received: %u\n", bytes);
printf("Text: ’%s’\n", buf);
write(clientfd, buf, bytes);
}
}

Итак рассмотрим, что представляет из себя структура struct sockaddr_in.


Данная структура определена следующим образом:

/usr/include/netinet/in.h:
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
/usr/include/bits/sockaddr.h:
...
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
...

Полю sin_family присваивается значение AF_INET, что соответствует


протоколу TCP/IP версии 4 (AF_INET6 – версия 6), sin_port и sin_addr номер порта
и адрес сервера соответственно. Данные этой структуры используются при
установлении соединения. После заполнения структуры происходит вызов функции
socket():

/usr/include/sys/socket.h:
/*
* Create a new socket of type TYPE in domain DOMAIN, using
* protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
* Returns a file descriptor for the new socket, or -1 for errors.
*/
extern int socket(int __domain, int __type, int __protocol) __THROW;

Итак, как видно из комментария, данная функция создает новый сокет и при
успешном выполнении возвращает дескриптор на созданный сокет, при возникновении
ошибки возвращает значение меньше 0 (аналогично работе функции open()). Теперь
рассмотрим, что происходит после ее вызова в ядре.
Запускаем клиентскую часть, как и ранее (Механизмы создания процессов в
Linux) выполняя трассировку программы:

$ ltrace -S ./client
...
socket(2, 1, 0 <unfinished ...>
SYS_socketcall(1, 0xbfca5f40, 0xb80bcff4, 0x8048720, 0x80484f0) = 3
<... socket resumed> )
...

$ strace ./client
...
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
...

*Примечание: strace раскрывает «непонятные» цифры 2, 1, 0.

Как видно происходит системный вызов sys_socketcall:

net/socket.c:
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
...
switch (call) {
case SYS_SOCKET:
err = sys_socket(a0, a1, a[2]);
break;
...
default:
err = -EINVAL;
break;
}
return err;
}

Данная функция представляет собой в некотором роде «свитч» для системных


вызовов. В переменной call устанавливается номер требуемой функции. Данные
номера определены в файле include/linux/net.h:

...
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
...

Как видно на данный момент (версия ядра 2.6.28) их всего 18, что
соответствует количеству операторов case в sys_socketcall. В нашем случае в
переменной call находится значение равное 1, что соответствует вызову
SYS_SOCKET, то есть созданию сокета (при установлении соединения, как нетрудно
догадаться, это значение будет соответствовать 3 — SYS_CONNECT).
Как выяснилось происходит вызов системного вызова sys_socket() с передачей
трех аргументов, которые мы задали в функции soket() (о параметрах можно
почитать на страницах справочного руководства man 2 socket или на сайте
http://www.opennet.ru/man.shtml?topic=socket&category=2&russian=0). Функция
sys_socket() определена в файле net/socket.c:
asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
...
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}

Значение retval является тем самым дескриптором, который возвращает


пользовательская функция soket(). Структура struct socket, описывающая сокет,
определена следующим образом:

include/linux/net.h:
struct socket {
socket_state state;
short type;
unsigned long flags;
const struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
};

Итак в функции sys_soсket() происходит вызов sock_create(), в которой


заполняется структура struct soсket и выделяется память под сокет (sock_alloc).
Функция sock_map_fd() связывает созданный сокет с дескриптором (тот, который мы
используем в пользовательском приложении).
Таким образом выстраивается следующая (обобщенная) схема создания сокета:

soket() → sys_socketcall() → sys_socket() → sock_create() → sock_alloc()

Оценить