Skip to content

Latest commit

 

History

History
350 lines (272 loc) · 8.24 KB

13.md

File metadata and controls

350 lines (272 loc) · 8.24 KB

HTTPServer

Hello World!

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);
  	}
}

connect

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);
    }
}

response

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);
  	}
}

thread

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因為只會使用有用到的部分,而不是全部掃描,所以效能會變快