1. 概述
服務器的開發不容易,尤其是開發高性能、穩定性好服務器,更加不容易,因此人們嘗試更好簡單的方式來開發軟件。
在服務器方面,使用Web服務器,採用HTTP協議來代替底層的socket,是常見的選擇。採用HTTP協議更加除了能得到穩定的服務器支持外,更加可以兼容各種客戶端(手機、PC、瀏覽器)等等。這樣實現了一個服務器之後,多個客戶端可以通用。
2.通信過程
HTTP 協議採用請求/響應模型。客戶端向服務器發送一個請求報文,服務器以一個狀態作爲響應。
HTTP 請求/響應的步驟:
- 客戶端連接到web服務器:HTTP 客戶端與web服務器建立一個 TCP 連接;
- 客戶端向服務器發起 HTTP 請求:通過已建立的TCP 連接,客戶端向服務器發送一個請求報文;
- 服務器接收 HTTP 請求並返回 HTTP 響應:服務器解析請求,定位請求資源,服務器將資源副本寫到 TCP 連接,由客戶端讀取;
- 釋放 TCP 連接:若connection 模式爲close,則服務器主動關閉TCP 連接,客戶端被動關閉連接,釋放TCP 連接;若connection 模式爲keepalive,則該連接會保持一段時間,在該時間內可以繼續接收請求;
- 客戶端瀏覽器解析HTML內容:客戶端將服務器響應的 html 文本解析並顯示。
3. 測試代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
// 創建通信端點:套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
// 設置本地地址結構體
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)); // 清空
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(8000); // 端口
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
// 綁定
int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if( err_log != 0)
{
perror("binding");
close(sockfd);
return -1;
}
err_log = listen(sockfd, 10); // 監聽,監聽套接字改爲被動
if(err_log != 0)
{
perror("listen");
close(sockfd);
return -1;
}
printf("listen client @port=%d...\n", 8000);
int connfd;
connfd = accept(sockfd, NULL, NULL); // 等待連接
char recv_buf[8*1024] = {0};
read(connfd, recv_buf, sizeof(recv_buf));
printf("%s", recv_buf);
//獲取客戶端需要的網頁內容
char filename[200] = {0};
sscanf(recv_buf, "GET /%[^ ]", filename); //獲取文件名字
printf("filename = %s\n", filename);
int fd;
fd = open(filename, O_RDONLY);//只讀方式打開
if(fd < 0)//打開文件失敗
{
//HTTP 響應報文由狀態行、響應頭部、空行、響應包體4個部分組成
char err[]= "HTTP/1.1 404 Not Found\r\n" //狀態行
"Content-Type: text/html\r\n" //響應頭部
"\r\n" //空行
"<HTML><BODY>File not found</BODY></HTML>"; //響應包體
perror("open");
send(connfd, err, strlen(err), 0);//發送失敗的響應報文頭
close(connfd);
return -1;
}
//HTTP 響應報文由狀態行、響應頭部、空行、響應包體4個部分組成
char head[] = "HTTP/1.1 200 OK\r\n" //狀態行
"Content-Type: text/html\r\n" //響應頭部
"\r\n"; //空行
send(connfd, head, strlen(head), 0); //發送成功的響應報文頭
//發送響應包體
int len;
char file_buf[4 * 1024];
//循環讀取併發送文件,讀多少,發多少
while((len = read(fd, file_buf, sizeof(file_buf))) > 0)
{
send(connfd, file_buf, len, 0);
}
close(fd);
close(connfd);
while(1)
{
NULL;
}
return 0;
}
- 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
- 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
終端運行服務器程序:
瀏覽器請求服務,得到相應內容:
4. 簡單版Web服務器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/************************************************************************
函數名稱: int main(int argc, char *argv[])
函數功能: 通過進程創建webserver
函數參數: int argc, char *argv[]
函數返回: 無
************************************************************************/
int main(int argc, char *argv[])
{
unsigned short port = 8000; //設置默認端口號
if(argc > 1)
{
port = atoi(argv[1]); //將參數2賦值給端口號變量
}
//創建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if( sockfd < 0)
{
perror("socket");
exit(-1);
}
//服務器套接字地址變量賦值
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; //IPV4族
my_addr.sin_port = htons(port); //將端口號轉換成網絡字節序
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本機IP地址
//綁定TCP套接字
if( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)) != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
//監聽
if( listen(sockfd, 10) != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("Listenning at port=%d\n",port); //打印端口號信息
printf("Usage: http://127.0.0.1:%d/html/index.html\n", port);
while(1)
{
char cli_ip[INET_ADDRSTRLEN] = {0}; //存放客戶端點分十進制IP地址
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
//等待客戶端連接
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
printf("connfd=%d\n",connfd); //打印已連接套接字
if(connfd > 0)
{
if(fork() == 0) //創建進程並判斷返回值
{
close(sockfd);
//子進程執行
int fd = 0;
int len = 0;
char buf[1024] = "";
char filename[50] = "";
//將網絡字節序轉換成點分十進制形式存放在cli_ip中
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("connected form %s\n\r", cli_ip); //打印點分十進制形式的客戶端IP地址
recv(connfd, buf, sizeof(buf), 0); //接收客戶端發送的請求內容
sscanf(buf, "GET /%[^ ]", filename); //解析客戶端發送請求字符串
printf("filename=*%s*\n", filename);
fd = open(filename, O_RDONLY); //以只讀方式打開文件
if( fd < 0) //如果打開文件失敗
{
//HTTP失敗頭部
char err[]= "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<HTML><BODY>File not found</BODY></HTML>";
perror("open error");
send(connfd, err, strlen(err), 0);
close(connfd); //關閉已連接套接字
exit(0); //子進程退出
}
//打開文件成功後
//接收成功時返回的頭部
char head[]="HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n";
send(connfd, head, strlen(head), 0); //發送HTTP請求成功頭部
while( (len = read(fd, buf, sizeof(buf))) > 0) //循環讀取文件內容
{
send(connfd, buf, len, 0); //將讀得的數據發送給客戶端
}
close(fd); //成功後關閉文件
close(connfd); //關閉已連接套接字
exit(0); //子進程退出
}
}
close(connfd); //父進程關閉連接套接字
}
close(sockfd);
printf("exit main!\n");
return 0;
}