Reputation: 311
I compile my program using
all:
gcc server.c -o server
gcc file_reader.c -o file_reader
After compiling, I input "./server [port_num]" in the terminal.
After initializing the server, I can type something on the browser like this: "http://127.0.0.1:[port_num]/cgi_program?filename=[filename]"
Then my CGI(file_reader) will correctly dump the content of the file named "filename" to the browser I type.
I post all of my code here, sorry it's very long since there is a lot of functions to call, you can skip and assume the followings functions are right.
The problem remains the same: an error message "select: Bad file descriptor", and can only read from one guy.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define TIMEOUT_SEC 5 // timeout in seconds for wait for a connection
#define MAXBUFSIZE 1024 // timeout in seconds for wait for a connection
#define NO_USE 0 // status of a http request
#define ERROR -1
#define READING 1
#define WRITING 2
#define ERR_EXIT(a) { perror(a); exit(1); }
typedef struct {
char hostname[512]; // hostname
unsigned short port; // port to listen
int listen_fd; // fd to wait for a new connection
} http_server;
typedef struct {
int conn_fd; // fd to talk with client
int status; // not used, error, reading (from client), writing (to client)
char file[MAXBUFSIZE]; // requested file
char query[MAXBUFSIZE]; // requested query
char host[MAXBUFSIZE]; // client host
char* buf; // data sent by/to client
size_t buf_len; // bytes used by buf
size_t buf_size; // bytes allocated for buf
size_t buf_idx; // offset for reading and writing
} http_request;
static char* logfilenameP; // log file name
static void init_http_server(http_server *svrP, unsigned short port); // initailize a http_request instance, exit for error
static void init_request(http_request* reqP); // initailize a http_request instance
static void free_request(http_request* reqP); // free resources used by a http_request instance
static int read_header_and_file(http_request* reqP, int *errP);
// return 0: success, file is buffered in retP->buf with retP->buf_len bytes
// return -1: error, check error code (*errP)
// return 1: continue to it until return -1 or 0
// error code:
// 1: client connection error
// 2: bad request, cannot parse request
// 3: method not implemented
// 4: illegal filename
// 5: illegal query
// 6: file not found
// 7: file is protected
static void set_ndelay(int fd);
// Set NDELAY mode on a socket.
int main(int argc, char **argv) {
http_server server; // http server
http_request* requestP = NULL; // pointer to http requests from client
int maxfd; // size of open file descriptor table
struct sockaddr_in cliaddr; // used by accept()
int clilen;
int conn_fd; // fd for a new connection with client
int err; // used by read_header_and_file()
int i, ret, nwritten;
// Initialize http server
init_http_server(&server, (unsigned short) atoi(argv[1]));
maxfd = getdtablesize();
requestP = (http_request*) malloc(sizeof(http_request) * maxfd);
if (requestP == (http_request*) 0) {
fprintf(stderr, "out of memory allocating all http requests\n");
exit(1);
}
for (i = 0; i < maxfd; i ++)
init_request(&requestP[i]);
requestP[server.listen_fd].conn_fd = server.listen_fd;
requestP[server.listen_fd].status = READING;
fprintf(stderr, "\nstarting on %.80s, port %d, fd %d, maxconn %d, logfile %s...\n", server.hostname, server.port, server.listen_fd, maxfd, logfilenameP);
fd_set master; /* master file descriptor list */
fd_set read_fds; /* temp file descriptor list for select() */
FD_SET(server.listen_fd, &master);
int fdmax = server.listen_fd;
while (1) { /* Main loop */
read_fds = master;
if (select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1)
ERR_EXIT("select")
printf("server select() is OK!\n");
for (i = 0; i < fdmax + 1; i++) {
if (FD_ISSET(i, &read_fds)) {
if (i == server.listen_fd) {
clilen = sizeof(cliaddr);
conn_fd = accept(server.listen_fd, (struct sockaddr *) &cliaddr, (socklen_t *) &clilen);
if (conn_fd < 0) {
if (errno == EINTR || errno == EAGAIN) continue; // try again
if (errno == ENFILE) {
(void) fprintf(stderr, "out of file descriptor table ... (maxconn %d)\n", maxfd);
continue;
}
ERR_EXIT("accept")
}
requestP[conn_fd].conn_fd = conn_fd;
requestP[conn_fd].status = READING;
strcpy(requestP[conn_fd].host, inet_ntoa(cliaddr.sin_addr));
set_ndelay(conn_fd);
FD_SET(conn_fd, &master);
if (conn_fd > fdmax)
fdmax = conn_fd;
fprintf(stderr, "getting a new request... fd %d from %s\n", conn_fd, requestP[conn_fd].host);
}
else { /* Handle data from a client */
ret = read_header_and_file(&requestP[i], &err);
if (ret == 1) continue;
else if (ret < 0) {
fprintf(stderr, "error on fd %d, code %d\n", requestP[i].conn_fd, err);
requestP[i].status = ERROR;
close(requestP[i].conn_fd);
free_request(&requestP[i]);
break;
}
else if (ret == 0) {
fprintf(stderr, "writing (buf %s, idx %d) %d bytes to request fd %d\n", requestP[conn_fd].buf, (int) requestP[conn_fd].buf_idx, (int) requestP[conn_fd].buf_len, requestP[conn_fd].conn_fd);
nwritten = write(requestP[conn_fd].conn_fd, requestP[conn_fd].buf, requestP[conn_fd].buf_len);
fprintf(stderr, "complete writing %d bytes on fd %d\n", nwritten, requestP[conn_fd].conn_fd);
fprintf(stderr, "=============================================\n");
// char *m = strchr(requestP[conn_fd].query, '=') + 1;
// char *filename = strncpy(requestP[conn_fd].query, m, sizeof(requestP[conn_fd].query));
int fd[2];
if (pipe(fd) == -1)
ERR_EXIT("pipe")
pid_t pid;
if ((pid = fork()) < 0) {
ERR_EXIT("fork")
}
else if (pid == 0) { /* In Child Process */
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execl("file_reader", "./file_reader", requestP[i].query, (char *)0);
fprintf(stderr, "Error: Unexpect flow of control.\n");
exit(EXIT_FAILURE);
}
else { /* In Parent Process */
close(fd[1]);
char recv[1024];
read(fd[0], recv, sizeof(recv));
printf("The file content is:\n%s\n", recv);
}
close(conn_fd); // I forgot to close the listen conn_fd!
free_request(&requestP[i]);
FD_CLR(conn_fd, &master); // I forgot to FD_CLR the conn_fd!
}
}
}
}
}
free(requestP);
return 0;
}
//=========================
//The following are some APIs
#include <time.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/mman.h>
static void add_to_buf(http_request *reqP, char* str, size_t len);
static void strdecode(char* to, char* from);
static int hexit(char c);
static char* get_request_line(http_request *reqP);
static void* e_malloc(size_t size);
static void* e_realloc(void* optr, size_t size);
static void init_request(http_request* reqP) {
reqP->conn_fd = -1;
reqP->status = 0; // not used
reqP->file[0] = (char) 0;
reqP->query[0] = (char) 0;
reqP->host[0] = (char) 0;
reqP->buf = NULL;
reqP->buf_size = 0;
reqP->buf_len = 0;
reqP->buf_idx = 0;
}
static void free_request(http_request* reqP) {
if (reqP->buf != NULL) {
free(reqP->buf);
reqP->buf = NULL;
}
init_request(reqP);
}
#define ERR_RET(error) { *errP = error; return -1; }
// return 0: success, file is buffered in retP->buf with retP->buf_len bytes
// return -1: error, check error code (*errP)
// return 1: read more, continue until return -1 or 0
// error code:
// 1: client connection error
// 2: bad request, cannot parse request
// 3: method not implemented
// 4: illegal filename
// 5: illegal query
// 6: file not found
// 7: file is protected
//
static int read_header_and_file(http_request* reqP, int *errP) {
// Request variables
char* file = (char *) 0;
char* path = (char *) 0;
char* query = (char *) 0;
char* protocol = (char *) 0;
char* method_str = (char *) 0;
int r, fd;
struct stat sb;
char timebuf[100];
int buflen;
char buf[10000];
time_t now;
void *ptr;
// Read in request from client
while (1) {
r = read(reqP->conn_fd, buf, sizeof(buf));
if (r < 0 && (errno == EINTR || errno == EAGAIN)) return 1;
if (r <= 0) ERR_RET(1)
add_to_buf(reqP, buf, r);
if (strstr(reqP->buf, "\015\012\015\012") != (char*) 0 ||
strstr(reqP->buf, "\012\012") != (char*) 0) break;
}
fprintf(stderr, "=============================================\n");
fprintf(stderr, "header: %s", reqP->buf);
fprintf(stderr, "=============================================\n");
// Parse the first line of the request.
method_str = get_request_line(reqP);
if (method_str == (char*) 0) ERR_RET(2)
path = strpbrk(method_str, " \t\012\015");
if (path == (char*) 0) ERR_RET(2)
*path++ = '\0';
path += strspn(path, " \t\012\015");
protocol = strpbrk(path, " \t\012\015");
if (protocol == (char*) 0) ERR_RET(2)
*protocol++ = '\0';
protocol += strspn(protocol, " \t\012\015");
query = strchr(path, '?');
if (query == (char*) 0)
query = "";
else
*query++ = '\0';
if (strcasecmp(method_str, "GET") != 0) ERR_RET(3)
else {
strdecode(path, path);
if (path[0] != '/') ERR_RET(4)
else file = &(path[1]);
}
if (strlen(file) >= MAXBUFSIZE-1) ERR_RET(4)
if (strlen(query) >= MAXBUFSIZE-1) ERR_RET(5)
strcpy(reqP->file, file);
strcpy(reqP->query, query);
char *m = strchr(reqP->query, '=') + 1;
char *filename = strncpy(reqP->query, m, sizeof(reqP->query));
fprintf(stderr, "filename = %s\n", filename);
fprintf(stderr, "reqP.conn_fd = %d\n", reqP->conn_fd);
fprintf(stderr, "reqP.status = %d\n", reqP->status);
fprintf(stderr, "reqP.file = %s\n", reqP->file);
fprintf(stderr, "reqP.query = %s\n", reqP->query);
fprintf(stderr, "reqP.host = %s\n", reqP->host);
fprintf(stderr, "reqP.buf = %s\n", reqP->buf);
fprintf(stderr, "reqP.buf_len = %zu\n", reqP->buf_len);
fprintf(stderr, "reqP.buf_size = %zu\n", reqP->buf_size);
fprintf(stderr, "reqP.buf_idx = %zu\n", reqP->buf_idx);
fprintf(stderr, "=============================================\n");
// if (query[0] == (char) 0) {
if (query[0] == 'f') {
fprintf(stderr, "query[0] = %c\n", query[0]);
// for file request, read it in buf
r = stat(filename, &sb);
// r = stat(reqP->file, &sb);
if (r < 0) ERR_RET(6)
fd = open(filename, O_RDONLY);
// fd = open(reqP->file, O_RDONLY);
if (fd < 0) ERR_RET(7)
reqP->buf_len = 0;
buflen = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\015\012Server: SP TOY\015\012");
add_to_buf(reqP, buf, buflen);
now = time((time_t*) 0);
(void) strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf);
add_to_buf(reqP, buf, buflen);
buflen = snprintf(
buf, sizeof(buf), "Content-Length: %lld\015\012", (int64_t) sb.st_size);
add_to_buf(reqP, buf, buflen);
buflen = snprintf(buf, sizeof(buf), "Connection: close\015\012\015\012");
add_to_buf(reqP, buf, buflen);
ptr = mmap(0, (size_t) sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ptr == (void*) -1) ERR_RET(8)
add_to_buf(reqP, ptr, sb.st_size);
(void) munmap(ptr, sb.st_size);
close(fd);
// printf("%s\n", reqP->buf);
// fflush(stdout);
reqP->buf_idx = 0; // writing from offset 0
return 0;
}
return 0;
}
static void add_to_buf(http_request *reqP, char* str, size_t len) {
char** bufP = &(reqP->buf);
size_t* bufsizeP = &(reqP->buf_size);
size_t* buflenP = &(reqP->buf_len);
if (*bufsizeP == 0) {
*bufsizeP = len + 500;
*buflenP = 0;
*bufP = (char*) e_malloc(*bufsizeP);
} else if (*buflenP + len >= *bufsizeP) {
*bufsizeP = *buflenP + len + 500;
*bufP = (char*) e_realloc((void*) *bufP, *bufsizeP);
}
(void) memmove(&((*bufP)[*buflenP]), str, len);
*buflenP += len;
(*bufP)[*buflenP] = '\0';
}
static char* get_request_line(http_request *reqP) {
int begin;
char c;
char *bufP = reqP->buf;
int buf_len = reqP->buf_len;
for (begin = reqP->buf_idx ; reqP->buf_idx < buf_len; ++reqP->buf_idx) {
c = bufP[reqP->buf_idx];
if (c == '\012' || c == '\015') {
bufP[reqP->buf_idx] = '\0';
++reqP->buf_idx;
if (c == '\015' && reqP->buf_idx < buf_len &&
bufP[reqP->buf_idx] == '\012') {
bufP[reqP->buf_idx] = '\0';
++reqP->buf_idx;
}
fprintf(stderr, "bufP = %s\n", bufP);
fprintf(stderr, "=============================================\n");
return &(bufP[begin]);
}
}
fprintf(stderr, "http request format error\n");
exit(1);
}
static void init_http_server(http_server *svrP, unsigned short port) {
struct sockaddr_in servaddr;
int tmp;
gethostname(svrP->hostname, sizeof(svrP->hostname));
svrP->port = port;
svrP->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (svrP->listen_fd < 0) ERR_EXIT("socket")
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
tmp = 1;
if (setsockopt(svrP->listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &tmp, sizeof(tmp)) < 0) ERR_EXIT ("setsockopt ")
if (bind(svrP->listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind")
if (listen(svrP->listen_fd, 1024) < 0) ERR_EXIT("listen")
}
// Set NDELAY mode on a socket.
static void set_ndelay(int fd) {
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if (flags != -1) {
newflags = flags | (int) O_NDELAY; // nonblocking mode
if (newflags != flags)
(void) fcntl(fd, F_SETFL, newflags);
}
}
static void strdecode(char* to, char* from) {
for (; *from != '\0'; ++to, ++from) {
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1]) * 16 + hexit(from[2]);
from += 2;
} else {
*to = *from;
}
}
*to = '\0';
}
static int hexit(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0; // shouldn't happen
}
static void* e_malloc(size_t size) {
void* ptr;
ptr = malloc(size);
if (ptr == (void*) 0) {
(void) fprintf(stderr, "out of memory\n");
exit(1);
}
return ptr;
}
static void* e_realloc(void* optr, size_t size) {
void* ptr;
ptr = realloc(optr, size);
if (ptr == (void*) 0) {
(void) fprintf(stderr, "out of memory\n");
exit(1);
}
return ptr;
}
And here is the file_reader.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define ERR_EXIT(a) { perror(a); exit(1); }
int main(int argc, char **argv) {
FILE *fp = fopen(argv[1], "r");
char c;
char buf[1024];
int i = 0;
fprintf(stderr, "filename = %s\n", argv[1]);
if (fp == NULL)
ERR_EXIT("ERROR open!\n")
while ((c = fgetc(fp)) != EOF) {
buf[i] = c;
i++;
}
buf[i] = '\0';
write(STDOUT_FILENO, buf, sizeof(buf));
fclose(fp);
return 0;
}
Upvotes: 1
Views: 8961
Reputation: 595320
You are storing multiple connected clients in the same requestP
slot, because you are using the listening socket descriptor as the array index after calling accept()
. That will work if there is only ever 1 client connected at a time, otherwise you will be trashing that slot when multiple clients are connected at a time.
However, your reading/writing code is using the client socket descriptor as the array index. Since the client socket descriptors are different values than the listening socket descriptor, you need to change requestP[i]
to requestP[conn_fd]
when preparing a newly accept()
'ed client.
This also assumes that requestP
is a fixed-length array and the various socket descriptors never exceed the bounds of the array when used as indexes.
You are also not re-calculating maxfd
whenever you remove a socket descriptor from the master
list. That could also be contributing to your error.
You are also not handling the case where write()
returns a blocking error on a non-blocking socket. In that case, you need to use the write_fds
part of select()
to detect when the socket can accept more data before calling write()
again.
A better solution would be to change requestP
into a dynamic array or linked list, and stop using socket descriptors as array indexes altogether, eg:
typedef struct http_request {
http_request *next;
int conn_fd; // fd to talk with client
int status; // not used, error, reading (from client), writing (to client)
char file[MAXBUFSIZE]; // requested file
char query[MAXBUFSIZE]; // requested query
char host[MAXBUFSIZE]; // client host
char* buf; // data sent by/to client
size_t buf_len; // bytes used by buf
size_t buf_size; // bytes allocated for buf
size_t buf_idx; // offset for reading and writing
} http_request;
http_request *requests_head = NULL;
http_request *requests_tail = NULL;
while (1) { /* Main loop */
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_SET(server.listen_fd, &read_fds);
int maxfd = server.listen_fd;
http_request *request = requests_head;
while (request) {
FD_SET(request->conn_fd, &read_fds);
if (request->status == WRITING} {
FD_SET(request->conn_fd, &write_fds);
}
maxfd = max(request->conn_fd, maxfd);
request = request->next;
}
if (select(maxfd+1, &read_fds, &write_fds, NULL, NULL) < 0)
ERR_EXIT("select")
printf("server select() is OK!\n");
if (FD_ISSET(server.listen_fd, &read_fds)) {
clilen = sizeof(cliaddr);
conn_fd = accept(server.listen_fd, (struct sockaddr *) &cliaddr, (socklen_t *) &clilen);
if (conn_fd < 0) {
if ((errno != EINTR) && (errno != EAGAIN)) { // try again later
if (errno != ENFILE) {
ERR_EXIT("accept")
}
fprintf(stderr, "out of file descriptor table ... (maxconn %d)\n", maxfd);
}
}
else
{
request = (http_request*) malloc(sizeof(http_request));
if (!request) {
// error handling ...
close(conn_fd);
}
else
{
memset(request, 0, sizeof(http_request));
request->conn_fd = conn_fd;
request->status = READING;
strcpy(request->host, inet_ntoa(cliaddr.sin_addr));
set_ndelay(conn_fd);
fprintf(stderr, "getting a new request... fd %d from %s\n", conn_fd, request->host);
if (!requests_head) requests_head = request;
if (requests_tail) requests_tail->next = request;
requests_tail = request;
}
}
}
request = requests_head;
http_request *next, *prev = NULL;
while (request) {
if (FD_ISSET(request->conn_fd, &read_fds)) {
// Handle data from a client
if (request->state != READING) {
char recv[1024];
if (read(request->conn_fd, recv, sizeof(recv)) <= 0) {
fprintf(stderr, "error on fd %d, code %d\n", request->conn_fd, err);
close(request->conn_fd);
free_request(request);
next = request->next;
if (request == requests_head) requests_head = next;
if (request == requests_tail) requests_tail = prev;
if (prev) prev->next = next;
free(request);
request = next;
continue;
}
}
else
{
fprintf(stderr, "reading from conn_fd %d\n", request->conn_fd);
ret = read_header_and_file(request, &err);
if (ret < 0) {
fprintf(stderr, "error on fd %d, code %d\n", request->conn_fd, err);
close(request->conn_fd);
free_request(request);
next = request->next;
if (request == requests_head) requests_head = next;
if (request == requests_tail) requests_tail = prev;
if (prev) prev->next = next;
free(request);
request = next;
continue;
}
if (ret == 0) {
request->status = WRITING;
FD_SET(request->conn_fd, &write_fds);
}
}
}
if (FD_ISSET(request->conn_fd, &write_fds)) {
// Handle data to a client
if (request->buf_idx < request->buf_len) {
fprintf(stderr, "writing (buf %s, idx %d) %d bytes to request fd %d\n", request->buf, (int) request->buf_idx, (int) request->buf_len, request->conn_fd);
nwritten = write(request->conn_fd, &request->buf[request->buf_idx], request->buf_len - request->buf_idx);
if (nwritten < 0) {
if ((errno != EINTR) && (errno != EAGAIN)) {
fprintf(stderr, "error on fd %d, code %d\n", request->conn_fd, errno);
close(request->conn_fd);
free_request(request);
next = request->next;
if (request == requests_head) requests_head = next;
if (request == requests_tail) requests_tail = prev;
if (prev) prev->next = next;
free(request);
request = next;
continue;
}
}
else
{
request->buf_idx += nwritten;
}
}
if (request->buf_idx == request->buf_len) {
fprintf(stderr, "complete writing %d bytes on fd %d\n", (int) request->buf_len, request->conn_fd);
fprintf(stderr, "=============================================\n");
int fd[2];
if (pipe(fd) == -1)
ERR_EXIT("pipe")
pid_t pid;
if ((pid = fork()) < 0)
ERR_EXIT("fork")
else if (pid == 0) { /* In Child Process */
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execl("file_reader", "./file_reader", request->query, (char *)0);
fprintf(stderr, "Error: Unexpect flow of control.\n");
exit(EXIT_FAILURE);
}
else { /* In Parent Process */
close(fd[1]);
char recv[1024];
read(fd[0], recv, sizeof(recv));
printf("The file content is:\n%s\n", recv);
}
close(request->conn_fd);
free_request(request);
next = request->next;
if (request == requests_head) requests_head = next;
if (request == requests_tail) requests_tail = prev;
if (prev) prev->next = next;
free(request);
request = next;
continue;
}
}
request = request->next;
}
}
Upvotes: 2
Reputation: 310840
After you close an FD that is in the readFDS
or writeFDs
you need to remove it from there.
Upvotes: 0