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;
}
終端運作伺服器程式:
浏覽器請求服務,得到相應内容:
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;
}