我是C编程的新手。我正在寻找可以为每个服务器线程服务许多客户端的C开源代码实现,并使用异步I / O.我在C中实现了简单的服务器和客户端,但这对我来说太先进了。 你能指出我能在哪里找到上述要求的准备实施吗?如果有人已经有书面代码,请与我们分享。
答案 0 :(得分:3)
这个答案假定类似Unix的系统,例如Linux,Mac OS X或BSD。
首先,您不需要线程在C中执行异步I / O. select
系统调用可用于等待一个或多个文件描述符上的活动。
Threads are Evil(至少在C中)。它们会导致默认共享所有内容,这违反了最小权限原则。另一方面,线程使您不必“将代码从里到外”。我建议不要在C中使用线程,但选择权在你手中。以下示例不使用线程。
如果你正在编写TCP服务器,那么一个好的起点是man 7 tcp
。它告诉您给socket
函数的参数,以及开始监听连接需要采取的步骤。
下面的代码是一个“parrot服务器”,一个接受来自客户端和回声的程序的程序。您可以通过在命令行中运行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
会发生什么)。得到一些有用的东西,并玩弄它。当您遇到问题时,然后返回手册以了解如何处理它。如果您在手册中阅读的内容解决了您实际观察到的问题,那么您一定会记住它。
另一方面,请务必检查您所进行的每个系统调用的返回值。如果你不这样做,你的程序将开始表现得很奇怪,你不会知道为什么。即使您所做的只是打印错误消息,至少您会知道系统调用失败。