首先创建几个函数以方便我们调用

  • open_clinetfd: 创建一个网络socket,作为客户端,并且尝试连接主机
  • open_listenfd: 创建socket,作为主机,并开始监听
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "mynet.h"

int open_clientfd(char *hostname, char *port){
	int clientfd;
	struct addrinfo hints, *listp, *p;

	memset(&hints, 0,sizeof(struct addrinfo));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_NUMERICSERV;
	hints.ai_flags |= AI_ADDRCONFIG;
	getaddrinfo(hostname, port, &hints, &listp);
	for(p=listp;p;p=p->ai_next){
		if((clientfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol))<0)
		  continue;
		if(connect(clientfd,p->ai_addr,p->ai_addrlen)!=-1)
		  break;
		close(clientfd);
	}

	freeaddrinfo(listp);
	if(!p) return -1;
	else return clientfd;
}
int open_listenfd(char *port)
{
	struct addrinfo hints, *listp, *p;
	int listenfd, optval=1;
	memset(&hints, 0 ,sizeof(struct addrinfo));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
	hints.ai_flags |= AI_NUMERICSERV;
	getaddrinfo(NULL, port, &hints, &listp);

	for(p=listp;p;p=p->ai_next){
		if((listenfd = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) < 0)
		  continue;
		setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(const void *)&optval,sizeof(int));

		if(bind(listenfd,p->ai_addr,p->ai_addrlen)==0)
		  break; //success
		close(listenfd); //bind fail, try next
	}

	freeaddrinfo(listp);
	if(!p)
	  return -1;
	if(listen(listenfd,LISTENQ)<0){
		close(listenfd);
		return -1;
	}
	return listenfd;
}

我们的服务器有以下几个模块

  • int main(int argc, char** argv):从shell读取参数作为端口,使用上述open_listenfd创建一个监听端口,使用accept创建一个连接,进入do_it开始处理事务
  • void doit(int fd):do_it的参数是connfd,读取请求,调用parse_uri分析请求是静态请求还是动态请求,分别调用serve_static和serve_dynamic进行响应
  • int parse_uri(char *uri, char *filename, char *cgiargs):url不包含cgi-bin则认为是静态请求,读取请求的文件,若没有,默认返回home.html;否则认为是动态请求
  • void serve_static(int fd, char *filename, int filesize):发送header,再发送文件
  • void serve_dynamic(int fd, char *filename, char *cgiargs):服务动态内容,在cgi-bin找运行程序
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include "mynet.h"

void doit(int fd);
void read_requesthdrs(int fd);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char*cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char** argv)
{
	int listenfd,connfd;
	char hostname[MAXLINE], port[MAXLINE];
	socklen_t clientlen;
	struct sockaddr_storage clientaddr;

	if(argc!=2){
		fprintf(stderr, "usage: %s <port>\n",argv[0]);
		exit(1);
	}

	listenfd = open_listenfd(argv[1]);
	while(1){
		clientlen = sizeof(clientaddr);
		connfd = accept(listenfd,(SA*)&clientaddr,&clientlen);
		getnameinfo((SA*)&clientaddr,clientlen,hostname,MAXLINE,port,MAXLINE,0);
		printf("Accepted connection from (%s, %s)\n",hostname,port);
		doit(connfd);
		close(connfd);
	}
}


void doit(int fd){
	int is_static;
	struct stat sbuf;
	char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
	char filename[MAXLINE], cgiargs[MAXLINE];

	read(fd, buf, MAXLINE);
	printf("Requset hears:\n");
	printf("%s",buf);
	sscanf(buf, "%s %s %s", method, uri, version);
	if(strcasecmp(method, "GET")){
		clienterror(fd, method, "501", "Not implementedd","Tiny does not implement this method");
		return;
	}
	//read_requesthdrs(fd);
	is_static = parse_uri(uri,filename,cgiargs);
	if(stat(filename,&sbuf)<0){
		clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file");
		return;
	}

	if(is_static){
		if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)){
			clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
			return;
		}
		serve_static(fd,filename,sbuf.st_size);
	}
	else {
		if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)){
			clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
			return;
		}
		serve_dynamic(fd,filename,cgiargs);
	}
}

void read_requesthdrs(int fd){
	char buf[MAXLINE];

	read(fd,buf,MAXLINE);
	while(strcmp(buf, "\r\n")){
		read(fd,buf,MAXLINE);
		printf("%s",buf);
	}
	return;
}
int parse_uri(char *uri, char *filename, char *cgiargs){
	char *ptr;

	if(!strstr(uri, "cgi-bin")) {
		strcpy(cgiargs,"");
		strcpy(filename,".");
		strcat(filename,uri);
		if(uri[strlen(uri)-1] == '/')
		  strcat(filename,"home.html");
		return 1;
	}
	else{	//dynammic content
		ptr = index(uri,'?');
		if (ptr) {
			strcpy(cgiargs, ptr+1);
			*ptr = '\0';
		}
		else
		  strcpy(cgiargs, ptr+1);
		strcpy(filename,".");
		strcat(filename,uri);
		return 0;
	}
}


void serve_static(int fd, char *filename, int filesize){
	int srcfd;
	char *srcp, filetype[MAXLINE], buf[MAXLINE*10];

	get_filetype(filename, filetype);
	sprintf(buf, "HTTP/1.0 200 OK\r\n");
	sprintf(buf, "%sServer: Tiny Web Server\r\n",buf);
	sprintf(buf, "%sConnection: close\r\n",buf);
	sprintf(buf, "%sContent-length: %d\r\n",buf,filesize);
	sprintf(buf, "%sContent-type: %s\r\n\r\n",buf,filetype);
	write(fd,buf,strlen(buf));
	printf("Response headers:\n");
	printf("%s",buf);

	srcfd = open(filename,O_RDONLY, 0);
	srcp = mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
	close(srcfd);
	write(fd,srcp,filesize);
	munmap(srcp,filesize);
}

void get_filetype(char *filename, char *filetype){
	 if(strstr(filename,".html"))
	   strcpy(filetype, "text/html");
	 else if (strstr(filename,".gif"))
	   strcpy(filetype, "image/gif");
	else if (strstr(filename,".png"))
	   strcpy(filetype, "image/png");
	else if (strstr(filename,".jpg"))
	   strcpy(filetype, "image/jpeg");
	else
	   strcpy(filetype, "image/plain");
}

void serve_dynamic(int fd, char *filename, char *cgiargs){
	char buf[MAXLINE],*emptylist[] = {NULL};

	sprintf(buf,"HTTP/1.0 200 OK\r\n");
	write(fd,buf,strlen(buf));
	sprintf(buf,"Server: Tiny Web Server\r\n");
	write(fd,buf,strlen(buf));

	if(fork()==0) {
		setenv("QUERY_STRING",cgiargs,1);
		dup2(fd,STDOUT_FILENO);
		execve(filename,emptylist,environ);
	}
	wait(NULL);
}

void clienterror(int fd, char*cause, char *errnum, char *shortmsg, char *longmsg){
	char buf[MAXLINE], body[100*MAXLINE];
	sprintf(body,"<html><title>Tiny Error</title>");
	sprintf(body,"%s<body bgcolor=""ffffff"">\r\n",body);
	sprintf(body,"%s%s: %s\r\n",body,errnum,shortmsg);
	sprintf(body,"%s<p>%s: %s\r\n",body,longmsg,cause);
	sprintf(body,"%s<hr><em>The Tiny Web server</em>\r\n",body);

	sprintf(buf, "HTTP/1.0 %s %s\r\n",errnum,shortmsg);
	write(fd,buf,strlen(buf));
	sprintf(buf, "Content-type: text/html\r\n");
	write(fd,buf,strlen(buf));
	sprintf(buf, "Content-length: %d\r\n\r\n",(int)strlen(body));
	write(fd,buf,strlen(buf));
	write(fd,body,strlen(body));
}