Подробное объяснение сетевого программирования, связанного с сокетами, на C/C++ в среде Windows и подробное объяснение некоторых TCP.
Подробное объяснение сетевого программирования, связанного с сокетами, на C/C++ в среде Windows и подробное объяснение некоторых TCP.

Предисловие:

В операционной системе Windows межпроцессное взаимодействие (IPC) может осуществляться с помощью различных механизмов. Ниже приведены некоторые распространенные методы взаимодействия.

  1. общая память:Например черезWin32 APIизCreateFileMappingиOpenFileMappingфункция,Процесс может создать область общей памяти.,Другие процессы открывают этот отображенный в памяти объект с тем же именем.,Для выполнения операций чтения и записи в одной и той же памяти,достичь цели обмена данными.
  2. Трубка:включая анонимные каналыиименованный канал。Анонимные каналы в основном используются для родительских процессов.имежду дочерними процессамииз Односторонняя связь;именованный канал则可以существовать Не актуальноиз Двусторонняя связь между процессами,Его также можно использовать через Интернет.
  3. очередь сообщений:Windowsпредоставил наборочередь Механизм сообщений, позволяющий процессам отправлять и получать сообщения. очередь Сообщения могут реализовать асинхронную передачу сообщений, что подходит для сценариев, когда сообщения необходимо хранить до тех пор, пока получатель не будет готов.
  4. Розетки:Хотя в первую очередь предназначен для сетевых коммуникаций,Но его также можно использовать локально.,Обеспечивает надежную двустороннюю связь.

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

  • на сетевом уровне,IP-адресИспользуется для идентификации сетиизкаждый хост,Убедитесь, что данные направляются в правильное место назначения.
  • Номер порта транспортного уровняипротокол транспортного уровня(нравитьсяTCPилиUDP)объединить,Затем далее найдите хостизкто-тоидентификацияприложение(илипроцесс)。каждыйномер портасвязанный с положительнымсуществоватьбегатьиз Служитьилиприложение,так,Через IP-адрес,протокол,номер порта Эта тройка,Можно однозначно идентифицировать процесс в сети во всем мире.

Что касается реализации прикладного уровня, то интерфейс программирования сокетов является одним из наиболее широко используемых механизмов в настоящее время. Он берет свое начало из системы UNIX BSD и стал стандартом кросс-платформенного сетевого программирования.

Можно сказать,“Всё есть розетка


В этой статье будет создан простой TCP-эхо-сервер и клиент на основе программирования сокетов под Windows, а также дано подробное объяснение некоторых кодов и принципов TCP.

Что такое розетка?

Socket,Часто переводится как «розетка» на китайском языке.,Это очень важная концепция в компьютерных сетях.,это сетевое общениеиз Одна из основ。Socket Предоставляет механизм межсетевого взаимодействия, позволяющий приложениям на двух разных компьютерах обмениваться данными по сети. На более конкретном уровне Socket Его можно рассматривать как интерфейс для двух программ в сети для связи через двустороннюю связь. Некоторые люди также рассматривают сокет как специальный файл, и с ним работают некоторые функции сокета (чтение/запись ввода-вывода, открытие, закрытие).

Принцип работы Socket основан на модели CS, где одна сторона играет роль клиента, а другая — роль сервера. Общий процесс в Windows выглядит следующим образом:

Процесс на стороне сервера:

0.Инициализируйте сетевое окружение.

первый,Необходимо инициализировать сетевую библиотеку,нравитьсясуществоватьWindowsиспользуется в системеWSAStartupфункцияинициализацияWinsockБиблиотека,Явная инициализация обычно не требуется в системах Unix/Linux.

Язык кода:cpp
копировать
#include <windows.h>
#include <iostream>

#pragma comment(lib,"ws2_32.lib")

int main()
{
	// 0. Инициализируйте сетевое окружение
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Инициализация Winsock не удалась\n");
		return -1;
	}
    printf("Winsock успешно инициализирован\n");

    // Разместите здесь код сетевой связи...

	// Очистка ресурсов Winsock
	WSACleanup();
    printf("Ресурсы очищены\n");
	return 0;
}

1. Создайте сокет:

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

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

Язык кода:javascript
копировать
SOCKET WSAAPI socket(
  [in] int af,
  [in] int type,
  [in] int protocol
);

af:Прямо сейчаспротоколдомен,также известный какпротоколклан(family)。Обычно используетсяизпротоколкланиметь,AF_INET означает IPv4. AF_INET6 означает IPv6 и так далее.

type:обозначениеsocketтип。Обычно используетсяизsocketтипиметь,SOCK_STREAM представляет TCP-соединение.,SOCK_DGRAM означает UDP и т. д.

protocol:Итак, имя означает,то естьобозначениепротокол。Обычно используетсяизпротоколиметь,IPPROTO_TCP, IPPTOTO_UDP и т. д.,Они соответствуют соответственноTCPпередача инфекциипротокол、UDPпередача инфекциипротокол

Служить端и Все клиентские программы будут вызыватьsocketфункция СоздайтеSocket。Нужен в это времяобозначениекоммуникацияизпротоколдомен、типиобозначениепротокол(обозначениепротокол Обычно заполняется0,Пусть система выбереттиппереписыватьсяизпо умолчаниюпротокол)。

Язык кода:cpp
копировать
	// 1. Создать дескриптор сервера (сокет)
	// AF_INET ipv4 AF_INET6 ipv6
	// SOCK_STREAM --> TCP       SOCK_DREAM --> UDP
	SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockServer)
	{
		printf("Не удалось создать дескриптор сервера\n");
		WSACleanup();
		return -1;
	}
	printf("1. Сервер успешно создан\n");

Когда функция сокета() вызывается для создания сокета, сокету не назначается конкретный сетевой адрес (IP-адрес и номер порта). Чтобы назначить сокету адрес (в основном IP-адрес и номер порта), следующим шагом является функцияbind().

2. Привязать адрес (привязать):

Служить端想существоватьего созданиеизSocketПривяжите одинIP-адресиномер порта,Нужно позвонитьbind()функция,И передайте параметр, содержащий информацию об адресе (например, структуру SOCKADDR_IN). Этот шаг связывает определенный сетевой адрес с сокетом.,Позволяет сокету начать прослушивание соединений с этого адреса (для сервера) или в качестве исходного адреса для последующих вызовов Connect() (для клиента).

Язык кода:javascript
копировать
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:Это до прохожденияsocket()функцияобратный звонокиздескриптор сокета。это целое число,Представляет сокет, к которому должен быть привязан адрес. Этот параметр позволяет операционной системе узнать, какому сокету вы хотите назначить информацию об адресе.

addr:Это указательsockaddrструктураизуказатель

А параметр addr типа struct sockaddr* необходимо указать в соответствии с доменом протокола, указанным при создании сокета.

Для IPv4 используется структура struct sockaddr_in, которая содержит:

sin_family: Член семейства адресов, обычно установленный в AF_INET для IPv4.

sin_port: Номер порта, выраженный в сетевом порядке байтов.

sin_addr: Структура, содержащая адрес IPv4. Ее член s_addr хранит 32-битный адрес IPv4, также в сетевом порядке байтов.

Аббревиатура IPv6

addrlen:этоsocklen_tтипизценить,Указывает размер структуры адреса, на которую указывает адрес.

Язык кода:cpp
копировать
	// 2. Привязать номер порта и IP-адрес
	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9870);// номер порта  host to net short
	//addr.sin_addr.S_un.S_addr должен быть установлен на IP-адрес, на котором расположен сервер
	//addr.sin_addr.S_un.S_addr = INADDR_ANY; // IP-адрес Все IP в порядке
	//addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.4"); // идентификацияIP-адрес 
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");   //Локальный адрес
	if (bind(sockServer, (sockaddr*)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
	{
		printf("привязочный номер неудачно\n");
		closesocket(sockServer);
		WSACleanup();
		return -1;
	}
	printf("2. обязательный номер портауспех\n");

После вызова функций socket() иbind() вам следует вызвать метод Listen() для мониторинга сокета. Если клиент вызывает метод Connect() для выдачи запроса на соединение, сервер получит запрос.

3. Прислушайтесь к соединениям:

Вызов сервераlistenфункция

int listen(int sockfd, int backlog);

Начните прослушивать запросы на подключение к связанному порту.

sockfd:мониторизsocket

backlog:еще нетaccept()вызов принятьиззапрос на соединениеизмаксимальное количество,Включая соединения, которые завершили трехстороннее рукопожатие, но еще не были обработаны серверным процессом через метод Accept(). это означает,Если быстро поступает большое количество запросов на соединение,Запросы, превышающие это значение, могут быть отклонены.

Язык кода:cpp
копировать
	// 3. мониторномер порт(сообщить операционной системе,и Текущая программа устанавливает логическую связь)
	if (listen(sockServer, 5) == SOCKET_ERROR)
	{
		printf("мониторномер неудачно\n");
		closesocket(sockServer);
		WSACleanup();
		return -1;
	}
	printf("3. мониторномер портауспех\n");

TCP-сервер настраивает и начинает прослушивать запросы на подключение для указанного IP-адреса и порта, последовательно вызывая функции Socket(), Bind() и Listen(). Конкретно:

Socket() создает несвязанный сокет.

bind() привязывает сокет к определенному IP-адресу и номеру порта.

Listen() переводит сокет в режим прослушивания и устанавливает максимальную длину очереди ожидания соединений.

Далее вам следует использовать функцию Connect(), чтобы попытаться установить соединение с определенным IP-адресом и портом сервера. Это действие включает в себя трехэтапный процесс установления связи TCP для установления надежного соединения.

4. Подтвердите соединение:

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

Язык кода:javascript
копировать
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

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

addr:Это указательstruct sockaddr * указатель, используемый для получения информации об адресе клиента. Когда метод Accept() завершает работу успешно, эта структура будет заполнена адресом клиента и информацией о порте, чтобы сервер знал, какой клиент инициировал соединение.

addrlen:указывает наsocklen_tтипизуказатель,Используется для указания размера структуры, на которую указывает адрес. Перед вызовом Accept(),Это значение необходимо сначала инициализировать,сообщить ядру размер структуры,После звонка,Обычно обновляется, чтобы отразить фактический размер заполненной структуры адреса.

Язык кода:cpp
копировать
	while (true)
	{
		// 4. Получить клиентское соединение Будет создан новый сокет (он же тег клиента)
		printf("4. Приготовьтесь дождаться прибытия клиента\n");
		SOCKADDR_IN clientAddr = {};
		int nAddrLen = sizeof(SOCKADDR_IN);
		SOCKET sockClient = accept(sockServer, (sockaddr*)&clientAddr, &nAddrLen);
		if (INVALID_SOCKET == sockClient)
		{
			printf("Получить клиентское соединениенеудача\n");
			continue; // После обработки ошибок продолжайте ждать следующего клиента.
		}
		printf("4. Получить клиентское соединениеуспех\n");
		// Цикл для общения с клиентом

		// Закрыть клиентский сокет
		closesocket(sockClient);
		printf("Текущий клиент отключился, ожидание следующего клиента...\n");
	}

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

5. Передача данных:

Чтение данных:Обычно это делается с помощьюrecv()илиread()функцияот соединенияизв розетке Чтение данные. Эти функции позволяют программам читать данные, отправленные клиентом или сервером.

Отправить данные:Так же,Они могут отправлять сообщения друг другу с помощью функций send() или write(). Эти функции записывают данные в сокет,и затем передается другой стороне.

Для TCP-соединений передача данных основана на потоках, что обеспечивает порядок и надежность данных, тогда как для UDP данные передаются в виде дейтаграмм, и порядок не гарантируется и может быть ненадежным.

дляWindowsПлатформа, которую я обычно используюrecvиsendфункцияруководитьI/Oдействовать

Язык кода:cpp
копировать
int recv(
  SOCKET s,
  char FAR *buf,
  int len,
  int flags
);

s:дескриптор сокета,Возвращается функцией сокета(), которая ранее создала сокет. Он идентифицирует конечную точку связи для чтения данных.

buf:указатель на буферизуказатель,Этот буфер используется для приема данных. Данные будут считываться в этот буфер.

len:буфериздлина,В байтах. Этот параметр указывает максимальный объем данных, которые можно получить из сокета.

flags:контрольный приемдействоватьизлоготип。общийизлоготипиметьMSG_PEEK(Предварительный просмотр данных, не удаляя их из очереди приема)ждать。нравиться Если вы не используете специальные функции,Обычно можно установить на 0.

recvфункцияиз返回ценитьиметь Несколько типичных ситуаций,Каждый представляет собой разное значение:

значение больше 0:выражатьуспех Полученные данные,Возвращаемое значение — это фактическое количество полученных байтов. Это означает, что данные были успешно прочитаны из буфера сокета в предоставленный буфер.

значение, равное 0:Обычно это означает, что соединение было закрыто другой стороной.。существоватьTCPПодключение,Когда партнер выполняет обычный процесс завершения работы (отправляет пакет FIN),и все оставшиеся данные получены,Recv может вернуть 0. Это означает нормальное завершение передачи данных.

значение меньше 0:Это означает, что произошла ошибка。существоватьWindowsв системе,Значение ошибки обычно равно SOCKET_ERROR (обычно определяется как -1). в это время,Вам нужно вызвать WSAGetLastError(), чтобы получить конкретный код ошибки.,Для дальнейшего анализа причины ошибки,Сравниватьнравиться Сеть недоступна、Соединение прерванождатьвопрос。

Если для сокета установлен неблокирующий режим, функция Recv также может немедленно вернуться, если нет данных для чтения. В это время возвращаемым значением может быть код ошибки WSAEWOULDBLOCK, указывающий, что вызов следует повторить позже и не следует. расценивать как ошибку. Кроме того, в некоторых случаях Recv может также возвращать -1, если операция приема прерывается сигналом, а errno (в системах POSIX) или WSAGetLastError() (в Windows) может быть установлено в EINTR, указывая, что операция была прервана. Нужно попробовать еще раз.

Язык кода:cpp
копировать
int send(
  SOCKET s,
  const char FAR *buf,
  int len,
  int flags
);

s:То же самоедескриптор сокета,идентифицированный Отправить Конечная точка связи для данных.

buf:Укажите на желание Отправить указатель данных на буфер. Данные будут считаны из этого буфера и отправлены подключенному узлу.

len:отправитьизданныеиздлина,В байтах.

flags:иrecvсерединаизflagsпохожий,Флаг, используемый для управления операциями отправки. Общие из них включают MSG_OOB (отправка внеполосных данных) и т. д. Обычно,Если не требуется никаких специальных операций,Можно установить на 0.

Возвращаемое значение функции send также имеет несколько возможных ситуаций, каждая из которых имеет определенное значение:

значение больше 0:выражатьуспех发送了данные,Возвращаемое значение — это количество фактически отправленных байтов. Это может быть меньше общего количества байтов, которые вы пытаетесь отправить.,Особенно если установлен флаг MSG_PARTIAL или операция прерывается сигналом,Но обычно оно должно быть равно количеству байтов, которые вы запрашиваете для отправки.,пока не Произошла ошибкаили В неблокирующем режимеизособые обстоятельства。

значение, равное 0:эта ситуациясуществоватьTCP编程середина是不общийиз,Обычно означает, что данные не отправляются,Это может быть связано с тем, что сокет был закрыт или что-то серьезное пошло не так.

значение меньше 0:выражать发送действоватьнеудача。существоватьWindowsв системе,Обычно это SOCKET_ERROR (значение -1). в это время,Вам нужно вызвать WSAGetLastError(), чтобы получить подробный код ошибки.,примернравиться Сеть недоступна、Соединение прервано、Буфер заполнен и так далее.

В частности, когда сокет установлен в неблокирующий режим, если буфер отправки заполнен или дополнительные данные не могут быть отправлены временно по другим причинам, send может немедленно вернуть SOCKET_ERROR, а WSAGetLastError() возвращает WSAEWOULDBLOCK, указывая, что данные не могут быть отправлены немедленно. , надо попробовать еще раз позже. Кроме того, если операция отправки прерывается сигналом, в некоторых системах возвращаемое значение также может быть -1, а индикацией кода ошибки является EINTR, который также необходимо обработать и операцию отправки можно повторить.

Язык кода:cpp
копировать
// Цикл для общения с клиентом
while (true)
{
	char szData[1024] = {};
	int ret = recv(sockClient, szData, sizeof(szData) - 1, 0);

	if (ret > 0)
	{
		szData[ret] = '\0'; // Добавить терминатор строки
		printf("5. Данные клиента успешно получены [%s]\n", szData);

		// Отправить эхо-данные
		ret = send(sockClient, szData, ret, 0);
		if (ret == SOCKET_ERROR)
		{
			printf("Отправить данныенеудача\n");
			break; // Не удалось отправить, отключитесь от клиента
		}
	}
	else if (ret == 0) // Клиент закрывает соединение
	{
		printf("Клиент активно отключился.\n");
		break; // Выйдите из цикла обычным образом и подготовьтесь к обработке следующего клиента.
	}
	else // Произошла ошибка
	{
		printf("Не удалось получить данные клиента\n");
		break; // Отключение после обработки ошибки
	}
}

6. Полный код на стороне сервера

Язык кода:cpp
копировать
#include <windows.h>
#include <iostream>

#pragma comment(lib,"ws2_32.lib")

int main()
{
	// 0. Инициализируйте сетевое окружение
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Инициализация Winsock не удалась\n");
		return -1;
	}
	printf("Winsock успешно инициализирован\n");
	// 1. Создать дескриптор сервера (сокет)
	// AF_INET ipv4 AF_INET6 ipv6
	// SOCK_STREAM --> TCP       SOCK_DREAM --> UDP
	SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockServer)
	{
		printf("Не удалось создать дескриптор сервера\n");
		WSACleanup();
		return -1;
	}
	printf("1. Сервер успешно создан\n");

	// 2. Привязать номер порта и IP-адрес
	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9870);// номер порта  host to net short
	//addr.sin_addr.S_un.S_addr указывает расположение хоста сервера.
	//addr.sin_addr.S_un.S_addr должен быть установлен на IP-адрес, на котором расположен сервер
	//addr.sin_addr.S_un.S_addr = INADDR_ANY; // IP-адрес Все IP в порядке
	//addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.4"); // идентификацияIP-адрес 
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");   //Локальный адрес
	if (bind(sockServer, (sockaddr*)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
	{
		printf("привязочный номер неудачно\n");
		closesocket(sockServer);
		WSACleanup();
		return -1;
	}
	printf("2. обязательный номер портауспех\n");

	// 3. мониторномер порт(сообщить операционной системе,и Текущая программа устанавливает логическую связь)
	if (listen(sockServer, 5) == SOCKET_ERROR)
	{
		printf("мониторномер неудачно\n");
		closesocket(sockServer);
		WSACleanup();
		return -1;
	}
	printf("3. мониторномер портауспех\n");

	while (true)
	{
		// 4. Получить клиентское соединение Будет создан новый сокет (он же тег клиента)
		printf("4. Приготовьтесь дождаться прибытия клиента\n");
		SOCKADDR_IN clientAddr = {};
		int nAddrLen = sizeof(SOCKADDR_IN);
		SOCKET sockClient = accept(sockServer, (sockaddr*)&clientAddr, &nAddrLen);

		if (INVALID_SOCKET == sockClient)
		{
			printf("Получить клиентское соединениенеудача\n");
			continue; // После обработки ошибок продолжайте ждать следующего клиента.
		}
		printf("4. Получить клиентское соединениеуспех\n");

		// Цикл для общения с клиентом
		while (true)
		{
			char szData[1024] = {};
			int ret = recv(sockClient, szData, sizeof(szData) - 1, 0);

			if (ret > 0)
			{
				szData[ret] = '\0'; // Добавить терминатор строки
				printf("5. Данные клиента успешно получены [%s]\n", szData);

				// Отправить эхо-данные
				ret = send(sockClient, szData, ret, 0);
				if (ret == SOCKET_ERROR)
				{
					printf("Отправить данныенеудача\n");
					break; // Не удалось отправить, отключитесь от клиента
				}
			}
			else if (ret == 0) // Клиент закрывает соединение
			{
				printf("Клиент активно отключился.\n");
				break; // Выйдите из цикла обычным образом и подготовьтесь к обработке следующего клиента.
			}
			else // Произошла ошибка
			{
				printf("Не удалось получить данные клиента\n");
				break; // Отключение после обработки ошибки
			}
		}

		// Закрыть клиентский сокет
		closesocket(sockClient);
		printf("Текущий клиент отключился, ожидание следующего клиента...\n");
	}

	// После завершения основного цикла закройте сокет сервера.
	closesocket(sockServer);
	// Очистка ресурсов Winsock
	WSACleanup();
	printf("Ресурсы очищены\n");
	return 0;
}

Клиентский процесс

За исключением того, что привязка и прослушивание отсутствуют, процесс установления сокета аналогичен серверному, поэтому он опускается.

Просто поговорим о Connect()

Подключиться к серверу

Язык кода:javascript
копировать
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:Это до прохождениявызовsocket()функциясоздаватьиздескриптор сокета。

addr:Это указательstruct Указатель на структуру sockaddr. Краткое введение в серверную часть sockaddr

addrlen:это указатель,Указатель на переменную, в которой хранится размер структуры адреса.

После вызова функции Connect() она попытается установить соответствующее соединение с сервером по указанному адресу. В случае успеха функция немедленно возвращает 0. Если соединение не может быть установлено немедленно (например, из-за того, что сеть недоступна или сервер не отвечает), функция блокируется до тех пор, пока соединение не будет установлено или не произойдет тайм-аут/ошибка, после чего возвращается -1, а подробности можно получить с помощью кода ошибки errno или WSAGetLastError() (под Windows).

Язык кода:cpp
копировать
    SOCKADDR_IN addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9870);
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // IP-адрес 
    int ret = connect(sockClient, (sockaddr*)&addr, sizeof(SOCKADDR_IN));

    if (SOCKET_ERROR == ret)
    {
        printf("Подключиться к сбой сервера\n");
        return -1;
    }
    while (1)

клиентский код

Язык кода:cpp
копировать
#include <windows.h>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
	// 0. Инициализируйте сетевое окружение
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Инициализация Winsock не удалась\n");
		return -1;
	}
	printf("Winsock успешно инициализирован\n");


    // 1. Создать дескриптор клиента (сокет)
    // AF_INET ipv4
    // SOCK_STREAM --> TCP       SOCK_DREAM --> UDP
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

    if (INVALID_SOCKET == sockClient)
    {
        printf("Не удалось создать дескриптор клиента\n");

        return -1;
    }
    printf("1. Дескриптор клиента успешно создан\n");
    // 2. Подключиться к серверуномер портаиIP-адрес

    SOCKADDR_IN addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9870);
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // IP-адрес 
    int ret = connect(sockClient, (sockaddr*)&addr, sizeof(SOCKADDR_IN));

    if (SOCKET_ERROR == ret)
    {
        printf("Подключиться к сбой сервера\n");
        return -1;
    }
    while (1)
    {

        char buf[1024] = { 0 };
        printf("Пожалуйста, введите символы:");
        //Читаем по одной строке из консоли
        gets_s(buf);

        // 3. Генерация данных на сервере.
        
        int retSend = send(sockClient, buf, strlen(buf), 0);
        // retSend !=13 Отправка не удалась
        // 4. Примите данные сервера

        char szRecv[4096] = {};
        int retRecv = recv(sockClient, szRecv, 4096, 0);
        if (retRecv <= 0)
        {
            printf("Не удалось принять данные сервера\n");
            return -1;
        }
        printf("Полученные данные сервера: %s\n", szRecv);

    }
    // 5. Закройте дескриптор клиента.
    closesocket(sockClient);


    return 0;
}

Запустить скриншот


Трехстороннее TCP-подтверждение в сокете устанавливает соединение:

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

чтобы закрыть соединение.

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

Трехстороннее рукопожатие происходит при подключении клиента. При вызове метода Connect() нижний уровень выполняет трехстороннее рукопожатие через протокол TCP.

Общий процесс выглядит следующим образом

Первое рукопожатие:

1. Клиент устанавливает флаг SYN в 1.

2. Сгенерируйте случайный 32-битный серийный номер seq=J. Этот серийный номер может содержать после себя данные (размер данных).

Второе рукопожатие:

1. Сервер получает соединение клиента: ACK=1.

2. Сервер отправит обратно порядковый номер подтверждения: ack = порядковый номер клиента + длина данных + SYN/FIN (рассчитывается в одном байте)

3. Сервер инициирует запрос на соединение с клиентом: SYN=1.

4. Сервер сгенерирует случайный порядковый номер: seq = K.

Третье рукопожатие:

1. Клиент отвечает на запрос подключения сервера: ACK=1.

2. Клиент отвечает, что получил данные от сервера: ack = серийный номер сервера + длина данных + SYN/FIN (рассчитывается как один байт)

Четырехстороннее TCP-подтверждение в сокете освобождает соединение:

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

И клиент, и сервер могут активно инициировать отключение. Тот, кто первым вызовет метод close(), инициирует его.

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

Общий процесс выглядит следующим образом

1. Процесс приложения сначала вызывает close, чтобы активно закрыть соединение. В это время TCP отправляет FIN M;

2. После того, как другой конец получает FIN M, он выполняет пассивное закрытие и подтверждает FIN. Его прием также передается процессу приложения как символ конца файла, поскольку прием FIN означает, что процесс приложения больше не может получать дополнительные данные по соответствующему соединению;

3. Через некоторое время процесс приложения, который получает символ конца файла, вызывает close, чтобы закрыть свой сокет. Это заставляет его TCP также отправлять FIN N;

4. TCP отправителя отправителя, который получает этот FIN, подтверждает его.


Чтобы привлечь хорошие идеи

В настоящее время echoServer (эхо-сервер) может обрабатывать только одно клиентское соединение.,Что делать, если клиентов несколько? Подключиться к серверу? Можно ли разделить полученную клиентскую строку для идентификации,И переслать клиенту сообщение, соответствующее строке? Как передать информацию клиентам, отличным от клиента, отправившего информацию? (реализация простого чата)


Справочная статья:

Программирование сокетов Linux (не ограничиваясь Linux)

Основы сокетной связи

Официальная документация Windows


Я участвую в последнем конкурсе эссе для специального учебного лагеря Tencent Technology Creation 2024. Приходите и разделите со мной приз!

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.