DNK_C_C++_Go_Rust
45 subscribers
14 photos
45 links
DNK - дневник кодера С и С++
Download Telegram
#221_Cpp_MTH_PkS_TP

Как избежать состояния гонки?

Чтобы избежать состояния гонки (race condition) в многопоточной программе, следует применять различные методы синхронизации и координации потоков.

Мьютекс (Mutex от английского "mutual exclusion" — взаимное исключение) — позволяет ограничить доступ к общему ресурсу так, чтобы только один поток мог работать с ним в конкретный момент времени.
Это предотвращает состояние гонки, связанное с изменением общих данных.


Пример на C++ с использованием std::mutex:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_counter = 0;

void incrementCounter() {
/* Захватываем мьютекс перед работой с общей переменной */
std::lock_guard<std::mutex> lock(mtx);
shared_counter++;
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Shared counter: " << shared_counter << std::endl;
return 0;
}


Атомарные операции — гарантируют, что операция выполняется целиком и полностью, без возможности вмешательства других потоков.
Это особенно полезно для небольших операций над примитивными типами данных.


Пример на C++ с использованием std::atomic:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> shared_counter(0);

void incrementCounter() {
shared_counter++;
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Shared counter: " << shared_counter << std::endl;
return 0;
}



Семафоры (Semaphores) — позволяют контролировать доступ к ресурсу, предоставляя ограниченное количество разрешений.
В отличие от мьютекса, семафор может быть использован несколькими потоками одновременно, если доступно достаточное количество разрешений.


Пример на C++ с использованием std::counting_semaphore (C++20):

#include <iostream>
#include <thread>
#include <semaphore>

/* Семафор с начальным значением 1 */
std::counting_semaphore<1> sem(1);
int shared_counter = 0;

void incrementCounter() {
// Ожидание разрешения
sem.acquire();
shared_counter++;
// Освобождение разрешения
sem.release();
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Shared counter: " << shared_counter << std::endl;
return 0;
}

Барьеры (Barriers) — позволяют синхронизировать несколько потоков так, чтобы все потоки достигли определенной точки выполнения, прежде чем продолжить работу дальше.

Пример на C++ с использованием std::barrier (C++20):

#include <iostream>
#include <thread>
#include <barrier>

std::barrier barrier(2);
int shared_counter = 0;

void incrementCounter() {
shared_counter++;
/* Все потоки дожидаются друг друга */
barrier.arrive_and_wait();
if (std::this_thread::get_id() == std::thread::id()) {
std::cout << "Shared counter: " << shared_counter << std::endl;
}
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

return 0;
}


Условные переменные (Condition Variables)позволяют одному потоку ждать выполнения определенного условия другим потоком.
Это полезно, когда нужно синхронизировать выполнение задач, зависящих друг от друга.


Пример на C++ с использованием std::condition_variable:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;
int shared_counter = 0;

void waitForWork() {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{ return ready; });
shared_counter++;
}

void signalWork() {
{
std::lock_guard<std::mutex> lck(mtx);
ready = true;
}
cv.notify_all();
}

int main() {
std::thread t1(waitForWork);
std::thread t2(signalWork);

t1.join();
t2.join();

std::cout << "Shared counter: " << shared_counter << std::endl;
return 0;
}
Использование иммутабельных объектовисключает возможность возникновения race conditions.

Пример на JavaScript с использованием Object.freeze():

const immutableObject = Object.freeze({ counter: 0 });
console.log(immutableObject.counter); // 0
immutableObject.counter = 42; /* Не сработает, объект заморожен */
console.log(immutableObject.counter);
// Всё ещё 0


Правильный выбор метода синхронизации зависит от конкретных требований вашего проекта.

Важно понимать, какие данные используются совместно, и выбирать подходящий инструмент для избежания состояний гонок.
#222_Cpp_MTH_PkS_TP

Что такое атомарная операция?

Атомарная операция — выполняется как единая, неделимая единица работы. Она не может быть прервана или выполнена частично.
В контексте многопоточного программирования атомарные операции важны, т. к. предотвращают состояние гонки (race condition) и обеспечивают консистентность данных.

Свойства атомарных операций:

Неделимость — операция считается атомарной, если выполняется целиком или не выполняется вовсе. Никакие другие потоки не могут вмешиваться в процесс выполнения такой операции.

Консистентность — после успешного завершения атомарной операции данные остаются в согласованном состоянии, т.е. никакие другие потоки не увидят промежуточные результаты.

Неизменяемость — во время выполнения атомарной операции другие потоки не могут изменять те же самые данные.

Примеры атомарных операций:

Чтение/запись регистра — классический пример атомарной операции. Процессор выполняет эти операции за один такт, и они не могут быть прерваны.

Инкрементация/декрементация — увеличение или уменьшение значения переменной на единицу может быть выполнено атомарно, если процессор поддерживает такую команду.

Тестирование и установка — эта операция проверяет значение переменной и устанавливает новое значение, если текущее соответствует ожидаемому.
Такая операция часто используется для реализации
спинлоков.

Реализация атомарных операций:
В современных ЯП и библиотеках существуют специальные средства для выполнения атомарных операций:

Пример на C++ с использованием std::atomic:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void incrementCounter() {
counter++;
}

int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);

t1.join();
t2.join();

std::cout << "Counter: " << counter << std::endl;
return 0;
}


Здесь std::atomic<int> гарантирует, что операция инкрементации будет выполнена атомарно, исключая возможность состояния гонки.

На Java с использованием AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);

Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
};

Thread t1 = new Thread(task);
Thread t2 = new Thread(task);

t1.start();
t2.start();

try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("Counter: " + counter.get());
}
}


Класс AtomicInteger предоставляет атомарные операции для целочисленных значений, что делает код безопасным для многопоточного исполнения.


Преимущества атомарных операций:


Безопасность данных — атомарные операции защищают общие данные от несогласованных изменений, что снижает вероятность ошибок и повышает надежность программы.

Производительность атомарные операции обычно быстрее, чем использование традиционных механизмов синхронизации, таких как мьютексы, так как они не требуют переключения контекста и могут выполняться непосредственно на уровне процессора.

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


Ограничения атомарных операций:

Ограниченность функционала — атомарные операции поддерживают лишь базовые типы данных и простые операции.
Для сложных структур данных или сложных операций требуется дополнительная синхронизация.


Процессорные ограничения — хотя многие процессоры поддерживают атомарные операции, не все команды могут быть выполнены атомарно.
Разработчики должны учитывать архитектуру целевой платформы.

Атомарные операции играют ключевую роль в обеспечении целостности данных в многопоточных приложениях.
Они обеспечивают безопасность и производительность, минимизируя риски, связанные с состоянием гонки.
#223_Cpp_MTH_PkS_TP

Как работать с std::mutex?

std::mutex — один из основных инструментов для синхронизации потоков в C++ и используется для защиты критических секций кода от одновременного доступа несколькими потоками.

Основные шаги работы с std::mutex:

Создание мьютекса:

// Подключение хедера mutex:
#include <mutex>
/* Объяление переменной типа std::mutex */
std::mutex mtx;


Блокировка mutex (lock) — чтобы защитить критическую секцию кода, необходимо заблокировать мьютекс перед входом в эту секцию и разблокировать его после выхода.
Это можно сделать двумя способами:

С помощью метода lock():

// Блокируем мьютекс
mtx.lock();

// Критическая секция
/* Здесь выполняется код, который должен быть защищен от параллельного доступа */

// Разблокируем мьютекс
mtx.unlock();

С использованием RAII-объектов (std::lock_guard, std::unique_lock) — это предпочтительнее, так как они автоматически освобождают мьютекс при выходе из области видимости:

{
/* Автоматически блокируем мьютекс */
std::lock_guard<std::mutex> lock(mtx);

// Критическая секция
/* Здесь выполняется код, который должен быть защищен от параллельного доступа */
} /* Мьютекс автоматически разблокируется при выходе из блока
*/

Использование std::unique_lock — если вам нужно более гибкое управление блокировкой (например, временная разблокировка внутри критической секции), используйте

std::unique_lock


позволяет явно управлять временем блокировки и разблокировки.

std::unique_lock<std::mutex> ulk(mtx);

// Критическая секция
// ...

/* Можно временно разблокировать мьютекс */
ulk.unlock();
// Некритический код
// ...

/* Заблокировать снова, если нужно */
ulk.lock();


Синхронизация нескольких мьютексов — когда требуется работа с несколькими мьютексами одновременно, рекомендуется использовать std::lock и std::adopt_lock.

std::mutex mtx1, mtx2;
/* Заблокируем оба мьютекса сразу */
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);

// Критическая секция
// ...


Рекомендации по использованию:
— всегда старайтесь минимизировать время удержания блокировки;
— избегайте захвата мьютекса дважды в одном потоке без промежуточной разблокировки, это может привести к deadlock'у.

— используйте RAII-объекты (std::lock_guard, std::unique_lock) вместо явных вызовов lock() и unlock(), чтобы избежать ошибок управления ресурсами.

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void incrementCounter(int id) {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}

int main() {
std::thread t1(incrementCounter, 1);
std::thread t2(incrementCounter, 2);

t1.join();
t2.join();

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}


В этом примере два потока увеличивают общую переменную counter, используя мьютекс для обеспечения корректности результата.
#224_NTWK_PkS_TOS_TP

Что такое СОКЕТ?

СОКЕТ (от англ. socket) — программный интерфейс для обмена данными между процессами через сеть или внутри одной системы.
Сокеты позволяют приложениям взаимодействовать друг с другом независимо от их физического местоположения.
Этот механизм широко применяется в сетевых коммуникациях, таких как передача данных через интернет.


Основные характеристики сокетов.

Типы сокетов:

TCP-сокеты — используются для передачи данных по протоколу TCP (Transmission Control Protocol). Они обеспечивают надёжную передачу данных с контролем ошибок и упорядоченной доставкой пакетов.

UDP-сокеты — работают по протоколу UDP (User Datagram Protocol). Данные передаются быстрее, но без гарантии доставки и порядка получения пакетов.

Адресность — каждый сокет имеет уникальный адрес, состоящий из IP-адреса и порта.
Например, 192.168.1.10:80 указывает на сокет, привязанный к IP-адресу 192.168.1.10 и порту 80.

Работа с сокетом — процесс взаимодействия с сокетом включает несколько этапов:
— создание сокета;
— привязка сокета к определённому адресу (IP + порт)
;
— ожидание подключения (для серверов) или подключение к удаленному сокету (для клиентов);
— обмен данными;
— закрытие соединения.


Сокеты используются во многих современных технологиях:
— веб-серверы и браузеры общаются через HTTP/HTTPS, которые работают поверх TCP-сокетов;
— потоковые сервисы (видео, аудио) могут использовать UDP-сокеты для быстрой передачи данных;
— игры, чаты и другие приложения реального времени часто используют сокеты для быстрого обмена информацией.

Таким образом, сокеты — фундаментальная часть сетевого программирования и позволяют создавать мощные и эффективные сетевые приложения.
#225_Cpp_NTWK_PkS_TOS_TP

Пример работы с сокетами на С++ в Linux

В Linux для работы с сокетами обычно используется библиотека POSIX API.
Она предоставляет набор функций для создания, настройки и управления сокетами.

Пример эхо-сервера на C++, работающего под управлением Linux.

Этап 1: Создадим сокет для прослушивания входящих соединений.
Этап 2: Привяжем созданный сокет к конкретному порту.
Этап 3: Прослушивание входящих соединений.
Этап 4: Принятие нового соединениякогда поступит запрос на соединение, примем его и начнем обработку.
Этап 5: Чтение и отправка данных — будем читать данные от клиента и отправлять обратно те же самые данные.
Этап 6: Завершение работы — закроем все открытые дескрипторы.

Полный пример кода:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <iostream>

#define DEFAULT_PORT 8080

int main() {
// Создание сокета
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket < 0) {
perror("Ошибка создания сокета");
return 1;
}

// Привязка сокета к порту
struct sockaddr_in serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(DEFAULT_PORT);

if (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) {
perror("Ошибка привязки сокета");
close(serverSocket);
return 1;
}

// Начало прослушивания
if (listen(serverSocket, SOMAXCONN) < 0) {
perror("Ошибка начала прослушивания");
close(serverSocket);
return 1;
}

std::cout << "Сервер запущен. Ожидаю подключения..." << std::endl;

while (true) {
// Прием нового соединения
struct sockaddr_in clientAddress;
socklen_t clientLength = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientLength);
if (clientSocket < 0) {
perror("Ошибка приема соединения");
continue;
}

char buffer[1024];
ssize_t bytesReceived;

do {
// Чтение данных от клиента
bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesReceived > 0) {
/* Отправка данных обратно клиенту */
send(clientSocket, buffer, bytesReceived, 0);
}
} while (bytesReceived > 0);

/* Закрытие соединения с клиентом */
close(clientSocket);
}

// Закрытие основного сокета
close(serverSocket);

return 0;
}



Пояснение к коду.

Создание сокета:
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket < 0) {
perror("Ошибка создания сокета");
return 1;
}


Создается сокет типа AF_INET (IPv4) и протокола SOCK_STREAM (TCP).


Привязка сокета к порту:
struct sockaddr_in serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(DEFAULT_PORT);

if (bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) {
perror("Ошибка привязки сокета");
close(serverSocket);
return 1;
}

Сокет привязывается к порту DEFAULT_PORT (в данном случае 8080).


Прослушивание входящих соединений:
if (listen(serverSocket, SOMAXCONN) < 0) {
perror("Ошибка начала прослушивания");
close(serverSocket);
return 1;
}


Сервер начинает прослушивать входящие соединения.
Прием новых соединений и обработка данных:
while (true) {
// Прием нового соединения
struct sockaddr_in clientAddress;
socklen_t clientLength = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientLength);
if (clientSocket < 0) {
perror("Ошибка приема соединения");
continue;
}

char buffer[1024];
ssize_t bytesReceived;

do {
// Чтение данных от клиента
bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesReceived > 0) {
// Отправка данных обратно клиенту
send(clientSocket, buffer, bytesReceived, 0);
}
} while (bytesReceived > 0);

// Закрытие соединения с клиентом
close(clientSocket);
}

Сервер принимает новые соединения и читает данные от клиента, отправляя их обратно.

Завершение работы:
close(serverSocket);

После завершения работы закрывается основной сокет.


Пример демонстрирует базовую функциональность работы с сокетами на C++ под Linux.
Аналогичный подход можно применять и для других Unix-подобных ОС.
#226_Cpp_NTWK_PkS_TOS_TP

Какие операции можно делать с сокетом?

Сокет — инструмент для межпроцессорного общения, особенно в контексте сетевой коммуникации.

Операции, которые можно выполнять с сокетом, зависят от его типа (TCP, UDP и т.д.) и ОС, однако основные действия остаются схожими.

Операции, которые можно производить с сокетом:

Создание сокета — необходимо выполнить для работы с сокетом.
В зависимости от используемого ЯП и платформы, создание сокета может выглядеть следующим образом:

На языке C/C++:

int socket_fd = socket(AF_INET, SOCK_STREAM, 0);

На Python:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


Привязка сокета к адресу и порту (bind) — необходима для серверных приложений, чтобы указать системе, какой адрес и порт будет использоваться для приёма входящих соединений.

На языке C/C++:

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);

bind(socket_fd, (struct sockaddr *) &addr, sizeof(addr));

На Python:

s.bind((HOST, PORT))


Прослушивание входящих соединений (listen) — после привязки сокета к адресу и порту, серверное приложение должно начать прослушивание входящих соединений. Это делается с помощью вызова функции listen.

На языке C/C++:

listen(socket_fd, BACKLOG);

На Python:

s.listen(BACKLOG)

Принятие нового соединения (accept)когда приходит новый запрос на соединение, сервер должен принять его, создав новый сокет для дальнейшего общения с клиентом.

На языке C/C++:

struct sockaddr_storage client_addr;
socklen_t addr_size = sizeof(client_addr);
int new_socket = accept(socket_fd, (struct sockaddr *) &client_addr, &addr_size);

На Python:

conn, addr = s.accept()

Отправка данных (send) — для отправки данных через сокет используется функция send.

На языке C/C++:

const char* message = "Hello, World!";
send(new_socket, message, strlen(message), 0);

На Python:

conn.sendall(b"Hello, World!")


Чтение данных (recv) — чтобы получить данные, отправленные другим процессом через сокет, используется функция recv.

На языке C/C++:

char buffer[1024];
ssize_t bytes_received = recv(new_socket, buffer, sizeof(buffer), 0);

На Python:

data = conn.recv(1024)


Закрытие сокета (close) — по завершении работы с сокетом его следует закрыть, чтобы освободить ресурсы.

На языке C/C++:

close(new_socket);

На Python:

conn.close()


Дополнительные операции:

Многопоточность — использование многопоточности для обработки множества подключений одновременно.

Мультиплексирование — использование селекторов (select, poll, epoll) для мониторинга состояния множества сокетов.

Управление тайм-аутами — установка тайм-аутов для операций чтения и записи.

Настройка параметров сокета — изменение различных параметров сокета, например, буферов, опций повторной передачи и т.п.

Эти операции составляют основу работы с сокетами и позволяют строить сложные сетевые приложения, такие как веб-серверы, клиенты, чаты и многое другое.
#227_C_NTWK_PkS_TOS_TP

Какая информация нужна, чтобы создать сокет?

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

Доменная область (Domain):
AF_INET для IPv4
AF_INET6
для IPv6
AF_UNIX
для Unix домена (используется для межпроцессного взаимодействия на одной машине)

Тип сокета:
SOCK_STREAM для TCP (надежная доставка данных)
SOCK_DGRAM для UDP (быстрая передача данных без гарантий доставки)
SOCK_RAW для низкоуровневого доступа к данным (например, для создания собственных протоколов).

Протокол:
Обычно указывается значение 0, что означает использование стандартного протокола для выбранного типа сокета (например, TCP для SOCK_STREAM).

Адрес и порт:
— для серверных приложений необходимо указать IP-адрес и порт, к которому будет привязан сокет.
— для клиентских приложений указываются IP-адрес и порт удаленного сервера.

Пример создания сокета на языке C:
#include <sys/socket.h>

// Создание сокета
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("Ошибка создания сокета");
exit(EXIT_FAILURE);
}

Пример создания сокета на Python:
import socket

# Создание сокета
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


После создания сокета для серверного приложения потребуется выполнить дополнительные шаги, такие как привязка сокета к адресу и порту (bind), начало прослушивания (listen) и принятие новых соединений (accept).
#228_NTWK_PkS_TOS_TP

Какие бывают модели сетей?

Модели сетей — абстрактные структуры, описывающие архитектуру, организацию и взаимодействие компонентов сети.
Эти модели помогают понять принципы работы сетей, их структуру и функционирование.


Существуют различные типы моделей сетей, каждая из которых фокусируется на разных аспектах архитектуры и функциональности:
Модель OSI (Open Systems Interconnection) — была разработана Международной организацией по стандартизации (ISO) в конце 1970-х годов. Она представляет собой семиуровневую модель, которая описывает процессы связи между системами.

Уровни OSI:
Уровень 7: Прикладной уровень (Application Layer) — обеспечивает доступ приложений к сетевым службам.
Уровень 6: Уровень представления (Presentation Layer) — преобразует данные в формат, понятный прикладным программам.
Уровень 5: Сеансовый уровень (Session Layer) — управляет сеансами связи между устройствами.
Уровень 4: Транспортный уровень (Transport Layer) — обеспечивает надежную доставку данных между конечными точками.
Уровень 3: Сетевой уровень (Network Layer) — отвечает за маршрутизацию пакетов данных.
Уровень 2: Канальный уровень (Data Link Layer) — обрабатывает пакеты данных и управляет передачей данных по физическим каналам.
Уровень 1: Физический уровень (Physical Layer) — передача битов данных по физическому носителю.


Модель TCP/IP (Transmission Control Protocol/Internet Protocol) — является реальным воплощением принципов, заложенных в модели OSI.

Уровни TCP/IP:
Уровень 4: Приложения (Application Layer) — включает протоколы высокого уровня, такие как HTTP, FTP, SMTP.
Уровень 3: Транспорт (Transport Layer) — обеспечивает надежность передачи данных с использованием протоколов TCP и UDP.
Уровень 2: Интернет (Internet Layer) — ответственен за адресацию и маршрутизацию пакетов с использованием протокола IP.
Уровень 1: Доступ к сети (Network Access Layer) — соответствует двум нижним уровням модели OSI и отвечает за физическую передачу данных.


Модель IEEE 802 — разработана Институтом инженеров электротехники и электроники (IEEE) и охватывает стандарты для локальных вычислительных сетей (LAN) и беспроводных сетей (WLAN). Она разделяет канальный уровень модели OSI на два подуровня:

LLC (Logical Link Control) — логическое управление связью, обеспечивающее услуги для верхних уровней.
MAC (Media Access Control) — управление доступом к среде передачи данных, определяющее правила доступа устройств к общему каналу связи.


Модель DoD (Department of Defense)
разработана Министерством обороны США и также известна как модель DARPA (Defense Advanced Research Projects Agency). Она аналогична модели TCP/IP, но отличается некоторыми деталями:


Процесс (Process/Application Layer) — высший уровень, включающий пользовательские программы и службы.
Хост-за-хозяином (Host-to-Host Transport Layer) — транспортный уровень, аналогичный транспортному уровню модели TCP/IP.
Интернет (Internet Layer) — сетевой уровень, отвечающий за адресацию и маршрутизацию.
Сеть (Network Interface Layer) — низший уровень, соответствующий физическому и канальному уровням модели OSI.
Модель клиент-сервер — описывает архитектуру распределённых систем, где одна система (сервер) предоставляет услуги другой системе (клиенту):
Клиенты
— запрашивают услуги у сервера.
Серверы — предоставляют услуги клиентам.

Примером такой модели является веб-браузер (клиент), запрашивающий страницы у веб-сервера.


Одноранговая модель (Peer-to-Peer, P2P)
— в одноранговой модели все участники сети равноправны и могут выступать как клиентами, так и серверами:
Пиры (Peers) — могут предоставлять и получать услуги от других пиров.
Примером P2P-моделей являются файлообменные сети, такие как BitTorrent.


Трехзвенная архитектура — эта модель включает три компонента:
Пользовательский интерфейс — представление данных пользователям.
Бизнес-логика — правила и процедуры обработки данных.
Данные — хранение и управление данными.

Такая архитектура помогает разделить логику приложения на независимые компоненты, облегчая разработку и поддержку.


Эти модели играют важную роль в проектировании и реализации сетей, обеспечивая понимание процессов передачи данных и взаимодействия между различными компонентами сети.
Выбор конкретной модели зависит от требований проекта и особенностей решаемой задачи.
#229_NTWK_PkS_TOS_TP

Уровни модели OSI.

Модель OSI (Open Systems Interconnection) — эталонная модель взаимодействия открытых систем, разработанная Международной организацией по стандартизации (ISO). Она делит процесс передачи данных между системами на семь уровней, каждый из которых отвечает за определённые функции и задачи.

Физический уровень (Physical Layer) — отвечает за передачу битов данных через физическую среду (например, кабель, радиоэфир). Он определяет физические характеристики среды передачи, такие как тип кабеля, разъемы, уровни напряжения и т.д. Примеры технологий этого уровня включают Ethernet, Wi-Fi, USB.

Канальный уровень (Data Link Layer) — для управления доступом к физической среде передачи данных и контроля ошибок при передаче данных.
Канальный уровень обеспечивает надёжную передачу данных между двумя устройствами, находящимися непосредственно рядом друг с другом. Он также разбивает данные на кадры (frames), добавляет заголовки для управления передачей и проверяет целостность переданных данных.
Примером протокола канального уровня является Ethernet.

Сетевой уровень (Network Layer) — отвечает за маршрутизацию пакетов данных от источника до получателя через сеть. Он решает вопросы адресации и выбора наилучшего пути передачи данных.
На этом уровне работают протоколы IP (Internet Protocol) и ICMP (Internet Control Message Protocol).

Транспортный уровень (Transport Layer) — обеспечивает надёжность передачи данных между конечными точками сети. Он управляет потоками данных, контролирует ошибки и обеспечивает восстановление потерянных данных.
Протоколы транспортного уровня, такие как TCP (Transmission Control Protocol) и UDP (User Datagram Protocol), обеспечивают разные уровни надежности передачи данных.

Сеансовый уровень (Session Layer) — отвечает за установление, поддержание и завершение сеансов связи между приложениями.
Сеансы могут включать аутентификацию, синхронизацию и управление диалогом.
Примерами протоколов сеансового уровня являются NFS (Network File System) и RPC (Remote Procedure Call).

Уровень представления (Presentation Layer) — занимается преобразованием форматов данных, чтобы они были понятны обоим участникам коммуникации.
Этот уровень может обеспечивать шифрование/дешифровку данных, сжатие и распаковку информации.
Примеры включают кодировки ASCII, Unicode, а также различные форматы файлов (JPEG, MPEG и др.).

Прикладной уровень (Application Layer) — верхний уровень модели OSI, который взаимодействует напрямую с пользовательскими приложениями.
Прикладной уровень предоставляет сервисы, которые используются программным обеспечением для доступа к сетевым ресурсам.
Примеры протоколов прикладного уровня включают HTTP (HyperText Transfer Protocol), FTP (File Transfer Protocol), SMTP (Simple Mail Transfer Protocol).
#230_NTWK_PkS_TOS_TP

Уровни модели TCP/IP.

Модель TCP/IP (Transmission Control Protocol/Internet Protocol) — архитектура сетей, которая лежит в основе Интернета.
В отличие от семиуровневой модели OSI, модель TCP/IP состоит всего из четырёх уровней. Каждый уровень описывает определенный набор функций, необходимых для передачи данных между устройствами в сети.


Уровень приложения (Application Layer) — соответствует нескольким верхним уровням модели OSI (прикладному уровню, уровню представления и сеансовому уровню).
Уровень приложения включает в себя протоколы, обеспечивающие взаимодействие между приложениями пользователя и сетью:

HTTP (Hypertext Transfer Protocol)используется для передачи веб-страниц;
FTP (File Transfer Protocol) — передача файлов;
SMTP (Simple Mail Transfer Protocol) — отправка электронной почты;
DNS (Domain Name System) — разрешение доменных имен в IP-адреса.


Транспортный уровень (Transport Layer) — аналогичен транспортному уровню модели OSI и отвечает за доставку данных между конечными узлами сети.

Основные протоколы транспортного уровня:

TCP (Transmission Control Protocol) — обеспечивает надежную передачу данных с подтверждением доставки и контролем ошибок;
UDP (User Datagram Protocol) — менее надежный, но более быстрый способ передачи данных без подтверждения доставки.


Межсетевой уровень (Internet Layer) — эквивалентен сетевому уровню модели OSI. Его основная функция — маршрутизация пакетов данных между различными сетевыми сегментами.

Ключевыми компонентами межсетевого уровня являются:
IP (Internet Protocol) — основной протокол, отвечающий за адресацию и маршрутизацию пакетов данных;
ICMP (Internet Control Message Protocol) — используется для диагностики состояния сети и отправки сообщений об ошибках.


Уровень доступа к сети (Link Layer) — объединяет функции физического и канального уровней модели OSI и отвечает за передачу данных между узлами внутри одной локальной сети.

На этом уровне работают такие технологии, как:
Ethernet — наиболее распространённый стандарт для локальных сетей;
Wi-Fi — беспроводные сети;
PPP (Point-to-Point Protocol) — протокол для соединения двух узлов по последовательному каналу.

Таким образом, модель TCP/IP представляет собой упрощённую версию модели OSI, объединяя некоторые её уровни для повышения эффективности работы.
Эта модель была разработана раньше, чем модель OSI, и именно она стала основой для создания глобальной сети Интернет.
#231_NTWK_PkS_TOS_TP

Что такое IP-адрес?

IP-адрес (от англ. Internet Protocol Address) — уникальный числовой идентификатор устройства, подключенного к компьютерной сети, использующей протокол IP.
Этот адрес позволяет другим устройствам находить и обмениваться данными с данным устройством.

IP-адрес состоит из последовательности чисел, разделённых точками, и существует в двух основных версиях:

IPv4 — использует 32-битное представление адреса, что даёт около 4 миллиардов возможных уникальных адресов.
Адрес IPv4 обычно записывается в виде четырех десятичных чисел, каждое из которых находится в диапазоне от 0 до 255.
Пример:
192.168.1.100

IPv6 — использует 128-битное представление адреса, что значительно увеличивает количество возможных уникальных адресов.
Адрес IPv6 записывается в шестнадцатеричной системе счисления и делится на восемь групп по четыре цифры, разделенные двоеточиями.
Пример:
2001:db8:85a3:0:0:8a2e:370:7334


Функциональность IP-адресов:
Идентификация устройства — IP-адрес позволяет однозначно определить устройство в сети.
Маршрутизация — с помощью IP-адресов осуществляется маршрутизация данных между разными сетями и устройствами.
Разделение на подсети — IP-адреса можно разделить на части, соответствующие разным подсетям, что облегчает управление большими сетями.

Типы IP-адресов:
Публичные IP-адреса — доступны из любой точки интернета и уникальны во всей сети. Они назначаются интернет-провайдерами и используются для прямого подключения устройств к интернету.
Частные IP-адреса — используются внутри частных сетей (например, домашних или корпоративных) и недоступны извне без использования специальных механизмов, таких как NAT (Network Address Translation).
Частные диапазоны адресов определены стандартом RFC 1918:
10.0.0.010.255.255.255
172.16.0.0172.31.255.255
192.168.0.0192.168.255.255


Динамические и статические IP-адреса:

Динамический IP-адрес — назначается устройству временно, например, при каждом новом подключении к сети.
Обычно используется DHCP-сервером (Dynamic Host Configuration Protocol).

Статический IP-адрес — постоянный адрес, назначенный устройству вручную администратором сети.
Часто используется для серверов и других важных устройств, которым требуется постоянное подключение.


IP-адреса играют ключевую роль в работе современных компьютерных сетей, обеспечивая возможность обмена данными между устройствами и маршрутизацию трафика в интернете.
#232_NTWK_PkS_TOS_TP

Для чего используется маска подсети?

Маска подсети (или subnet mask) — это 32-битное число, которое используется вместе с IP-адресом для определения границ подсетей в сетях, работающих по протоколу IPv4.
Маска подсети позволяет разделить большую сеть на несколько меньших подсетей, что делает возможным более эффективное использование IP-адресов и улучшает управление трафиком.


Как работает маска подсети?

Маска подсети состоит из серии единиц, за которыми следуют нули.
Она применяется к IP-адресу путем побитовой операции AND (логическое И). Результатом этой операции является сетевая часть IP-адреса, которая указывает на принадлежность устройства к определенной подсети.

Пример маски подсети:

255.255.255.0

В двоичном представлении эта маска выглядит следующим образом:

11111111.11111111.11111111.00000000

Если применить эту маску к IP-адресу, например:

192.168.1.100

Получится следующее:

192.168.1.100 & 255.255.255.0 = 192.168.1.0

Здесь 192.168.1.0 — это сетевая часть IP-адреса, указывающая на то, что данное устройство принадлежит к подсети с префиксом /24.

Префикс CIDR (Classless Inter-Domain Routing) — сокращенная форма записи маски подсети.
Чтобы не указывать полную маску, указывается количество бит, используемых для обозначения сетевой части IP-адреса. Например, маска 255.255.255.0 эквивалентна префиксу /24, потому что первые 24 бита маски равны единицам.


Зачем нужна маска подсети?

Управление большими сетямиразбиение большой сети на подсети позволяет лучше управлять трафиком и улучшать производительность сети.
Эффективное использование IP-адресов — подсети позволяют более гибко распределять IP-адреса, избегая их нехватки.
Безопасность — разделив сеть на подсети, можно ограничить доступ к определенным сегментам сети, повышая безопасность.
Маршрутизация — маршрутизаторы используют маски подсети для определения того, куда направлять пакеты данных.

Использование масок подсети является важным аспектом проектирования и настройки сетей, особенно в крупных организациях и провайдерских сетях.
#233_NTWK_PkS_TOS_TP

Какая разница между IPv4 и IPv6?

Протоколы IPv4 и IPv6 — две версии протокола IP (Internet Protocol), используемые для передачи данных в компьютерных сетях.
Несмотря на схожесть задач, у них есть существенные различия, о которых стоит знать.

Размер адресного пространства
IPv4: Адресное пространство составляет 32 бита, что дает примерно 4 миллиарда уникальных адресов (2^32).
IPv6: Адресное пространство увеличено до 128 бит, что обеспечивает гораздо больше уникальных адресов — порядка 2^128. Это приблизительно 3.4×10383.4×1038 адресов, что значительно превышает потребности современного интернета.

Формат адреса:
IPv4: Адрес представлен четырьмя группами десятичных чисел, разделенными точками, например: 192.168.1.100.
IPv6: Адрес представлен восемью группами шестнадцатеричных цифр, разделенных двоеточиями, например: 2001:db8:85a3:0:0:8a2e:370:7334.

Поддержка качества обслуживания (QoS):
IPv4: QoS реализован опционально и требует дополнительных настроек.
IPv6: Поддержка QoS встроена в сам протокол, что упрощает настройку и повышает эффективность передачи данных.

Автоматическая конфигурация:
IPv4: Для автоматической конфигурации используется протокол DHCP (Dynamic Host Configuration Protocol).
IPv6: Поддерживается автоматическая конфигурация адресов с использованием механизма Stateless Address Autoconfiguration (SLAAC), что упрощает развертывание больших сетей.

Безопасность:
IPv4: Безопасность обеспечивается дополнительными протоколами, такими как IPSec.
IPv6: Безопасность встроена в сам протокол, включая поддержку IPSec на уровне ядра.

Маршрутизация:
IPv4: Маршрутизация основана на таблицах маршрутов, которые могут быть перегружены в больших сетях.
IPv6: Благодаря большему количеству адресов и улучшенной структуре заголовков, маршрутизация становится более эффективной и масштабируемой.

Совместимость:
IPv4: Широко поддерживается всеми существующими сетевыми устройствами и сервисами.
IPv6: Хотя поддержка IPv6 растет, переход на этот протокол еще не завершен, и многие устройства и сервисы продолжают использовать IPv4.


Переход с IPv4 на IPv6 осложнен тем, что эти два протокола несовместимы на уровне сетевого слоя. Для обеспечения совместимости используются различные механизмы, такие как туннелирование (например, 6to4, Teredo) и двойные стеки (Dual Stack), когда устройства поддерживают оба протокола одновременно.


Основное различие между IPv4 и IPv6 заключается в размере адресного пространства и возможности более эффективного управления сетью благодаря новым функциям, таким как встроенная безопасность и автоматическая конфигурация.
Переход на IPv6 необходим для решения проблемы исчерпания адресов IPv4 и улучшения производительности и безопасности сети.
#234_NTWK_PkS_TOS_TP

Сколько памяти необходимо для хранения IPv4?

Для хранения одного IPv4-адреса требуется 32 бита (или 4 байта) памяти.
Это связано с тем, что IPv4-адрес состоит из четырех частей, каждая из которых представлена числом от 0 до 255, что занимает 8 бит (1 байт) на каждую часть.

Итак, общее количество памяти для хранения одного IPv4-адреса равно:

4 \times 8 = 32 \text{ бита} = 4 \text{ байта}
#235_NTWK_PkS_TOS_TP

Для чего нужен порт?

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

Основная цель порта — обеспечить множественный доступ к различным сервисам на одном устройстве.
Например, одно устройство может одновременно обслуживать веб-сайт, электронную почту и другие сервисы.
Порты помогают ОС понять, какому процессу или сервису передать полученные данные.

Нумерация портов — порты имеют номера от 0 до 65535.

Первые 1024 порта (0–1023) называются "well-known ports" ("хорошо известные порты") и зарезервированы для стандартных сервисов:

80 — HTTP (веб-сайты)
443 — HTTPS (защищенное соединение для веб-сайтов)
25 — SMTP (электронная почта)
53 — DNS (разрешение доменных имен)
20/21 — FTP (передача файлов)


Остальные порты (1024–49151) называются "registered ports" ("зарегистрированные порты") и могут использоваться различными приложениями и сервисами.
Порты выше 49152 считаются динамическими или частными и могут свободно использоваться любыми приложениями.

Работа порта:
Когда приложение хочет отправить данные другому устройству, оно создает пакет данных, включающий IP-адрес назначения и номер порта.
Когда пакет достигает целевого устройства, ОС смотрит на номер порта и передает данные соответствующему процессу или сервису.

Например, если вы открываете веб-сайт, ваш браузер отправляет запрос на порт 80 (для HTTP) или 443 (для HTTPS) сервера. Сервер получает этот запрос и возвращает ответ через тот же порт.

Примеры использования портов:
Веб-сервер: Веб-сервер обычно слушает запросы на портах 80 (HTTP) и 443 (HTTPS).
Почтовый сервер: Почтовый сервер может слушать запросы на порте 25 (SMTP) для отправки писем и на порте 110 (POP3) или 143 (IMAP) для получения писем.
Игровые серверы: Игры часто используют нестандартные порты для связи между клиентами и серверами.


Порты играют критически важную роль в организации сетевых коммуникаций, позволяя множеству приложений и сервисов работать параллельно на одном устройстве.
Без портов было бы невозможно отличить данные, предназначенные для разных приложений, что сделало бы современные сети крайне неэффективными.
#236_NTWK_PkS_TOS_TP

Сколько максимально может быть портов?

Максимальное количество портов, которое может быть использовано в сетевом взаимодействии, определяется размером поля, отведённого под номер порта в заголовке пакета протокола TCP или UDP.
Это поле имеет размер 16 бит, что означает, что максимальное количество портов составляет:

2^{16} = 65,536

Таким образом, диапазон номеров портов простирается от 0 до 65,535 включительно.
#237_NTWK_PkS_TOS_TP

Какая разница между TCP и UDP?

TCP (Transmission Control Protocol) и UDP (User Datagram Protocol) — два ключевых транспортных протокола, используемых в сетевых коммуникациях.
Оба протокола работают на транспортном уровне модели
OSI и TCP/IP, однако они существенно различаются по своим характеристикам и применению.

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

Контроль потока и перегрузки:
TCP: Включает механизмы контроля потока и предотвращения перегрузок, что позволяет регулировать скорость передачи данных в зависимости от пропускной способности сети и возможностей приёмника.
UDP: Не имеет таких механизмов. Передаваемые данные отправляются без учёта состояния сети или приёмника, что может привести к переполнению буферов и потере данных.

Соединения:
TCP: Работает по принципу установления соединения (connection-oriented). Перед началом передачи данных устанавливается виртуальный канал между отправителем и получателем, что гарантирует, что обе стороны готовы к обмену данными.
UDP: Является протоколом без установления соединения (connectionless). Данные отправляются сразу, без предварительного согласования с получателем.

Задержка и производительность:
TCP: Из-за необходимости устанавливать соединение и контролировать ошибки, TCP имеет более высокую задержку и может быть медленнее, особенно в условиях высокой нагрузки.
UDP: Меньше накладных расходов, поэтому быстрее и эффективнее для передачи небольших объёмов данных, где надёжность не критична.

Применение:
TCP: Используется для приложений, требующих надёжной передачи данных, таких как электронная почта, веб-браузинг, файловые трансферы (FTP), SSH и другие.
UDP: Применяется там, где важна низкая задержка и высокая скорость передачи, даже ценой возможной потери данных. Примеры включают голосовую связь (VoIP), видео-конференции, онлайн-игры и потоковое мультимедиа.


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