Этот ответ предполагает Unix-подобную систему, такую как Linux, Mac OS X или BSD.
Прежде всего, вам не нужны потоки для выполнения асинхронного ввода-вывода в C. Системный вызов select
может использоваться для ожидания активности одного или нескольких файловых дескрипторов.
Темы зла (по крайней мере, в C). Они заставляют все разделяться по умолчанию, что нарушает принцип наименьших привилегий. С другой стороны, потоки избавляют вас от необходимости «выворачивать код наизнанку». Я рекомендую не использовать темы в C, но выбор за вами. В приведенном ниже примере не используются потоки.
Если вы пишете TCP-сервер, лучше всего начать с man 7 tcp
. В нем сообщается, какие аргументы необходимо предоставить функции socket
, а также какие шаги необходимо предпринять, чтобы начать прослушивание соединений.
Следующий код представляет собой «сервер попугая», программу, которая принимает соединения от клиентов и повторяет то, что они отправляют. Вы можете подключиться к нему, запустив telnet localhost 1337
в командной строке. Я надеюсь, что это поможет вам начать:
#include <arpa/inet.h>
#include <err.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 1337
#define CLIENT_MAX 3
/* Utility macro: return the maximum of two numbers. */
#define max(a, b) ((a) > (b) ? (a) : (b))
/* Utility function: send a NUL-terminated string on a socket. */
static ssize_t send_string(int sockfd, const char *str, int flags)
{
return send(sockfd, str, strlen(str), flags);
}
/*
* Filter out negative values in an array of ints.
* Return the new array count.
*/
static int filter_out_negatives(int *fds, int count)
{
int i;
int new_count;
for (i = 0, new_count = 0; i < count; i++) {
if (fds[i] >= 0)
fds[new_count++] = fds[i];
}
return new_count;
}
int main(void)
{
/* Server socket */
int server;
/* Client socket array */
int clients[CLIENT_MAX];
int client_count = 0;
/* Other useful variables */
int i;
int rc;
/* See man 7 tcp. */
server = socket(AF_INET, SOCK_STREAM, 0);
if (server < 0) {
/* Simple error handling: print an error message and exit. */
err(1, "socket");
}
{
/* This structure is described in man 7 ip. */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); /* port in *network byte order*, hence the htons */
addr.sin_addr.s_addr = INADDR_ANY;
rc = bind(server, (const struct sockaddr *) &addr, sizeof(addr));
if (rc < 0)
err(1, "bind");
}
rc = listen(server, 20);
if (rc < 0)
err(1, "listen");
for (;;) {
int nfds;
fd_set readfds;
FD_ZERO(&readfds);
/*
* Listen for activity on the server socket. It will be readable
* when a client attempts to connect.
*
* The nfds argument to select is pesky. It needs to be the
* highest-numbered file descriptor we supply, plus one.
*/
FD_SET(server, &readfds);
nfds = server + 1;
/* Listen for activity on any client sockets. */
for (i = 0; i < client_count; i++) {
FD_SET(clients[i], &readfds);
nfds = max(nfds, clients[i] + 1);
}
/* Wait for activity from one or more of the sockets specified above. */
rc = select(nfds, &readfds, NULL, NULL, NULL);
if (rc < 0) {
warn("select");
continue;
}
/* Check for activity on client sockets. */
for (i = 0; i < client_count; i++) {
if (FD_ISSET(clients[i], &readfds)) {
/*
* The parrot only has so much breath. If the client sends us
* a long message, it's not a big deal the parrot has to squawk
* again.
*/
char buffer[100];
ssize_t readlen;
readlen = recv(clients[i], buffer, sizeof(buffer), 0);
if (readlen < 0) {
warn("recv");
} else if (readlen == 0) {
/* Client closed the connection. */
if (close(clients[i]) < 0)
err(1, "close (1)");
/*
* Set client socket to -1. We'll remove it
* at the end of this loop.
*/
clients[i] = -1;
} else {
if (send_string(clients[i], "Squawk! ", 0) < 0)
warn("send (2)");
if (send(clients[i], buffer, readlen, 0) < 0)
warn("send (3)");
}
}
}
/* Filter out closed clients. */
client_count = filter_out_negatives(clients, client_count);
/*
* If there is activity on the server socket, it means someone
* is trying to connect to us.
*/
if (FD_ISSET(server, &readfds)) {
int client;
client = accept(server, NULL, NULL);
if (client < 0)
err(1, "accept");
if (client_count < CLIENT_MAX) {
clients[client_count++] = client;
if (send_string(client, "Squawk! Welcome to the Parrot Server!\n", 0) < 0)
err(1, "send (4)");
} else {
if (send_string(client, "Squawk! I'm busy, can you come back later?\n", 0) < 0)
err(1, "send (5)");
if (close(client) < 0)
err(1, "close (2)");
}
}
}
}
Самое важное - учиться в игре. Не беспокойтесь обо всех маленьких нюансах (например, что произойдет, если send
блокирует или выдает SIGPIPE
). Получите что-нибудь работающее, и поиграйте с этим. Если вы столкнулись с проблемой, , а затем вернитесь к руководству, чтобы узнать, как с ним справиться. Если что-то, что вы прочитали в руководстве, решит проблему, которую вы на самом деле наблюдаете, вы наверняка запомните это.
С другой стороны, не забудьте проверить возвращаемое значение каждого системного вызова, который вы делаете. Если вы этого не сделаете, ваша программа начнет вести себя странно, и вы не будете знать, почему. Даже если все, что вы делаете, это печатаете сообщение об ошибке, по крайней мере вы будете знать, что системный вызов не удался.