Как открыть и использовать сокет в C?

Я хотел бы знать самый простой и эффективный способ открытия и записи данных в сокет на языке программирования C для сетевого программирования.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
17
0
49 437
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Чтение и запись из базовых сокетов не сложнее, чем чтение и запись обычных файлов (просто используйте recv вместо чтения и вместо этого отправляйте при записи). Когда вам нужно открыть розетку, все становится немного сложнее. Причина в том, что существует множество различных способов связи с использованием сокетов (TCP, UDP и т. д.).

Ответ принят как подходящий

Вы правы, использование сокетов в C имеет сложный синтаксис. Более поздние языки, такие как Java и Python, делают это проще простого. Лучший учебник, который я нашел для программирования сокетов на C, - Руководство Beej по сетевому программированию. Я рекомендую вам начать с самого начала, чтобы получить хороший обзор, но если вам просто нужно заставить код, работающий Теперь, вы можете перейти к разделу под названием Фон клиент-сервер.

Удачи!

Это глупо. Сокеты - это определенный как C API, и «более поздние языки» должны выполнять все эти вызовы C на каком-то уровне. В C есть «более поздние библиотеки», которые также легко справятся с этим, вплоть до выполнения, скажем, HTTP-запроса вместо того, чтобы возиться с сокетами. У вас может легко быть клиентская функция, которая будет принимать IP-адрес в нотации хоста или точки как строку char *, номер порта как int и возвращать вам поток FILE *, обозначающий подключенную схему, или нулевой указатель с errno, установленным на что-то полезный.

Kaz 19.12.2013 03:54

Вы не упоминаете, на какой платформе находитесь, но копия Сетевое программирование Unix Стивенса была бы хорошим дополнением к вашей книжной полке. Большинство операционных систем реализуют сокеты Berkley с использованием socket, bind, connect и т. д.

Я пытался вспомнить название этой книги для своего собственного ответа, но в настоящее время она хранится. Это библия по программированию сокетов.

paxdiablo 21.11.2008 06:44

Да, это отличная книга. Я держу его под рукой на работе.

Bill the Lizard 21.11.2008 07:13

Все книги Стивенса великолепны, но UNP - лучшая. Оба тома.

qrdl 21.11.2008 10:44

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

Например, если вы просто хотите получить файл с помощью HTTP, используйте Неон или libcurl. Это будет проще, будет на более высоком уровне, и у вас будет бесплатный SSL, IPv6 и т. д.

Несколько голосов против и ни одного комментария, чтобы объяснить почему. Это многое говорит о техническом уровне многих пользователей SO ...

bortzmeyer 02.01.2009 01:22

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

David Sykes 07.07.2010 18:34

Если люди голосуют против, потому что они не согласны, они могут сказать об этом в комментариях. Ответ с рекомендацией (вы спросили, как сделать X, но я предлагаю вам вместо этого сделать Y) вполне приемлем, когда требования не ясны (если OP сказал, что «мой менеджер [или мой учитель] требует использования сокетов», все было бы иначе)

bortzmeyer 03.04.2013 19:05

Обычно я пишу на C++, но вы можете найти применение в техническом документе, который я написал «Как избежать десяти основных ошибок программирования сокетов» - игнорируйте совет по использованию инструментария ACE (поскольку он требует C++), но обратите внимание на сокет. ошибки в статье - их легко сделать и трудно найти, особенно новичку. http://www.riverace.com/sockets10.htm

Пример минимального исполняемого клиент-серверного TCP POSIX 7

Подключите два компьютера к локальной сети, например ваша домашняя сеть Wi-Fi.

Запускаем сервер на одном компьютере с:

./server.out

Получите IP-адрес серверного компьютера с ifconfig, например 192.168.0.10.

На другом компьютере запустите:

./client.out 192.168.0.10

Теперь введите строки на клиенте, и сервер вернет их с увеличением на 1 (шифр ROT-1).

server.c

#define _XOPEN_SOURCE 700

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char buffer[BUFSIZ];
    char protoname[] = "tcp";
    struct protoent *protoent;
    int enable = 1;
    int i;
    int newline_found = 0;
    int server_sockfd, client_sockfd;
    socklen_t client_len;
    ssize_t nbytes_read;
    struct sockaddr_in client_address, server_address;
    unsigned short server_port = 12345u;

    if (argc > 1) {
        server_port = strtol(argv[1], NULL, 10);
    }

    protoent = getprotobyname(protoname);
    if (protoent == NULL) {
        perror("getprotobyname");
        exit(EXIT_FAILURE);
    }

    server_sockfd = socket(
        AF_INET,
        SOCK_STREAM,
        protoent->p_proto
        /* 0 */
    );
    if (server_sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        exit(EXIT_FAILURE);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(server_port);
    if (bind(
            server_sockfd,
            (struct sockaddr*)&server_address,
            sizeof(server_address)
        ) == -1
    ) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(server_sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "listening on port %d\n", server_port);

    while (1) {
        client_len = sizeof(client_address);
        client_sockfd = accept(
            server_sockfd,
            (struct sockaddr*)&client_address,
            &client_len
        );
        while ((nbytes_read = read(client_sockfd, buffer, BUFSIZ)) > 0) {
            printf("received:\n");
            write(STDOUT_FILENO, buffer, nbytes_read);
            if (buffer[nbytes_read - 1] == '\n')
                newline_found;
            for (i = 0; i < nbytes_read - 1; i++)
                buffer[i]++;
            write(client_sockfd, buffer, nbytes_read);
            if (newline_found)
                break;
        }
        close(client_sockfd);
    }
    return EXIT_SUCCESS;
}

client.c

#define _XOPEN_SOURCE 700

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char buffer[BUFSIZ];
    char protoname[] = "tcp";
    struct protoent *protoent;
    char *server_hostname = "127.0.0.1";
    char *user_input = NULL;
    in_addr_t in_addr;
    in_addr_t server_addr;
    int sockfd;
    size_t getline_buffer = 0;
    ssize_t nbytes_read, i, user_input_len;
    struct hostent *hostent;
    /* This is the struct used by INet addresses. */
    struct sockaddr_in sockaddr_in;
    unsigned short server_port = 12345;

    if (argc > 1) {
        server_hostname = argv[1];
        if (argc > 2) {
            server_port = strtol(argv[2], NULL, 10);
        }
    }

    /* Get socket. */
    protoent = getprotobyname(protoname);
    if (protoent == NULL) {
        perror("getprotobyname");
        exit(EXIT_FAILURE);
    }
    sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* Prepare sockaddr_in. */
    hostent = gethostbyname(server_hostname);
    if (hostent == NULL) {
        fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
        exit(EXIT_FAILURE);
    }
    in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
    if (in_addr == (in_addr_t)-1) {
        fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_addr.s_addr = in_addr;
    sockaddr_in.sin_family = AF_INET;
    sockaddr_in.sin_port = htons(server_port);

    /* Do the actual connection. */
    if (connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
        perror("connect");
        return EXIT_FAILURE;
    }
    while (1) {
        fprintf(stderr, "enter string (empty to quit):\n");
        user_input_len = getline(&user_input, &getline_buffer, stdin);
        if (user_input_len == -1) {
            perror("getline");
            exit(EXIT_FAILURE);
        }
        if (user_input_len == 1) {
            close(sockfd);
            break;
        }
        if (write(sockfd, user_input, user_input_len) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }
        while ((nbytes_read = read(sockfd, buffer, BUFSIZ)) > 0) {
            write(STDOUT_FILENO, buffer, nbytes_read);
            if (buffer[nbytes_read - 1] == '\n') {
                fflush(stdout);
                break;
            }
        }
    }
    free(user_input);

    exit(EXIT_SUCCESS);
}

На GitHub с Makefile. Проверено на Ubuntu 15.10.

Длина сообщения

read вызывает как клиент, так и сервер внутри цикла while.

Как и при чтении из файлов, ОС может произвольно разбивать сообщения, чтобы ускорить работу, например один пакет может прийти намного раньше другого.

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

  • заголовок с индикатором длины (например, HTTP Content-Length)
  • уникальная строка, завершающая сообщения. Здесь мы используем \n.
  • сервер закрывает соединение: HTTP разрешает это https://stackoverflow.com/a/25586633/895245. Ограничено, конечно, так как следующее сообщение требует переподключения.

Следующие шаги

Этот пример ограничен, потому что:

  • сервер может обрабатывать только одно клиентское соединение за раз
  • общение синхронизируется просто. Например: в приложении чата P2P сервер (другой человек) может отправлять сообщения в любое время.

Решение этих проблем требует многопоточности и, возможно, других вызовов, таких как poll.

Другие вопросы по теме