Как работает Deno TCP Echo Server? | 🏆 Первый прием работ по технической тематике

задняя часть внешний интерфейс deno
Как работает Deno TCP Echo Server? | 🏆 Первый прием работ по технической тематике

существует "Простое для понимания вводное руководство по Deno (начиная с семи аспектов)В этой статье брат Абао рассказал, как использовать Deno для создания простого эхо-сервера TCP. В этой статье на этом примере будет показано, как работает эхо-сервер TCP?

Для тех, кто уже начал Дено и хочет сразиться с Дено, вы можете прочитать "Брат А Бао"Удивительный Deno Начало работы и практическое применение" Эта статья.

1. Создайте эхо-сервер TCP

Ну, без лишних слов, давайте к делу. Прежде всего, давайте рассмотрим написанный ранее эхо-сервер TCP.Конкретный код выглядит следующим образом:

echo_server.ts

const listener = Deno.listen({ port: 8080 });

console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}

Оператор for await...of создает итеративный цикл по асинхронным или синхронным итерируемым объектам, включая объекты типа String, Array, Array (такие как аргументы или NodeList), TypedArray, Map, Set и пользовательские асинхронные или синхронные итерируемые объекты. .

Синтаксис for await...of следующий:

for await (variable of iterable) {
  statement
}

Затем мы запускаем эхо-сервер TCP с помощью следующей команды:

$ deno run --allow-net ./echo_server.ts

Здесь следует отметить, что при запуске./echo_server.ts, нам нужно установить--allow-netфлаг, чтобы разрешить доступ к сети. В противном случае появится следующее сообщение об ошибке:

error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", 
  run again with the --allow-net flag

Почему это так? Это связано с тем, что Deno — это среда выполнения JavaScript/TypeScript, которая по умолчанию выполняет код в безопасной среде. После успешного запуска сервера мы используемncКоманда для проверки работоспособности сервера:

$ nc localhost 8080
一起来学习Deno吧 #发送的数据
一起来学习Deno吧 #返回的数据

nc является сокращением от netcat и имеет репутацию швейцарского армейского ножа в сетевой индустрии. Поскольку он короткий, мощный и функциональный, он разработан как простой и надежный сетевой инструмент.

Роль нк:

  1. Реализовать прослушивание любого порта TCP/UDP, nc можно использовать в качестве сервера для прослушивания указанного порта в режиме TCP или UDP;

  2. Сканирование портов, nc может инициировать соединение TCP или UDP в качестве клиента;

  3. Передавайте файлы между машинами или проверяйте скорость сети между машинами.

Давайте проанализируем от запуска сервера эхо-сервера TCP до использованияncЧто произошло во время команды на подключение к этому серверу?

2. Анализ запущенного процесса эхо-сервера TCP

2.1 Запустите эхо-сервер TCP

запустить в командной строкеdeno run --allow-net ./echo_server.tsПосле команды текущая командная строка выведет следующую информацию:

listening on 0.0.0.0:8080

Указывает, что наш эхо-сервер TCP начал отслеживать локальный8080порт, здесь мы можем использоватьnetstatкоманда для печати информации о состоянии сетевой системы в Linux:

[root@izuf6ghot555xyn666xm888 23178]# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address    Foreign Address    State       PID/Program name    
tcp        0      0 0.0.0.0:8080     0.0.0.0:*          LISTEN      23178/deno

Изучив приведенную выше сетевую информацию, мы обнаружили, что текущий эхо-сервер TCP находится вLISTENСостояние прослушивания и PID текущего процесса23178.

В Linux все является файлом. В корневом каталоге Linux есть/procсодержание,/procФайловая система — это виртуальная файловая система, обеспечивающая интерфейс к структуре данных ядра в виде каталогов и файлов файловой системы, через которые можно просматривать и изменять различные системные свойства.

Далее мы вводим23178каталог процесса и использоватьls -l | grep '^d'Команда для просмотра информации о подкаталоге в текущем каталоге:

[root@izuf6ghot555xyn666xm888]# cd /proc/23178
[root@izuf6ghot555xyn666xm888 23178]# ls -l | grep '^d'
dr-xr-xr-x 2 root root 0 attr
dr-x------ 2 root root 0 fd
dr-x------ 2 root root 0 fdinfo
dr-x------ 2 root root 0 map_files
dr-xr-xr-x 5 root root 0 net
dr-x--x--x 2 root root 0 ns
dr-xr-xr-x 4 root root 0 task

Ниже мы в основном анализируем/proc/pid/taskа также/proc/pid/fdЭти два каталога:

2.1.1. Каталог /proc/pid/task

Этот каталог содержит все потоки в процессе. Имя каждого каталога названо в честь идентификатора потока (tid). Структура каталогов под каждым tid такая же, как/proc/pidСтруктура каталогов ниже такая же.Для свойств, общих для всех потоков,task/tidСодержимое каждого файла в подкаталоге такое же, как/proc/pidСоответствующие файлы в каталоге имеют одинаковое содержимое.как и во всех темахtask/tid/cwdфайлы и родительские каталоги/proc/pid/cwdСодержимое файла одинаково, поскольку все потоки совместно используют рабочий каталог. Для различных свойств каждого потокаtask/tidЗначение соответствующего файла ниже также отличается.

Для нашего процесса Deno (23178),Мы используемls -alвид команды/proc/23178/taskИнформация каталога:

[root@izuf6ghot555xyn666xm888 task]# ls -al
total 0
dr-xr-xr-x 4 root root 0 .
dr-xr-xr-x 9 root root 0 ..
dr-xr-xr-x 6 root root 0 23178
dr-xr-xr-x 6 root root 0 23179

Далее мы вводим/proc/23178/taskдиректория для начала анализа/proc/pid/fdсодержание.

2.1.2 Каталог /proc/pid/fd

Этот каталог содержит все файлы, открытые текущим процессом. Каждая запись представляет собой файловый дескриптор, символическую ссылку, указывающую на реальный открытый адрес.где 0 — стандартный ввод, 1 — стандартный вывод, 2 — стандартная ошибка.В многопоточной программе при выходе из основной программы эта папка будет недоступна.

Дескриптор файла формально представляет собой неотрицательное целое число. На самом деле это значение индекса, указывающее на таблицу записей открытых файлов процесса, поддерживаемую ядром для каждого процесса.Когда программа открывает существующий файл или создает новый файл, ядро ​​возвращает процессу дескриптор файла. В программировании некоторые низкоуровневые программы часто вращаются вокруг файловых дескрипторов. Но концепция файловых дескрипторов часто применима только к таким операционным системам, как UNIX и Linux.

Каждый процесс Unix (кроме возможногодемон) должен иметь три стандартныхPOSIXДескриптор файла, соответствующий трем стандартным потокам:

целочисленное значение имя символьные константы unistd.h Файловый поток stdio.h
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr

Для нашего процесса Deno (23178),Мы используемls -alвид команды/proc/23178/fdИнформация каталога:

[root@izuf6ghot555xyn666xm888 fd]# ls -al
total 0
dr-x------ 2 root root  0 .
dr-xr-xr-x 9 root root  0 ..
lrwx------ 1 root root 64 0 -> /dev/pts/0
lrwx------ 1 root root 64 1 -> /dev/pts/0
lrwx------ 1 root root 64 2 -> /dev/pts/0
lrwx------ 1 root root 64 3 -> anon_inode:[eventpoll]
lr-x------ 1 root root 64 4 -> pipe:[30180039]
l-wx------ 1 root root 64 5 -> pipe:[30180039]
lrwx------ 1 root root 64 6 -> /dev/pts/0
lrwx------ 1 root root 64 7 -> /dev/pts/0
lrwx------ 1 root root 64 8 -> socket:[30180040]

Глядя на приведенный выше вывод, мы обнаруживаем, что в дополнение к0-2Помимо файловых дескрипторов, наш процесс Deno (23178) также содержит другие файловые дескрипторы. Здесь мы сосредоточимся на файловых дескрипторах.8, согласно выходному результату, представляет собойSocket. Итак, когда был создан этот сокет? Давайте запомним этот вопрос и позже вместе исследуем внутренний процесс создания.

Далее, давайте проанализируем следующий процесс, который заключается в использованииncКоманда для подключения к нашему эхо-серверу TCP.

2.2 Подключение к эхо-серверу TCP

Далее воспользуемся введенным ранееncкоманда для подключения к нашему эхо-серверу TCP:

[root@izuf6ghot555xyn666xm888 ~]# nc localhost 8080

Затем введите на клавиатуреhello semlinker, оно будет автоматически отображено в текущей командной строкеhello semlinker. В это время мы впервые используемnetstatКоманда для просмотра текущего состояния сети, конкретная команда выглядит следующим образом:

[root@izuf6ghot555xyn666xm888 fd]# netstat -natp | grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      23178/deno          
tcp        0      0 127.0.0.1:55700         127.0.0.1:8080          ESTABLISHED 23274/nc            
tcp        0      0 127.0.0.1:8080          127.0.0.1:55700         ESTABLISHED 23178/deno  

Я верю, что зоркие друзья заметили23274/ncЭта строка, через эту строку, мы можем обнаружить, что nc использует родной55700Порт установил TCP-соединение с нашим эхо-сервером TCP, поскольку текущий статус соединенияESTABLISHED. На этом этапе давайте снова воспользуемсяls -alкоманда для просмотра/proc/23178/fdИнформация о каталоге, результат выполнения этой команды следующий:

[root@izuf6ghot555xyn666xm888 fd]# ls -al
total 0
dr-x------ 2 root root  0 .
dr-xr-xr-x 9 root root  0 ..
lrwx------ 1 root root 64 0 -> /dev/pts/0
lrwx------ 1 root root 64 1 -> /dev/pts/0
lrwx------ 1 root root 64 2 -> /dev/pts/0
lrwx------ 1 root root 64 3 -> anon_inode:[eventpoll]
lr-x------ 1 root root 64 4 -> pipe:[30180039]
l-wx------ 1 root root 64 5 -> pipe:[30180039]
lrwx------ 1 root root 64 6 -> /dev/pts/0
lrwx------ 1 root root 64 7 -> /dev/pts/0
lrwx------ 1 root root 64 8 -> socket:[30180040]
lrwx------ 1 root root 64 9 -> socket:[30181765]

Сравните предыдущие выходные результаты при использованииncПосле того, как команда установит соединение с эхо-сервером TCP,/proc/23178/fdВ каталог добавляется новый файловый дескриптор, а именно9 -> socket:[30181765], который также используется для представления Socket.

Итак, теперь, когда мы увидели явление, каков конкретный внутренний процесс? Чтобы проанализировать внутренний процесс выполнения, нам нужно использовать предоставленный LinuxstraceКоманда, которая часто используется для отслеживания системных вызовов и полученных сигналов во время выполнения процесса.

3. Используйте strace для отслеживания системных вызовов в процессе

Чтобы лучше понять последующее содержимое, нам нужно ввести некоторые предварительные знания, такие как Socket, Socket API, пользовательский режим и режим ядра и другие связанные знания.

3.1 Файловые дескрипторы

В системе Linux все рассматривается как файл, и файл можно разделить на: обычный файл, файл каталога, файл ссылки и файл устройства.Когда процесс открывает существующий файл или создает новый файл, ядро ​​возвращает процессу дескриптор файла, который представляет собой индекс, созданный ядром для эффективного управления открытым файлом и используемый для указания на открытый файл. Все системные вызовы выполнения операций /O проходят через файловые дескрипторы.

Каждый файловый дескриптор соответствует открытому файлу, и разные файловые дескрипторы также указывают на один и тот же файл. Один и тот же файл может быть открыт разными процессами или несколько раз в одном процессе.Система поддерживает таблицу файловых дескрипторов для каждого процесса, и значение этой таблицы начинается с 0, поэтому вы увидите один и тот же файловый дескриптор в разных процессах.В этом случае один и тот же файловый дескриптор может относиться к одному и тому же файлу, также указать на другой файл.

Чтобы понять файловые дескрипторы, нам нужно понять 3 структуры данных, поддерживаемые ядром.

  • Таблица файловых дескрипторов уровня процесса;
  • Таблица открытых файловых дескрипторов системного уровня;
  • Таблица i-узла для файловой системы.

На следующей диаграмме показана взаимосвязь между дескрипторами файлов, дескрипторами открытых файлов и i-узлами:

(картинка взята из интернета)

Два процесса на рисунке имеют много дескрипторов открытых файлов.

3.2 Socket

Две программы в сети обмениваются данными через двустороннее коммуникационное соединение. Один конец этого соединения называется сокетом. Поэтому для установления сетевого коммуникационного соединения требуется как минимум пара номеров портов.Socket — это, по сути, инкапсуляция стека протоколов TCP/IP, которая предоставляет интерфейс для программирования TCP или UDP, а не другой протокол.. С сокетами вы можете использовать протокол TCP/IP.

Английское значение Socket — «отверстие» или «гнездо». как BSD UNIXсвязь процессаМеханизм, возьми последнее значение. Также обычно называют «разъем", используемый для описания IP-адресов и портов, представляет собой дескриптор цепочки связи, которую можно использовать для реализации связи между разными виртуальными машинами или разными компьютерами.

в интернетехозяинКак правило, запускается несколько сервисных программ, и несколько сервисов предоставляются одновременно. Каждая служба открывает сокет и привязывает его к порту, а разные порты соответствуют разным службам. Socket похож на сокет с несколькими отверстиями, как и в английском языке. Хост похож на комнату, полную розеток. Каждая розетка имеет номер. Некоторые розетки обеспечивают 220 В переменного тока, некоторые — 110 В переменного тока, а некоторые — программы кабельного телевидения. Клиентское программное обеспечение может получать различные услуги, вставляя вилки в розетки с разными номерами. ——Энциклопедия Байду

Что касается Socket, можно резюмировать следующие моменты:

  • Он может реализовать низкоуровневую связь, и почти все прикладные уровни взаимодействуют через сокеты.
  • Он инкапсулирует протокол TCP/IP, удобный для вызовов протоколов прикладного уровня, и относится к промежуточному уровню абстракции между ними.
  • В семействе протоколов TCP/IP на транспортном уровне есть два общих протокола: TCP и UDP, Эти два протокола отличаются друг от друга, поскольку процесс реализации сокетов с разными параметрами также отличается.

Следующая диаграмма иллюстрирует отношения клиент/сервер API сокетов протокола, ориентированного на соединение.

3.3 Socket API

(1) функция socket(): используется для создания сокета и настройки различных атрибутов сокета, а также для возврата дескриптора.

int socket(int af, int type, int protocol);
  • af — это семейство адресов (Address Family), то есть тип IP-адреса, обычно используются AF_INET и AF_INET6. AF — это сокращение от «Семейство адресов», а INET — это сокращение от «Inetnet». AF_INET представляет адрес IPv4, а AF_INET6 представляет адрес IPv6.
  • type — это метод передачи данных/тип сокета, обычно используемый SOCK_STREAM (сокет потокового формата) и SOCK_DGRAM (сокет дейтаграммы).
  • протокол представляет протокол передачи, обычно используемые IPPROTO_TCP и IPPTTOTO_UDP, которые соответственно представляют протокол передачи TCP и протокол передачи UDP.

Как использовать:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

(2) функция bind(): используется для привязки сокета к определенному IP-адресу и порту.Только таким образом данные, проходящие через IP-адрес и порт, могут быть переданы сокету для обработки.

int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 

sock — дескриптор файла сокета, addr — указатель на структурную переменную sockaddr, а addrlen — размер переменной addr, который можно вычислить с помощью sizeof().

Как использовать:

//创建套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
serv_addr.sin_port = htons(8080);  //端口
//将套接字和IP、端口绑定
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

Приведенный выше код привязывает созданный сокет к IP-адресу 127.0.0.1 и порту 8080.

(3) функция listen(): используется для перевода сокета в состояние пассивного прослушивания. Так называемое пассивное прослушивание означает, что при отсутствии клиентского запроса сокет находится в «спящем» состоянии, и только при получении клиентского запроса сокет «пробуждается» для ответа на запрос.

int listen(int sock, int backlog);

sock — это сокет, который должен войти в состояние прослушивания, а backlog — это максимальная длина очереди запросов.Когда сокет обрабатывает запрос клиента, если приходит новый запрос, сокет не может его обработать, его можно только поместить в буфер, а затем прочитать из буфера после обработки текущего запроса. Если новые запросы продолжают поступать, они ставятся в очередь в буфере по порядку, пока буфер не заполнится.Этот буфер называется очередью запросов.

Когда очередь запросов заполнена, новые запросы не принимаются, и клиент получит ошибку ECONNREFUSED для Linux и ошибку WSAECONNREFUSED для Windows.Следует отметить, что функция listen() просто удерживает сокет в состоянии прослушивания и не получает запросы. Принятие запроса требует использования функции accept().

(4) функция accept(): когда сокет находится в состоянии прослушивания, клиентский запрос может быть получен через функцию accept().

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);  

Его параметры такие же, как у функции listen(): sock — сокет на стороне сервера, addr — структурная переменная sockaddr_in, а addrlen — длина параметра addr, который можно получить с помощью sizeof().

Функция accept() возвращает новый сокет для связи с клиентом, addr сохраняет IP-адрес и номер порта клиента, а sock — это сокет на стороне сервера, обратите внимание на различие.

Следует отметить, что функция listen() только переводит сокет в состояние прослушивания и фактически не получает клиентские запросы.Код после listen() будет продолжать выполняться до тех пор, пока не встретится с accept(). accept() блокирует выполнение программы до поступления нового запроса.После знакомства с этими основными API-интерфейсами Socket давайте рассмотрим пример Server Socket, чтобы вы могли лучше понять, как используются эти функции.

simple_tcp_demo.c

#include <unistd.h> 
#include <stdio.h> 
#include <sys/socket.h> 
#include <stdlib.h> 
#include <netinet/in.h> 
#include <string.h> 
#define PORT 8080 

int main(int argc, char const *argv[]) 
{ 
    int server_fd, new_socket, valread; 
    struct sockaddr_in address; 
    int opt = 1; 
    int addrlen = sizeof(address); 
    char buffer[1024] = {0}; 
    char *hello = "Hello from server"; 
       
     /* ① 创建监听套接字,使用IPV4地址 */ 
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) 
    { 
        perror("socket failed"); 
        exit(EXIT_FAILURE); 
    } 
       
    /* ② 设置socket相关配置 */
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, 
          &opt, sizeof(opt))) 
    { 
        perror("setsockopt"); 
        exit(EXIT_FAILURE); 
    } 
    
    /* AF_INET:因特网使用的 IPv4 地址,AF_INET6:因特网使用功能的 IPv6 地址 */
    address.sin_family = AF_INET; 
    /* INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,
       或“所有地址”、“任意地址”。*/
    address.sin_addr.s_addr = INADDR_ANY; 
    /* 网络端总是用Big endian,而本机端却要视处理器体系而定,比如x86就跟网络端的看法不同,
       使用的是Little endian。
       htons:Host To Network Short,它将本机端的字节序(endian)转换成了
       网络端的字节序 */
    address.sin_port = htons( PORT ); 
       
    /* ③ 绑定到本机地址,端口为8080  */ 
    if (bind(server_fd, (struct sockaddr *)&address,  
                                 sizeof(address))<0) 
    { 
        perror("bind failed"); 
        exit(EXIT_FAILURE); 
    } 
    /* ④ 为了更好的理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接口维护两个队列:
       - 未完成连接队列(incomplete connection queue),每个这样的 SYN 分节对应其中一项:
         已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接口
         处于 SYN_RCVD 状态。
       - 已完成连接队列(completed connection queue),每个已完成 TCP 三次握手过程的客户
         对应其中一项。这些套接口处于 ESTABLISHED 状态。*/
    if (listen(server_fd, 3) < 0) 
    { 
        perror("listen"); 
        exit(EXIT_FAILURE); 
    } 
    /* ⑤ accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,
       如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。*/
    /* 在实际开发过程中,此处会使用 while(true) 或 for (;;) 循环处理用户请求*/
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address,  
      (socklen_t*)&addrlen))<0) 
    { 
        perror("accept"); 
        exit(EXIT_FAILURE); 
    }
    /* 读取客户端发送过来的数据 */
    valread = read( new_socket , buffer, 1024); 
    printf("%s\n",buffer ); 
    /* 返回数据给客户端 */
    send(new_socket , hello , strlen(hello) , 0 ); 
    printf("Hello message sent\n"); 
    return 0; 
} 

для вышеперечисленногоsimple_tcp_demo.cкод, доступ к которому можно получить черезgccСкомпилируйте и запустите:

$ gcc simple_tcp_demo.c -o simple_tcp_demo && ./simple_tcp_demo

Затем продолжаем использоватьncкоманда для подключения к серверу:

$ nc localhost 8080
hello deno
Hello from server%  

Если все пойдет хорошо, вы должны увидеть следующий вывод в терминале командной строки:

$ tcp-server gcc simple_tcp_demo.c -o simple_tcp_demo && ./simple_tcp_demo
hello deno

Hello message sent

3.4 Пользовательский режим и режим ядра

Архитектура операционной системы Linux делится на пользовательский режим и режим ядра (или пространство пользователя и пространство ядра).Ядро, по сути, является своего рода программным обеспечением — оно управляет аппаратными ресурсами компьютера и обеспечивает среду, в которой работают приложения верхнего уровня.Пользовательский режим — это активное пространство приложения верхнего уровня, и выполнение приложения должно зависеть от ресурсов, предоставляемых ядром, включая ресурсы ЦП, ресурсы хранения, ресурсы ввода-вывода и т. д.

Чтобы приложение верхнего уровня могло получить доступ к этим ресурсам, ядро ​​должно предоставить приложению верхнего уровня интерфейс доступа: системный вызов.

Наименьшая функциональная единица операционной системы при выполнении системного вызова. В соответствии с различными сценариями приложений количество системных вызовов, предоставляемых различными дистрибутивами Linux, также различно, примерно между 240-350. Эти системные вызовы составляют основной интерфейс для взаимодействия между режимом пользователя и режимом ядра. В реальной операционной системе, чтобы скрыть эти сложные низкоуровневые детали реализации и уменьшить нагрузку на разработчиков, операционная система предоставляет нам библиотечные функции. Он реализует инкапсуляцию системных вызовов и предоставляет пользователям простой интерфейс бизнес-логики, который удобно вызывать разработчикам.

Здесь мы возьмем функцию write() в качестве примера для демонстрации процесса системного вызова:

(Источник изображения: https://www.linuxbnb.net/home/adding-a-system-call-to-linux-arm-architecture/)

В дополнение к системным вызовам, давайте кратко представим Shell, я думаю, что некоторые читатели уже написали сценарии Shell.Shell — это специальное приложение, широко известное как командная строка, которое по сути является интерпретатором команд, пропускает системные вызовы и пропускает различные приложения, обычно выступая своеобразным «клеем»., чтобы подключать различные небольшие функциональные программы, чтобы разные программы могли работать вместе с понятным интерфейсом, тем самым улучшая функции каждой программы.

Для облегчения взаимодействия между пользователями и системой обычно Shell соответствует терминалу, терминал представляет собой аппаратное устройство, а пользователю предоставляется графическое окно. Конечно, мы также упоминали ранее, что оболочка является программируемой.У нее есть стандартный синтаксис оболочки и текст, который соответствует ее синтаксису.Мы обычно называем это сценарием оболочки.

Итак, теперь вопрос в том, как переключиться из пользовательского режима в режим ядра? Для достижения переключения состояний есть три способа:

  • Системный вызов: Фактически сам системный вызов является прерыванием, но мягкое прерывание отличается от жесткого прерывания.
  • Исключение: если текущий процесс выполняется в пользовательском режиме, если в это время произойдет ненормальное событие, сработает переключатель.
  • Периферийное прерывание: когда периферийное устройство завершает запрос пользователя, оно посылает сигнал прерывания на ЦП.

3.5 команда strace

Команда strace часто используется для отслеживания системных вызовов и полученных сигналов во время выполнения процесса. В мире Linux процессы не могут получить прямой доступ к аппаратным устройствам.Когда процессу требуется доступ к аппаратным устройствам (например, чтение дисковых файлов, получение сетевых данных и т. д.), он должен переключиться из пользовательского режима в режим ядра и получить доступ к аппаратным устройствам через системные вызовы. strace может отслеживать системные вызовы процесса, включая параметры, возвращаемые значения и время выполнения.

Далее мы будем использоватьstraceкоманда для отслеживания потока системных вызовов процесса эхо-сервера Deno TCP. Сначала введите следующую команду в командной строке:

[root@izuf6ghot555xyn666xm888 deno]# strace -ff -o ./echo_server deno run -A ./echo_server.ts

-ff: если указано -o имя файла, результаты трассировки всех процессов выводятся в файл с соответствующим именем.pid, где pid — это идентификатор каждого процесса.

-o имя_файла: Записать вывод strace в файл имя_файла.

После успешного выполнения команды в/home/denoСледующие два файла будут сгенерированы в текущем каталоге:

-rw-r--r--  1 root root 14173 echo_server.23178
-rw-r--r--  1 root root   137 echo_server.23179

Для более интуитивного понимания23178а также23179Эти два процесса, здесь мы проходимpstree -ap | grep denoКоманда отображает процессы, связанные с deno, в виде древовидной диаграммы:

[root@izuf6ghot555xyn666xm888 deno]# pstree -ap | grep deno
  |   |       `-strace,23176 -ff -o ./echo_server deno run -A ./echo_server.ts
  |   |           `-deno,23178 run -A ./echo_server.ts
  |   |               `-{deno},23179
  |           |-grep,23285 --color=auto deno

Наблюдая за приведенным выше деревом процессов, мы можем знать, что нашTCP echo serverИдентификатор процесса, соответствующий процессу,23178, мы можем проверить наше предположение, посмотрев на текущее состояние сети:

[root@izuf6ghot555xyn666xm888 deno]# netstat -natp | grep deno
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      23178/deno

Давайте откроем/home/deno/echo_server.23178В этом файле много контента, выделим важные части:

Как видно из рисунка, при запуске эхо-сервера TCP будет вызвана функция socket() для создания прослушивающего сокета, после чего сокет будет передан на локальную машину.0.0.0.0адрес и8080Порты связаны, поэтому данные, проходящие через этот IP-адрес и порт, могут быть переданы в сокет для обработки. Затем он продолжит вызывать функцию listen(), напримерlisten(8, 128), перевести сокет в состояние пассивного прослушивания.

В этот момент мы входим/proc/23178/fdкаталог, используяls -alГлядя на статус текущего каталога, здесь мы видим ожидаемое описание файла —8 -> socket:[30180040].

[root@izuf6ghot555xyn666xm888 fd]# ls -al
total 0
dr-x------ 2 root root  0 .
dr-xr-xr-x 9 root root  0 ..
lrwx------ 1 root root 64 0 -> /dev/pts/0
lrwx------ 1 root root 64 1 -> /dev/pts/0
lrwx------ 1 root root 64 2 -> /dev/pts/0
lrwx------ 1 root root 64 3 -> anon_inode:[eventpoll]
lr-x------ 1 root root 64 4 -> pipe:[30180039]
l-wx------ 1 root root 64 5 -> pipe:[30180039]
lrwx------ 1 root root 64 6 -> /dev/pts/0
lrwx------ 1 root root 64 7 -> /dev/pts/0
lrwx------ 1 root root 64 8 -> socket:[30180040]

Далее мы используемncкоманда для подключения к нашему эхо-серверу TCP:

[root@izuf6ghot555xyn666xm888 deno]# nc localhost 8080

Ранее мы уже знали, что при успешном создании соединения/proc/23178/fdВ каталог будет добавлен новый файловый дескриптор:

lrwx------ 1 root root 64 9 -> socket:[30181765]

Мы уже говорили, что, когда сокет находится в состоянии прослушивания, для приема клиентских запросов можно использовать функцию accept(). Кроме того, функция accept() возвращает новый сокет для связи с клиентом. буду продолжать открывать/home/deno/echo_server.23178Этот файл, здесь мы находим содержимое, связанное с accept:

Из рисунка видно, что файловый дескриптор9Соответствующий сокет-сокет вызываетncПосле создания команды, когда клиент устанавливает соединение с сервером, он возвращает новый сокет для связи с клиентом. Думаю, некоторые читатели также заметили, что помимо рисункаaccept4Кроме того, есть также связанные с мультиплексированием ввода-выводаepoll_ctlа такжеepoll_waitфункция.

epoll — это расширяемый механизм уведомления о событиях ввода-вывода для ядра Linux. Впервые представленный в Linux 2.5.44, он предназначен для замены существующих системных функций выбора и опроса POSIX, позволяя программам, которым требуется большое количество файловых дескрипторов, работать лучше. Функция, реализованная epoll, аналогична функции poll в том смысле, что она прослушивает события на нескольких файловых дескрипторах.

epoll похож на kqueue FreeBSD в том смысле, что нижний уровень состоит из настраиваемых объектов ядра операционной системы и представлен в пользовательском пространстве в виде файловых дескрипторов. epoll ищет отслеживаемые файловые дескрипторы, используя красно-черное дерево (RB-дерево).

Что касается контента, связанного с мультиплексированием ввода-вывода и epoll, мы не будем продолжать его здесь расширять.Если в будущем будет время, я напишу статью о мультиплексировании ввода-вывода и представлю такие мультиплексоры, как select, poll и epoll.difference.

4. Справочные ресурсы


🏆 Первый выпуск технической тематики | Расскажем немного о Deno...

В этой статье используетсяmdniceнабор текста