headPrintServer.c: 創造一個有Hello World!的網頁
#include "../net.h"
char response[] = "HTTP/1.1 200 OK\r\n" // 響應行 版本 響應碼 附加信息
"Content-Type: text/plain; charset=UTF-8\r\n" // 響應頭
"Content-Length: 14\r\n\r\n"
"Hello World!\r\n"; //響應體
int main(int argc, char *argv[]) {
int port = (argc >= 2) ? atoi(argv[1]) : PORT;
net_t net;
net_init(&net, TCP, SERVER, port, NULL);
net_bind(&net);
net_listen(&net);
printf("Server started at port: %d\n", net.port);
while (1) {
int client_fd = net_accept(&net); // 阻塞函數,等待用戶連接
if (client_fd == -1) {
printf("Can't accept");
continue;
}
// == 取得表頭並印出來 ==
char header[TMAX];
int len = read(client_fd, header, TMAX);
header[len] = '\0';
printf("===========header=============\n%s\n", header);
write(client_fd, response, sizeof(response) - 1); /*-1:'\0'*/
close(client_fd);
}
}
helloWebServer.c: 讓Client可以傳送訊息到Server,傳送一次連線就斷開
#include "../net.h"
char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain; charset=UTF-8\r\n"
"Content-Length: 14\r\n\r\n"
"Hello World!\r\n";
int main(int argc, char *argv[]) {
int port = (argc >= 2) ? atoi(argv[1]) : PORT;
net_t net;
net_init(&net, TCP, SERVER, port, NULL);
net_bind(&net);
net_listen(&net);
printf("Server started at port: %d\n", net.port);
int count=0;
while (1) {
int client_fd = net_accept(&net); // recv
printf("%d:got connection, client_fd=%d\n", count++, client_fd);
int n = write(client_fd, response, strlen(response)); // send
fsync(client_fd); // 把緩衝區清出去,強制送出所有訊息
assert(n > 0);
sleep(1); // 一秒處理一個請求
close(client_fd);
}
}
htmlServer.c
#include "../net.h"
#include "httpd.h"
int main(int argc, char *argv[]) {
int port = (argc >= 2) ? atoi(argv[1]) : PORT;
net_t net;
net_init(&net, TCP, SERVER, port, NULL);
net_bind(&net);
net_listen(&net);
printf("Server started at port: %d\n", net.port);
while (1) {
int client_fd = net_accept(&net);
if (client_fd == -1) {
printf("Can't accept");
continue;
}
char header[TMAX], path[SMAX];
readHeader(client_fd, header);
printf("===========header=============\n%s\n", header);
parseHeader(header, path);
printf("path=%s\n", path);
if (strstr(path, ".htm") != NULL) {
printf("path contain .htm\n");
responseFile(client_fd, path); // 拿到資料丟回去到網站
} else {
printf("not html => no response!\n");
}
sleep(1);
close(client_fd);
}
}
htmlThreadServer.c: 把效能丟給作業系統做處理,運用mutithread
#include <pthread.h>
#include "../net.h"
#include "httpd.h"
void *serve(void *argu) {
int client_fd = *(int*) argu;
if (client_fd == -1) {
printf("Can't accept");
return NULL;
}
char header[TMAX], path[SMAX];
readHeader(client_fd, header);
printf("===========header=============\n%s\n", header);
parseHeader(header, path);
printf("path=%s\n", path);
if (strstr(path, ".htm") != NULL) { // strstr是找子字串,尋找副檔名 ".htm"
printf("path contain .htm\n");
responseFile(client_fd, path); // 回應檔案到路徑上
} else {
printf("not html => no response!\n");
}
sleep(1);
close(client_fd);
return NULL;
}
int main(int argc, char *argv[]) {
int port = (argc >= 2) ? atoi(argv[1]) : PORT;
net_t net;
net_init(&net, TCP, SERVER, port, NULL);
net_bind(&net);
net_listen(&net);
printf("Server started at port: %d\n", net.port);
while (1) {
int client_fd = net_accept(&net);
pthread_t thread1;
pthread_create(&thread1, NULL, &serve, &client_fd); // 寫程式碼最方便的模式,不過效能不是最好的,使用epoll會把效能提升更高
}
}
http.c
#include "httpd.h"
void readHeader(int client_fd, char *header) {
int len = read(client_fd, header, TMAX);
header[len] = '\0';
}
void parseHeader(char *header, char *path) {
sscanf(header, "GET %s HTTP/", path);
}
void responseFile(int client_fd, char *path) {
char text[TMAX], response[TMAX], fpath[SMAX];
sprintf(fpath, "./web%s", path); // ex: fpath = ./web/hello.html
printf("responseFile:fpath=%s\n", fpath);
FILE *file = fopen(fpath, "r");
int len;
if (file == NULL) {
strcpy(text, "<html><body><h1>File not Found!</h1></body></html>");
len = strlen(text);
} else {
len = fread(text, 1, TMAX, file);
text[len] = '\0';
}
sprintf(response, "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Content-Length: %d\r\n\r\n%s", len, text);
write(client_fd, response, strlen(response));
}
http.h
#ifndef __HTTPD_H__ // 使用引用防護,也可以使用 #pragma once,避免重複定義
#define __HTTPD_H__
#include "../net.h"
void readHeader(int client_fd, char *header);
void parseHeader(char *header, char *path);
void responseFile(int client_fd, char *path);
#endif
net.h
#ifndef __NET_H__
#define __NET_H__
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <time.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/errno.h>
#define PORT 8080
#define SMAX 256
#define TMAX 65536
#define ENDTAG "<__end__>"
enum { CLIENT, SERVER };
enum { TCP, UDP };
typedef struct _net_t {
char *serv_ip;
struct sockaddr_in serv_addr;
int sock_fd, port, side, protocol;
} net_t;
int net_init(net_t *net, int protocol, int side, int port, char *host);
int net_connect(net_t *net);
int net_bind(net_t *net);
int net_listen(net_t *net);
int net_accept(net_t *net);
int net_close(net_t *net);
#endif
net.c
#include "net.h"
char ip[SMAX];
char *host_to_ip(char *hostname, char *ip) { // 查出 host 對應的 ip
struct hostent *host = gethostbyname(hostname);
/*
char **pp;
printf("hostname=%s\n", host->h_name);
for (pp=host->h_aliases; *pp != NULL; pp++)
printf(" alias:%s\n", *pp);
for (pp=host->h_addr_list; *pp != NULL; pp++)
printf(" address:%s\n", inet_ntop(host->h_addrtype, *pp, ip, SMAX));
printf(" ip=%s\n", ip);
*/
inet_ntop(host->h_addrtype, host->h_addr_list[0], ip, SMAX); // 取第一個 IP
return ip;
}
int net_init(net_t *net, int protocol, int side, int port, char *host) {
memset(net, 0, sizeof(net_t));
net->protocol = protocol;
net->side = side;
net->port = port;
net->serv_ip = (side==CLIENT) ? host_to_ip(host, ip) : "127.0.0.1";
int socketType = (protocol == TCP) ? SOCK_STREAM : SOCK_DGRAM;
net->sock_fd = socket(AF_INET, socketType, 0);
assert(net->sock_fd >= 0);
net->serv_addr.sin_family = AF_INET;
net->serv_addr.sin_addr.s_addr = (side == SERVER) ? htonl(INADDR_ANY) : inet_addr(net->serv_ip);
net->serv_addr.sin_port = htons(net->port);
return 0;
}
int net_connect(net_t *net) {
int r = connect(net->sock_fd, (struct sockaddr *)&net->serv_addr, sizeof(net->serv_addr));
// assert(r>=0);
return r;
}
int net_bind(net_t *net) {
int r = bind(net->sock_fd, (struct sockaddr*)&net->serv_addr, sizeof(net->serv_addr));
assert(r>=0);
return r;
}
int net_listen(net_t *net) {
int r = listen(net->sock_fd, 10); // 最多十個同時連線
assert(r>=0);
return r;
}
int net_accept(net_t *net) {
int r = accept(net->sock_fd, (struct sockaddr*)NULL, NULL);
assert(r>=0);
return r;
}
int net_close(net_t *net) {
shutdown(net->sock_fd, SHUT_WR);
close(net->sock_fd);
return 0;
}
可以使用gbd除錯,可以使用n逐行除錯,看看程式碼到底哪裡有問題
可以參考進階文章: 高效 Web 伺服器開發 - HackMD
可以使用epoll,epoll因為只會使用有用到的部分,而不是全部掃描,所以效能會變快