以下是參考<winsock網絡編程經絡>中講解web應用http協議的時候,實現的一個簡單的http程序,包含一個服務器和一個客戶端。
先貼上客戶端的程序:
/************************************************************************* * * Copyright (c) 2012-2013 by xuwm All Rights Reserved * * FILENAME: WebClnt.c * * PURPOSE : HTTP 客戶端程序, 獲取網頁. * * AUTHOR : 許文敏 * **************************************************************************/ #include "stdafx.h" #include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") /* WinSock使用的庫函數 */ /* 定義常量 */ #define HTTP_DEF_PORT 80 /* 連接的缺省端口 */ #define HTTP_BUF_SIZE 1024 /* 緩沖區的大小 */ #define HTTP_HOST_LEN 256 /* 主機名長度 */ char *http_req_hdr_tmpl = "GET %s HTTP/1.1\r\n" "Accept: image/gif, image/jpeg, */*\r\nAccept-Language: zh-cn\r\n" "Accept-Encoding: gzip, deflate\r\nHost: %s:%d\r\n" "User-Agent: Huiyong's Browser <0.1>\r\nConnection: Keep-Alive\r\n\r\n"; /************************************************************************** * * 函數功能: 解析命令行參數, 分別得到主機名, 端口號和文件名. 命令行格式: * [http://www.baidu.com:8080/index.html] * * 參數說明: [IN] buf, 字符串指針數組; * [OUT] host, 保存主機; * [OUT] port, 端口; * [OUT] file_name, 文件名; * * 返 回 值: void. * **************************************************************************/ void http_parse_request_url(const char *buf, char *host, unsigned short *port, char *file_name) { int length = 0; char port_buf[8]; char *buf_end = (char *)(buf + strlen(buf)); char *begin, *host_end, *colon, *file; /* 查找主機的開始位置 */ begin = const_cast<char*>(strstr(buf, "//")); begin = (begin ? begin + 2 : const_cast<char*>(buf)); colon = strchr(begin, ':'); host_end = strchr(begin, '/'); if (host_end == NULL) { host_end = buf_end; } else { /* 得到文件名 */ file = strrchr(host_end, '/'); if (file && (file + 1) != buf_end) strcpy(file_name, file + 1); } if (colon) /* 得到端口號 */ { colon++; length = host_end - colon; memcpy(port_buf, colon, length); port_buf[length] = 0; *port = atoi(port_buf); host_end = colon - 1; } /* 得到主機信息 */ length = host_end - begin; memcpy(host, begin, length); host[length] = 0; } int main(int argc, char **argv) { WSADATA wsa_data; SOCKET http_sock = 0; /* socket 句柄 */ struct sockaddr_in serv_addr; /* 服務器地址 */ struct hostent *host_ent; int result = 0, send_len; char data_buf[HTTP_BUF_SIZE]; char host[HTTP_HOST_LEN] = "127.0.0.1"; unsigned short port = HTTP_DEF_PORT; unsigned long addr; char file_name[HTTP_HOST_LEN] = "index.html"; char file_nameforsave[HTTP_HOST_LEN] = "index1.html"; FILE *file_web; if (argc != 2) { printf("[Web] input : %s http://www.test.com[:8080]/index.html", argv[0]); return -1; } http_parse_request_url(argv[1], host, &port, file_name); WSAStartup(MAKEWORD(2,0), &wsa_data); /* 初始化 WinSock 資源 */ addr = inet_addr(host); if (addr == INADDR_NONE) { host_ent = gethostbyname(host); if (!host_ent) { printf("[Web] invalid host\n"); return -1; } memcpy(&addr, host_ent->h_addr_list[0], host_ent->h_length); } /* 服務器地址 */ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); serv_addr.sin_addr.s_addr = addr; http_sock = socket(AF_INET, SOCK_STREAM, 0); /* 創建 socket */ result = connect(http_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); if (result == SOCKET_ERROR) /* 連接失敗 */ { closesocket(http_sock); printf("[Web] fail to connect, error = %d\n", WSAGetLastError()); return -1; } /* 發送 HTTP 請求 */ send_len = sprintf(data_buf, http_req_hdr_tmpl, argv[1], host, port); result = send(http_sock, data_buf, send_len, 0); if (result == SOCKET_ERROR) /* 發送失敗 */ { printf("[Web] fail to send, error = %d\n", WSAGetLastError()); return -1; } file_web = fopen(file_nameforsave, "a+"); do /* 接收響應並保存到文件中 */ { result = recv(http_sock, data_buf, HTTP_BUF_SIZE, 0); if (result > 0) { fwrite(data_buf, 1, result, file_web); /* 在屏幕上輸出 */ data_buf[result] = 0; printf("%s", data_buf); } } while(result > 0); fclose(file_web); closesocket(http_sock); WSACleanup(); return 0; }
首先在vs2010中的,添加一個VC命令行程序,把上面的程序直接放到主程序對應的cpp文件中,然后編譯即可。
再貼上服務端的程序:
/************************************************************************* * * Copyright (c) 2012-2013 by xuwm All Rights Reserved * * FILENAME: WebSrv.c * * PURPOSE : HTTP 服務器程序, 向客戶端提供請求的文件內容. * * AUTHOR : 許文敏 * **************************************************************************/ #include "stdafx.h" #include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") /* WinSock使用的庫函數 */ /* 定義常量 */ #define HTTP_DEF_PORT 80 /* 連接的缺省端口 */ #define HTTP_BUF_SIZE 1024 /* 緩沖區的大小 */ #define HTTP_FILENAME_LEN 256 /* 文件名長度 */ /* 定義文件類型對應的 Content-Type */ struct doc_type { char *suffix; /* 文件后綴 */ char *type; /* Content-Type */ }; struct doc_type file_type[] = { {"html", "text/html" }, {"gif", "image/gif" }, {"jpeg", "image/jpeg" }, { NULL, NULL } }; char *http_res_hdr_tmpl = "HTTP/1.1 200 OK\r\nServer: Huiyong's Server <0.1>\r\n" "Accept-Ranges: bytes\r\nContent-Length: %d\r\nConnection: close\r\n" "Content-Type: %s\r\n\r\n"; /************************************************************************** * * 函數功能: 根據文件后綴查找對應的 Content-Type. * * 參數說明: [IN] suffix, 文件名后綴; * * 返 回 值: 成功返回文件對應的 Content-Type, 失敗返回 NULL. * **************************************************************************/ char *http_get_type_by_suffix(const char *suffix) { struct doc_type *type; for (type = file_type; type->suffix; type++) { if (strcmp(type->suffix, suffix) == 0) return type->type; } return NULL; } /************************************************************************** * * 函數功能: 解析請求行, 得到文件名及其后綴. 請求行格式: * [GET http://www.baidu.com:8080/index.html HTTP/1.1] * * 參數說明: [IN] buf, 字符串指針數組; * [IN] buflen, buf 的長度; * [OUT] file_name, 文件名; * [OUT] suffix, 文件名后綴; * * 返 回 值: void. * **************************************************************************/ void http_parse_request_cmd(char *buf, int buflen, char *file_name, char *suffix) { int length = 0; char *begin, *end, *bias; /* 查找 URL 的開始位置 */ begin = strchr(buf, ' '); begin += 1; /* 查找 URL 的結束位置 */ end = strchr(begin, ' '); *end = 0; bias = strrchr(begin, '/'); length = end - bias; /* 找到文件名的開始位置 */ if ((*bias == '/') || (*bias == '\\')) { bias++; length--; } /* 得到文件名 */ if (length > 0) { memcpy(file_name, bias, length); file_name[length] = 0; begin = strchr(file_name, '.'); if (begin) strcpy(suffix, begin + 1); } } /************************************************************************** * * 函數功能: 向客戶端發送 HTTP 響應. * * 參數說明: [IN] buf, 字符串指針數組; * [IN] buf_len, buf 的長度; * * 返 回 值: 成功返回非0, 失敗返回0. * **************************************************************************/ int http_send_response(SOCKET soc, char *buf, int buf_len) { int read_len, file_len, hdr_len, send_len; char *type; char read_buf[HTTP_BUF_SIZE]; char http_header[HTTP_BUF_SIZE]; char file_name[HTTP_FILENAME_LEN] = "index.html", suffix[16] = "html"; FILE *res_file; /* 得到文件名和后綴 */ http_parse_request_cmd(buf, buf_len, file_name, suffix); res_file = fopen(file_name, "rb+"); /* 用二進制格式打開文件 */ if (res_file == NULL) { printf("[Web] The file [%s] is not existed\n", file_name); return 0; } fseek(res_file, 0, SEEK_END); file_len = ftell(res_file); fseek(res_file, 0, SEEK_SET); type = http_get_type_by_suffix(suffix); /* 文件對應的 Content-Type */ if (type == NULL) { printf("[Web] There is not the related content type\n"); return 0; } /* 構造 HTTP 首部,並發送 */ hdr_len = sprintf(http_header, http_res_hdr_tmpl, file_len, type); send_len = send(soc, http_header, hdr_len, 0); //send_len=1; if (send_len == SOCKET_ERROR) { fclose(res_file); printf("[Web] Fail to send, error = %d\n", WSAGetLastError()); return 0; } do /* 發送文件, HTTP 的消息體 */ { read_len = fread(read_buf, sizeof(char), HTTP_BUF_SIZE, res_file); if (read_len > 0) { send_len = send(soc, read_buf, read_len, 0); file_len -= read_len; } } while ((read_len > 0) && (file_len > 0)); fclose(res_file); return 1; } int main(int argc, char **argv) { WSADATA wsa_data; SOCKET srv_soc = 0, acpt_soc; /* socket 句柄 */ struct sockaddr_in serv_addr; /* 服務器地址 */ struct sockaddr_in from_addr; /* 客戶端地址 */ char recv_buf[HTTP_BUF_SIZE]; unsigned short port = HTTP_DEF_PORT; int from_len = sizeof(from_addr); int result = 0, recv_len; if (argc == 2) /* 端口號 */ port = atoi(argv[1]); WSAStartup(MAKEWORD(2,0), &wsa_data); /* 初始化 WinSock 資源 */ srv_soc = socket(AF_INET, SOCK_STREAM, 0); /* 創建 socket */ if (srv_soc == INVALID_SOCKET) { printf("[Web] socket() Fails, error = %d\n", WSAGetLastError()); return -1; } /* 服務器地址 */ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); result = bind(srv_soc, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); if (result == SOCKET_ERROR) /* 綁定失敗 */ { closesocket(srv_soc); printf("[Web] Fail to bind, error = %d\n", WSAGetLastError()); return -1; } result = listen(srv_soc, SOMAXCONN); printf("[Web] The server is running ... ...\n"); while (1) { acpt_soc = accept(srv_soc, (struct sockaddr *) &from_addr, &from_len); if (acpt_soc == INVALID_SOCKET) /* 接受失敗 */ { printf("[Web] Fail to accept, error = %d\n", WSAGetLastError()); break; } printf("[Web] Accepted address:[%s], port:[%d]\n", inet_ntoa(from_addr.sin_addr), ntohs(from_addr.sin_port)); recv_len = recv(acpt_soc, recv_buf, HTTP_BUF_SIZE, 0); if (recv_len == SOCKET_ERROR) /* 接收失敗 */ { closesocket(acpt_soc); printf("[Web] Fail to recv, error = %d\n", WSAGetLastError()); break; } recv_buf[recv_len] = 0; /* 向客戶端發送響應數據 */ result = http_send_response(acpt_soc, recv_buf, recv_len); closesocket(acpt_soc); } closesocket(srv_soc); WSACleanup(); printf("[Web] The server is stopped.\n"); return 0; }
這個也跟客戶端程序一樣,打開VS2010,新建一個VC命令行程序,COPY上面的代碼,直接放到主程序的CPP文件中,編譯即可。
運行代碼如下:
1.先運行服務端程序,綁定端口,然后開啟監聽 在CMD里先切換到exe的目錄,然后 輸入 服務端程序名.exe 9000,此處服務端程序名換成對應的程序名稱.后面的9000端口號,也可以換成別的。
2. 再運行客戶羰程序,同上面一樣,切換到exe 的目錄,然后輸入 客戶端程序名.exe http://127.0.0.1:9000/index.html, 此處客戶端程序名換成對應的程序名稱,后面的http://127.0.0.1:9000/index.html,代表請求的網頁路徑。
3. 在服務器的exe目錄下,應創建一個index.html文件,里面可以輸入一個正規的html文件。
以上只是學習網絡編程的一點小體會,盡當以后溫故:)