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





Чтение и запись из базовых сокетов не сложнее, чем чтение и запись обычных файлов (просто используйте recv вместо чтения и вместо этого отправляйте при записи). Когда вам нужно открыть розетку, все становится немного сложнее. Причина в том, что существует множество различных способов связи с использованием сокетов (TCP, UDP и т. д.).
Вы правы, использование сокетов в C имеет сложный синтаксис. Более поздние языки, такие как Java и Python, делают это проще простого. Лучший учебник, который я нашел для программирования сокетов на C, - Руководство Beej по сетевому программированию. Я рекомендую вам начать с самого начала, чтобы получить хороший обзор, но если вам просто нужно заставить код, работающий Теперь, вы можете перейти к разделу под названием Фон клиент-сервер.
Удачи!
Вы не упоминаете, на какой платформе находитесь, но копия Сетевое программирование Unix Стивенса была бы хорошим дополнением к вашей книжной полке. Большинство операционных систем реализуют сокеты Berkley с использованием socket, bind, connect и т. д.
Я пытался вспомнить название этой книги для своего собственного ответа, но в настоящее время она хранится. Это библия по программированию сокетов.
Да, это отличная книга. Я держу его под рукой на работе.
Все книги Стивенса великолепны, но UNP - лучшая. Оба тома.
Если вы не напишете сетевой демон, большинство сетей на C может быть выполнено на более высоком уровне, чем использование непосредственно сокетов, с использованием соответствующих библиотек.
Например, если вы просто хотите получить файл с помощью HTTP, используйте Неон или libcurl. Это будет проще, будет на более высоком уровне, и у вас будет бесплатный SSL, IPv6 и т. д.
Несколько голосов против и ни одного комментария, чтобы объяснить почему. Это многое говорит о техническом уровне многих пользователей SO ...
Скорее всего, это битва между людьми, которые согласны с вами, и людьми, которые думают, что вы не отвечаете на заданный вопрос.
Если люди голосуют против, потому что они не согласны, они могут сказать об этом в комментариях. Ответ с рекомендацией (вы спросили, как сделать X, но я предлагаю вам вместо этого сделать Y) вполне приемлем, когда требования не ясны (если OP сказал, что «мой менеджер [или мой учитель] требует использования сокетов», все было бы иначе)
Обычно я пишу на 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.
Как и при чтении из файлов, ОС может произвольно разбивать сообщения, чтобы ускорить работу, например один пакет может прийти намного раньше другого.
Таким образом, протокол должен указывать соглашение о том, где останавливаются сообщения. Общие методы включают:
Content-Length)\n.Следующие шаги
Этот пример ограничен, потому что:
Решение этих проблем требует многопоточности и, возможно, других вызовов, таких как poll.
Это глупо. Сокеты - это определенный как C API, и «более поздние языки» должны выполнять все эти вызовы C на каком-то уровне. В C есть «более поздние библиотеки», которые также легко справятся с этим, вплоть до выполнения, скажем, HTTP-запроса вместо того, чтобы возиться с сокетами. У вас может легко быть клиентская функция, которая будет принимать IP-адрес в нотации хоста или точки как строку
char *, номер порта какintи возвращать вам потокFILE *, обозначающий подключенную схему, или нулевой указатель сerrno, установленным на что-то полезный.