我使用
编译我的程序all:
gcc server.c -o server
gcc file_reader.c -o file_reader
编译完成后,我在终端输入“./server [port_num]”。
初始化服务器后,我可以在浏览器上输入一些内容:“http://127.0.0.1:[port_num]/cgi_program?filename=[filename]”
然后我的CGI(file_reader)会正确地将名为“filename”的文件内容转储到我输入的浏览器中。
我在这里发布了我的所有代码,抱歉很长,因为有很多函数可以调用,你可以跳过并假设以下函数是正确的。
问题仍然存在:错误消息“select:Bad file descriptor”,只能从一个人那里读取。
#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;
}
这是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;
}
答案 0 :(得分:1)
您正在同一个requestP
插槽中存储多个连接的客户端,因为您在调用accept()
后使用侦听套接字描述符作为数组索引。如果一次只连接一个客户端,那么这将起作用,否则当一次连接多个客户端时,您将丢弃该插槽。
但是,您的读/写代码使用客户端套接字描述符作为数组索引。由于客户端套接字描述符的值与侦听套接字描述符的值不同,因此在准备新的requestP[i]
客户端时,需要将requestP[conn_fd]
更改为accept()
。
这也假设requestP
是一个固定长度的数组,并且当用作索引时,各种套接字描述符永远不会超过数组的边界。
每当从maxfd
列表中删除套接字描述符时,您也不会重新计算master
。这也可能导致您的错误。
您也没有处理write()
在非阻塞套接字上返回阻塞错误的情况。在这种情况下,您需要使用write_fds
的{{1}}部分来检测套接字何时可以接受更多数据,然后再次调用select()
。
更好的解决方案是将write()
更改为动态数组或链接列表,并完全停止使用套接字描述符作为数组索引,例如:
requestP
答案 1 :(得分:0)
关闭readFDS
或writeFDs
中的FD后,您需要将其从那里删除。