Сетевое программирование – программирование сокетов Linux
Сетевое программирование – программирование сокетов Linux

Программирование сокетов Linux


Оглавление

  • Предисловие
  • Функции сокетов
  • Основы сокетов
    • Тип розетки
    • базовая структура
    • базовая функция преобразования
  • Базовое использование сокетов
  • Пример TCP-сокета
  • Пример UDP-сокета
  • Запись проблемы
  • Подвести итог

Предисловие

Сокет (сокет) — метод сетевого программирования. Через сокеты можно обеспечить не только межпроцессное взаимодействие, но и межхостовое сетевое взаимодействие. Используя эту технологию, можно обеспечить связь по всей стране. Например: компьютер в Шэньчжэне получает информацию от компьютера в Пекине.  В этой статье не рассматриваются основные принципы работы сети, а только объясняется основное использование сокетов. В основном обратитесь к разделу «Сетевое программирование Linux». Как получить исходный код этой статьи можно узнать мелким шрифтом внизу текста.

Функции сокетов

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

Основы сокетов

Тип розетки

Существует три типа сокетов: потоковые сокеты (SOCK_STREAM), дейтаграммные сокеты (SOCK_DGRAM) и необработанные сокеты.

Потоковый сокет (SOCK_STREAM)   Потоковые сокеты обеспечивают надежный поток связи, ориентированный на соединение. Если вы отправляете последовательные данные через потоковый сокет: «1», «2». Тогда порядок поступления данных в удаленное место также будет «1», «2».

Рабочий процесс Socket, ориентированный на соединение

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

Необработанный сокет (SOCK_RAM)   Необработанные сокеты в основном используются для разработки некоторых протоколов и могут выполнять операции относительно низкого уровня. Он мощный, но не так удобен в использовании, как два представленных выше сокета, а обычные программы не используют оригинальные сокеты.

базовая структура

  • структура sockaddr Эта структура используется для хранения адреса сокета. Когда требуется адрес для корреспонденции,Эта структура будет использоваться,Напримерconnect()
Язык кода:javascript
копировать
struct sockaddr {
    unsigned short sa_family; /* адрес семьи, AF_xxx */
    char sa_data[14];         /* 14 Протокольный адрес байтов */
};

sa_familyДля указанного семейства протоколов,Обычно используютсяAF_INET、AF_UNIXждать。 sa_dataдля Связь между различными семействами протоколовчаснеобходимые данные。Например,sa_familyдляAF_INETчас,sa_dataХочу пройтиIPадрес иномер порта。

  • struct sockaddr_in sockaddr_inиспользуется для храненияAF_INETАдрес сокета,вinЭто означаетInet。   представлятьsockaddrчас,Говоря об использованииAF_INETнуждаться Хочу пройтиIPиномер порта,Но я не знаю, какIPиномер порта Заполнятьsockaddrгде в。затем,Разработанныйsockaddr_in,определятьадрес иномер портачлен。в использованиичас Тольконужно заполнитьsockaddr_in,Передача параметровчас Принудительный переводдляsockaddrВот и все(Обе конструкции имеют одинаковый размер.)。
Язык кода:javascript
копировать
struct sockaddr_in {
    short int sin_family;        /* Семейство интернет-адресов */
    unsigned short int sin_port; /* номер порта */
    struct in_addr sin_addr;     /* Интернет-адрес */
    unsigned char sin_zero[8];   /* добавьте 0 (и структура sockaddr того же размера) */
};
  • struct in_addr in_addrдляsockaddr_inчлен,Используется для хранения 4 байтов IP-адресов. Необходимость Примечание означает,Это значение необходимо заполнить в соответствии с сетевыми байтами.,Это можно сделать с помощью некоторых функций преобразования.
Язык кода:javascript
копировать
struct in_addr {
    unsigned long s_addr;
};
  • struct sockaddr_un sockaddr_unиспользуется для храненияAF_UNIXАдрес сокета,Предполагается, что un представляет UNIX (без проверки). ˜Аналогично предыдущему,При использовании AF_UNIX,нужно заполнитьAF_UNIXСтруктура адресаsockaddr_un,然后Передача параметровчас Принудительный переводдляsockaddr
Язык кода:javascript
копировать
struct sockaddr_un
{
    uint8_t sun_len;
    sa_family_t sun_family;  /* AF_LOCAL */
    char sun_path[104];      /* null-terminated pathname */
};

базовая функция преобразования

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

  • htons() — «Короткий порядок байтов хоста в сети» Преобразование порядка байтов хоста в сетевой порядок байтов (работает с короткими 4 байтами без знака)
  • htonl() —— «Host to Network Long» Преобразование порядка байтов хоста в сетевой порядок байтов (работает с 8 байтами беззнакового длинного типа)
  • ntohs() — «Короткий порядок байтов сети в хосте» Преобразование порядка байтов сети в порядок байтов хоста (работает с короткими 4 байтами без знака)
  • ntohl() —— «Network to Host Long» Преобразование порядка байтов сети в порядок байтов хоста (работает с 8 байтами беззнакового длинного типа)

трансляция IP-адреса

  • inet_addr() — преобразует строку чисел и точек, представляющую IP-адрес, в беззнаковый длинный номер в сетевом порядке байтов.
  • inet_ntoa() — «Сеть в ASCII» Преобразует длинное целое число в сетевом порядке байтов в строку.

Примечаниеinet_ntoa()Возвращает указатель символа,Он определяется как указатель на функцию inet_ntoa() в статической входной строке. Итак, каждый раз, когда вы вызываете inet_ntoa(), наконец, изменится после вызова inet_ntoa() Результат, полученный функцией.

Базовое использование сокетов

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

socketВ основном используются следующие функции:

  • Функция socket() — создает сокет.
  • Функцияbind() — информация об адресе сокета привязки. (для Inet требуется входящий IP-адрес и порт; для Unix требуется входящий путь)
  • Функция Connect() — подключается к указанному сокету сервера.
  • Функция прослушивания() — Сервер прослушивает соединения на клиентском сокете.
  • Функция принятия() — принимает сокет удаленного клиента и получает информацию об адресе клиента удаленного подключения. (блокирующий интерфейс)
  • Функция send()/функция Recv() — функция для связи с подключенным потоковым сокетом.
  • Функция sendto()/функция Recvfrom() — функция для связи с несвязанными дейтаграммными сокетами.
  • Функция close() — закрывает соединение, представленное дескриптором сокета.
  • Функция Shutdown() — указывает, как закрыть сокет.
  • Функция setockopt()/функция getsockopt() - установка и получение элементов настройки сокета.
  • Функция getpeername() — получает удаленную информацию о подключенном сокете.
  • Функция gotockname() — получает информацию о локальном хосте.
  • Функция gethostbyname() — получает IP-адрес через имя домена.
  • Функция gethostbyaddr() — получает информацию о хосте через IPv4-адрес.
  • Функция getprotobyname() — получает имя сетевого протокола.

Пример TCP-сокета

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

TCP-сокет-сервер

Язык кода:javascript
копировать
void DealClientMsgThread(int fd)
{
    while(1)
    {
        char buf[1024] = {0};
        int ret = read(fd, buf, sizeof(buf));
        if (ret > 0) {
            TSVR_LOG("# RECEIVE: %s.\n", buf);

            // response dstAddr msg
            char ack[20] = {0};
            snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
            write(fd, ack, strlen(ack));
        } else {
            break;
        }
    }
    
    TSVR_LOG("Disconnect.\n");
}

int main(int argc, char *argv[])
{
    int i = 0;
    std::thread threads[MAX_NUM_THREAD];

    if (argc != 2)
    {
        TSVR_LOG("usage ./tcp_server 8080.\n");
        return -1;
    }

    string port = argv[1];

    int sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd == -1)
    {
        TSVR_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    int op = 1;
    if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
        TSVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        goto exit;
    }

    struct sockaddr_in myAddr;
    bzero(&myAddr, sizeof(myAddr));
    myAddr.sin_family = AF_INET;
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myAddr.sin_port = htons(atoi(port.c_str()));
    if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(myAddr)) == -1) {
        TSVR_LOGE("bind failed. (%s)", strerror(errno));
        goto exit;
    }

    if (listen(sockFd, MAX_SIZE_BACKLOG) == -1) {
        TSVR_LOGE("listen failed. (%s)\n", strerror(errno));
        goto exit;
    }

    while(1)
    {
        // accept
        struct sockaddr_in dstAddr;
        socklen_t addrSize = (socklen_t)sizeof(dstAddr);
        int conFd = accept(sockFd, (struct sockaddr *)&dstAddr, &addrSize);
        if (conFd < 0) {
            TSVR_LOGE("accept failed. (%s)\n", strerror(errno));
            continue;
        }
        
        if (i < MAX_NUM_THREAD)
        {
            threads[i] = std::thread(DealClientMsgThread, conFd);
            threads[i].detach();
            i++;
        } else {
            TSVR_LOG("The number of threads reaches max(%d).\n", MAX_NUM_THREAD);
        }
    }

exit:
    close(sockFd);
    return 0;
}

клиент TCP-сокета

Язык кода:javascript
копировать
int main(int argc, char *argv[])
{
    int op = 1024;

    if (argc != 3)
    {
        TCLT_LOG("usage ./tcp_client <ip> <port>.\n");
        return -1;
    }

    string ipAddr = argv[1];
    string port = argv[2];

    int sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd == -1)
    {
        TCLT_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
        TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    if (setsockopt(sockFd, SOL_SOCKET, SO_SNDBUF, &op, sizeof(op)) < 0) {
        TCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    struct sockaddr_in dstAddr;
    dstAddr.sin_family = AF_INET;
    dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
    dstAddr.sin_port = htons(atoi(port.c_str()));
    // Linux TCP repeat connect directly return EISCONN.
    if (   connect(sockFd, (struct sockaddr*)&dstAddr, sizeof(struct sockaddr_in)) < 0 
        && errno != EISCONN) 
    {
        TCLT_LOGE("connect %s:%d failed. (%s)\n", ipAddr.c_str(), atoi(port.c_str()), strerror(errno));
        close(sockFd);
        return -1;
    }

    thread rTh([&]() {
        char recvBuf[1024] = {0};
        TCLT_LOG("Start write thread.\n");

        while(1)
        {
            int ret = read(sockFd, recvBuf, sizeof(recvBuf));
            if (ret > 0) {
                TCLT_LOG("# RECEIVE: %s.\n", recvBuf);
            } else {
                break;
            }
        }
    });
    
    thread wTh([&]() {
        TCLT_LOG("Start read thread.\n");

        while(1)
        {
            char sndBuf[] = "Hello World";
            int ret = write(sockFd, sndBuf, strlen(sndBuf));
            if (ret > 0)
            {
                TCLT_LOG("# SEND > %s.\n", sndBuf);
            } else {
                break;
            }
            sleep(2);
        }
    });
    
    rTh.join();
    wTh.join();

    return 0;
}

Код представляет собой очень простую обработку TCP-связи. Для облегчения понимания не используется чрезмерная инкапсуляция.

Пример UDP-сокета

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

UDP-сокет-сервер

Язык кода:javascript
копировать
int main(int argc, char *argv[])
{
    struct sockaddr_in dstAddr;
    socklen_t addrSize = sizeof(struct sockaddr_in);

    if (argc != 2)
    {
        USVR_LOG("usage ./udp_server 8080.\n");
        return -1;
    }
    
    string port = argv[1];

    int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockFd == -1)
    {
        USVR_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }
    
    int op = 1;
    if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
        USVR_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        goto exit;
    }

    struct sockaddr_in myAddr;
    bzero(&myAddr, sizeof(struct sockaddr_in));
    myAddr.sin_family = AF_INET;
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myAddr.sin_port = htons(atoi(port.c_str()));
    if (bind(sockFd, (struct sockaddr *)&myAddr, sizeof(struct sockaddr_in)) < 0) {
        USVR_LOGE("bind failed. (%s)\n", strerror(errno));
        goto exit;
    }

    while(1)
    {
        char buf[1024] = {0};
        int ret = recvfrom(sockFd, buf, sizeof(buf), 0, 
                    (struct sockaddr *)&dstAddr, &addrSize);

        if (ret > 0) {
            char ack[20] = {0};

            snprintf(ack, sizeof(ack), "Ack [%ld]", strlen(buf));
            USVR_LOG("# RECEIVE: %s.\n", buf);
            sendto(sockFd, ack, strlen(ack), 0, 
                    (struct sockaddr *)&dstAddr, addrSize);

        } else {
            USVR_LOG("recvfrom failed. (%s)\n", strerror(errno));
            break;
        }
    }

exit:
    close(sockFd);
    return 0;
}

Клиент сокета UDP

Язык кода:javascript
копировать
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        UCLT_LOG("usage ./udp_client <ip> <port>.\n");
        return -1;
    }

    string ipAddr = argv[1];
    string port = argv[2];

    int sockFd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockFd == -1)
    {
        UCLT_LOGE("socket failed. (%s)\n", strerror(errno));
        return -1;
    }

    int op = 1024;
    if (setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &op, sizeof(op)) < 0) {
        UCLT_LOGE("setsockopt failed. (%s)\n", strerror(errno));
        close(sockFd);
        return -1;
    }

    struct sockaddr_in dstAddr;
    bzero(&dstAddr, sizeof(struct sockaddr_in));
    dstAddr.sin_family = AF_INET;
    dstAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str());
    dstAddr.sin_port = htons(atoi(port.c_str()));

    thread wTh([&]() {
        UCLT_LOG("Start write thread.\n");

        while(1)
        {
            char sndBuf[] = "Hello World";
            int ret = sendto(sockFd, sndBuf, strlen(sndBuf), 0, 
                        (struct sockaddr *)&dstAddr, sizeof(struct sockaddr_in));

            if (ret > 0) {
                UCLT_LOG("# SEND: %s.\n", sndBuf);
            } else {
                break;
            }

            sleep(2);
        }
    });
    
    thread rTh([&]() {
        struct sockaddr_in dstAddr;
        UCLT_LOG("Start read thread.\n");

        while(1)
        {
            char recvBuf[1024] = {0};
            socklen_t addrSize = sizeof(struct sockaddr_in);
            bzero(&dstAddr, addrSize);
            int ret = recvfrom(sockFd, recvBuf, sizeof(recvBuf), 0, 
                        (struct sockaddr *)&dstAddr, &addrSize);

            if (ret > 0)
            {
                UCLT_LOG("# RECEIVE: %s.\n", recvBuf);
            } else {
                break;
            }
        }
    });
    
    wTh.join();
    rTh.join();
    return 0;
}

Запись проблемы

  • Как TCP-сервер и клиент точно определяют, что другая сторона находится в автономном режиме или аварийно отключена? ① Функция приема блокируется. Когда другая сторона отключается, функция приема возвращает исключение. ② Судя по кодам ошибок и сигналам, при аварийном отключении одного конца другой конец получит сигнал SIGPIPE, а затем запросит каждый сокет через gotockopt, чтобы подтвердить, какой из них отключен.
  • Как UDP обеспечивает стабильность передаваемых данных Связь UDP быстрее, чем связь TCP, поскольку не требует соединения, но она ненадежна, поскольку не гарантирует безопасную доставку данных. Чтобы добиться надежной связи, необходимо на уровне приложения установить механизм проверки точности данных и установить соглашение о повторной передаче ошибок.

Подвести итог

  • Реализация сокета очень хороша: он инкапсулирует сложную сетевую связь в простой интерфейс сокета. Пользователям не нужно слишком много думать о TCP, UDP и других сетевых концепциях более низкого уровня, и они могут быстро реализовать набор процессов сетевой связи.
  • В этой статье перечислены только процедуры сокетов, используемые в семействе адресов inet, которые также можно использовать для межпроцессного взаимодействия в домене UNIX.
  • Сетевое программирование очень интересно. Оно может осуществлять связь между миром и морем и соединять людей и вещи или вещи и вещи на больших расстояниях.

наконец

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

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose