程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C語言實現的Web服務器

C語言實現的Web服務器

編輯:關於C語言
 

自己研究了好幾天終於寫出來一個,哈哈,當然也從網上得到了很多的幫助拉。謝謝大家咯!這個版本還不是很完善,但Web服務器的基本框架已經出來了,還有部分的功能需要進行進一步的測試和修改。雖然說C的開發比較慢,對於程序員來說比較難以操作,但通過用C寫這些很底層的東西,可以更好的了解的象java的socket中的工作原理。有一定的幫助!

 

以下是源代碼:

 

/**************filename: Server.cpp****************
該程序通過標准socket實現簡單Http服務器
運行該服務器可以通過浏覽器訪問服務器目錄下的
Html文件和jpg圖片 完成初步的Http服務器功能
***************************************************/

#include <winsock.h>
#include <sys/stat.h>
#include <iostream>
using namespace std;

#define SERVER_PORT 10000 //自定義的服務端口
#define HOSTLEN 256 //主機名長度
#define BACKLOG 10 //同時等待的連接個數

/**************************************
該方法包裝了send()
通過該方法發送數據 能夠全部發出
沒有遺漏
**************************************/
int sendall(int s, char *buf, int *len) {
int total = 0; // 已經發送字節數
int bytesleft = *len; //還剩余多少字節
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // 返回實際發送出去的字節數
return n==-1?-1:0; // 成功發送返回0 失敗-1
}

/**************************************
該方法處理錯誤請求
並向客戶端發送錯誤信息
**************************************/
void wrong_req(int sock) {
char* error_head = "HTTP/1.0 501 Not Implemented\r\n"; //輸出501錯誤
int len = strlen(error_head);
if (sendall(sock, error_head, &len) == -1) { //向客戶發送
printf("Sending failed!");
return;
}

char* error_type = "Content-type: text/plain\r\n";
len = strlen(error_type);
if (sendall(sock, error_type, &len) == -1) {
printf("Sending failed!");
return;
}

char* error_end = "\r\n";
len = strlen(error_end);
if (sendall(sock, error_end, &len) == -1) {
printf("Sending failed!");
return;
}

char* prompt_info = "The command is not yet completed\r\n";
len = strlen(prompt_info);
if (sendall(sock, prompt_info, &len) == -1) {
printf("Sending failed!");
return;
}
}

/**********************************
該方法判斷用戶請求的文件是否存在
不存在返回true 存在返回false
***********************************/
bool not_exit(char* arguments) {
struct stat dir_info;
return (stat(arguments, &dir_info) == -1);
}

/*************************************
所請求的文件不存在
*************************************/
void file_not_found(char* arguments, int sock) {

char* error_head = "HTTP/1.0 404 Not Found\r\n"; //構造404錯誤head
int len = strlen(error_head);
if (sendall(sock, error_head, &len) == -1) { //向客戶端發送
printf("Sending error!");
return;
}

char* error_type = "Content-type: text/plain\r\n";
len = strlen(error_type);
if (sendall(sock, error_type, &len) == -1) {
printf("Sending error!");
return;
}

char* error_end = "\r\n";
len = strlen(error_end);
if (sendall(sock, error_end, &len) == -1) {
printf("Sending error!");
return;
}

char prompt_info[50] = "Not found: ";
strcat(prompt_info, arguments);
len = strlen(prompt_info);
if (sendall(sock, prompt_info, &len) == -1) { //輸出未找到的文件
printf("Sending error!");
return;
}
}

/*************************************
發送Http協議頭部信息
其中包括響應類型和Content Type
*************************************/
void send_header(int send_to, char* content_type) {

char* head = "HTTP/1.0 200 OK\r\n"; //正確的頭部信息
int len = strlen(head);
if (sendall(send_to, head, &len) == -1) { //向連接的客戶端發送數據
printf("Sending error");
return;
}

if (content_type) { //content_type不為空
char temp_1[30] = "Content-type: "; //准備好要連接的字串
strcat(temp_1, content_type); //構造content_type
strcat(temp_1, "\r\n");
len = strlen(temp_1);
if (sendall(send_to, temp_1, &len) == -1) {
printf("Sending error!");
return;
}
}
}

/***********************************
取得用戶所請求的文件類型
即文件後綴 (.html .jpg .gif)
************************************/
char* file_type(char* arg) {
char * temp; //臨時字符串指針
if ((temp=strrchr(arg,'.')) != NULL) { //取得後綴
return temp+1;
}
return ""; //如果請求的文件名中沒有. 則返回空串
}

/*************************************
該方法為程序核心
負責真正發送文件 如*.html *.jpg等
*************************************/
void send_file(char* arguments, int sock) {

char* extension = file_type(arguments); //獲得文件後綴名
char* content_type = "text/plain"; //初始化type='text/plain'
FILE* read_from; //本地文件指針從該文件中讀取.html .jpg等
int readed = -1; //每次讀得的字節數

if (strcmp(extension, "html") == 0) { //發送內容為html
content_type = "text/html";
}

if (strcmp(extension, "gif") == 0) { //發送內容為gif
content_type = "image/gif";
}

if (strcmp(extension, "jpg") == 0) { //發送內容為jpg
content_type = "image/jpg";
}

read_from = fopen(arguments, "r"); //打開用戶指定的文件准備讀取
if(read_from != NULL) { //指針不為空
char read_buf[128]; //讀文件時的字節緩存數組
send_header(sock, content_type); //發送協議頭
send(sock, "\r\n", 2, 0); //再加一個"\r\n" 不能缺少 格式要求

while(!feof(read_from)) { //判斷文件是否已經結束
fgets(read_buf, 128, read_from); //讀取
int len = strlen(read_buf);
if (sendall(sock, read_buf, &len) == -1) { //發送數據
printf("Sending error!"); //出現發送錯誤顯示到控制台 繼續發送
continue;
}
}
}
}

/***********************************
解析並處理用戶請求
***********************************/
void handle_req(char* request, int client_sock) {

char command[BUFSIZ]; //保存解析到的命令字段 GET PUT
char arguments[BUFSIZ]; //保存解析到的請求的文件

/*
* 在用戶請求前加上當前目錄符號
*/
strcpy(arguments, "./"); //注意該符號在不同操作系統的區別

/*
* 解析請求
*/
if (sscanf(request, "%s%s", command, arguments+2) != 2) {
return; //解析出錯在返回
}

printf("handle_cmd: %s\n",command); //向控制台輸出此時的命令
printf("handle_path: %s\n",arguments); //向控制台輸出此時的請求路徑

if (strcmp(command, "GET") != 0) { //請求命令格式是否正確
wrong_req(client_sock);
return;
}

if (not_exit(arguments)) { //請求的文件是否存在
file_not_found(arguments, client_sock);
return;
}

send_file(arguments, client_sock); //命令格式及請求路徑正確則發送數據

return;
}

/*************************************
該方法構造服務器端的SOCKET
返回構造好的socket描述符
*************************************/
int make_server_socket() {
struct sockaddr_in server_addr; //服務器地址結構體

int tempSockId; //臨時存儲socket描述符

tempSockId = socket(PF_INET, SOCK_STREAM, 0);

if (tempSockId == -1) { //如果返回值為-1 則出錯
return -1;
}

/*
* 填充服務器連接信息
*/
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //本地地址
memset(&(server_addr.sin_zero), '\0', 8);

if (bind(tempSockId, (struct sockaddr *)&server_addr,
sizeof(server_addr)) == -1) { //綁定服務如果出錯 則返回-1
printf("bind error!\n");
return -1;
}

if (listen(tempSockId, BACKLOG) == -1 ) { //開始監聽
printf("listen error!\n");
return -1;
}

return tempSockId; //返回取得的SOCKET
}

/***********************
主函數main()
程序入口
***********************/
void main(int argc, char * argv[]) {

/*
* 調用WSAStartup() 便於訪問sockets library
*/
WSADATA wsaData;

if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed.\n");
exit(1);
}

printf("My web server started...\n");

int server_socket; //服務器的socket
int acc_socket; //接收到的用戶連接的socket
int sock_size = sizeof(struct sockaddr_in);

struct sockaddr_in user_socket; //客戶連接信息

server_socket = make_server_socket(); //創建服務器端的socket

if (server_socket == -1) { //創建socket出錯
printf("Server exception!\n");
exit(2);
}

/*
* 主循環
*/
while(true) {
acc_socket = accept(server_socket, (struct sockaddr *)&user_socket, &sock_size); //接收連接

//cout << inet_ntoa(user_socket.sin_addr) << endl; //測試用:-)//

/*
* 讀取客戶請求
*/
int numbytes;
char buf[100];
if ((numbytes=recv(acc_socket, buf, 99, 0)) == -1) {
perror("recv");
exit(1);
}


//printf("buf ... %s", buf); //測試用

/*
* 處理用戶請求
*/
handle_req(buf, acc_socket);
}
}

/**************程序結束Server.cpp******************/

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved