C - Serve many clients with each server thread
I'm new to C programming. I looking for C open开发者_运维问答 source code implementation that can serve many clients with each server thread, and use asynchronous I/O. I have implemented simple server and client in C but this is too advanced for me. Can you point me where I can find ready implementation of above requirements? If someone already has a written code please share it with us.
This answer assumes a Unix-like system, such as Linux, Mac OS X, or BSD.
First of all, you don't need threads to do asynchronous I/O in C. The select
system call can be used to wait for activity on one or more file descriptors.
Threads are Evil (at least in C). They cause everything to be shared by default, which violates the principle of least privilege. On the other hand, threads keep you from having to "turn your code inside-out". I recommend not using threads in C, but the choice is yours. The example below does not use threads.
If you're writing a TCP server, a good place to start is man 7 tcp
. It tells you what arguments to give to the socket
function, as well as what steps you need to take to start listening for connections.
The code that follows is a "parrot server", a program that accepts connections from clients and echos what they send. You can connect to it by running telnet localhost 1337
in the command line. I hope it will help you get started:
#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)");
}
}
}
}
The most important thing is to learn through play. Don't worry about all the little nuances up front (e.g. what will happen if send
blocks or produces SIGPIPE
). Get something working, and play around with it. When you encounter a problem, then go back to the manual to see how to deal with it. If something you read in the manual solves a problem you are actually observing, you will surely remember it.
On the other hand, do worry about checking the return value of every system call you make. If you don't, your program will start acting weird, and you won't know why. Even if all you do is print an error message, at least you'll know that a system call failed.
精彩评论