C/C++ http協議發送字段,文件,單個和多張圖片

       關於c/c++ 網絡編程,無論在linux還是windows,要說到自由性,和安全性,socket無疑是比較好的!對於socket,因爲它的傳輸協議只有兩種tcp和udp,屬於網絡層,這裏我們不去重點討論。

       關於應用層協議http,如何用C/C++的socket來實現數據傳輸和下載呢?

   1. http是超文本協議,用在html文件中,那麼對於html是如何傳輸數據呢?   

    通過post或者get傳輸表單數據,當然http還有其他的方式head,put ,delete,option,trace等方式。head和get差不多,唯一的區別就是head只返回協議頭,put和post也很相似,但是可惜html表單數據不支持這一特性,put和post的區別在於,put說出來資源放置於服務器的位置,而post沒有,post將這項權利給予服務器來使用。delete顧名思義,就是指定刪除在服務器上的資源,option一般用來獲取當前URl所支持請求的方法(就是上訴的六種)。

       對於c/c++傳輸單數據,get方法:

         get方法,  形如: http://i.cnblogs.com/EditPosts.aspx?opt=1 

 這個表單傳輸的數據就是1,其中鍵值就是opt,這個需要和服務器上的保持一致

      對於一個簡單的html

1 <html>
2 <head><title>右邊</tile></head>
3 <body>
4  <form >
5   <input  type="text", name="opt" > 1 </input>
6 </form>
7 </body>
8 </html>

  opt就是鍵值

  那麼用socket如何實現: 

    首先,windows下,我們

   1. 先要啓動異步套接字啓動命令

//初始化套結字動態庫
 2 if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) //異步套接字啓動命令
 3     /版本(次,主)    //返回socket實現細節信息
 4     {
 5             system("WSAStartup failed!\n");
 6             system("pause");
 7             return -1;
 8      }

2.在想linux下一樣,創建套接字

  sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

3.綁定端口號,和設置要訪問的服務器主機地址

      //設置服務器地址

      servAddr.sin_family = AF_INET;
      servAddr.sin_addr.s_addr = inet_addr("203.195.192.24");
     servAddr.sin_port = htons((short)80);

4.連接服務器

1 retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));

5.然後接收信息字段

 char *pHttpGet = "GET %s?%s HTTP/1.1\r\n"
         "Host: %s:%d\r\n\r\n"; 
         char strHttpGet[1024] = { 0 };
         //ZeroMemory(strHttpGet, BUF_SZIE);   //初始化內存

         char msg[]="username=Gjxun&pwd=sssssss";
         sprintf(strHttpGet, pHttpGet, addr, msg, host, port);
         int var = send(sHost, strHttpGet, strlen(strHttpGet), 0);
    recv(sHost,rebuf ,sizeof(rebuf),0);

最後關閉的時候。需要用這個來關閉異步套接字

1 WSACleanup( );

這是http的基本流程,對於get發送單個或者多個表單數據如上面所示

對於post而言,情況 會多些,也會複雜些

   1.如果發送的是單個或者多個字段信息,那麼我們的處理方式大致可以有下面這兩種

   第一種: 就像get一樣,只不過單純的將數據放置於協議的後面,需要注意點的是,格式比較重要,特別協議頭和正文部分之間需要各一個空行:

    下面的msg亦可以和get一樣寫成 msg="username=Gxjun&pwd=ssssss"; 還有content-Length的長度: 是正文和正文數據以及尾部長度之後不需要算協議頭長度,不然會,當將連接改爲Connection: Keep-Alive 出現服務器長時間接受現象。---指導服務器接受到結尾幀或者數據長度達到那個長度爲止,纔會響應剛纔的動作!!!!

 1 void sendPost1(char* addr, char * host, char *msg, int port) {
 2         char *pHttpPost = "POST %s HTTP/1.1\r\n"
 3             "Host: %s:%d\r\n"
 4             "Content-Type: application/x-www-form-urlencoded\r\n"
 5             "Content-Length: %d\r\n\r\n"
 6             "%s";
 7 
13         char strHttpPost[1024] = { 0 };
14         //ZeroMemory(strHttpGet, BUF_SZIE);   //初始化內存
15         sprintf(strHttpPost, pHttpPost, addr, host, port, strlen(msg), msg);
16         int var = send(sHost, strHttpPost, strlen(strHttpPost), 0);
17         if (var < 0) {
18             MessageBoxA(NULL, "請求發送失敗!", 0, 0);
19             return;
20         }
21     }

另一種方式:多種數據表單的形式:協議頭部分,將Content-Type: multipart/form-data; 同時還需要加上一個分割標識,即boundary = Gxjunnndgx ,

    整體上就是設置爲 Content-Type: multipart/form-data; boundary=71b23e4066ed\r\n";  

   其他部分參開rfc2038部分。

  所以對於單個或者多個字段表單而言:

比如: 需要像如下的html文件一樣將username和pwd的鍵值數據發送給服務器數據數據:

<html>
 <head></head>
<body>
 <form action="xxx.xxx.xxxx"  method="post">
    <input type="text" name="username">Gxjun</input>
    <input type="password" name="pwd">ssssss</input>
<form>
</body>
</html>
 1 void sendPost(char* addr, char * host,string username,string psw, int port){
 2 
 3         std::string header("");
 6         std::string u_content("");      //用戶名
 7         std::string p_content("");        //密碼
 8 
 9         //----------------------post頭開始--------------------------------  
10         header += "POST ";
11         header += addr;
12         header += " HTTP/1.1\r\n";
13         header += "Host: ";
14         header += host;
15         header += "\r\n";
16         header += "Connection: Keep-Alive\r\n";
17         header += "Accept: */*\r\n";
18         header += "Pragma: no-cache\r\n";
19         header += "Content-Type: multipart/form-data; boundary=71gxjun\r\n";
20 
21         //用戶名數據表單
22         u_content += "--71gxjun\r\n";
23         u_content += "Content-Disposition: form-data; name=\"u\"\r\n\r\n";
24         u_content += username+"\r\n";
25 
26         //密碼數據表單
27         p_content += "--71gxjun\r\n";
28         p_content += "Content-Disposition: form-data; name=\"p\"\r\n\r\n";
29         p_content += psw+"\r\n";
30                 //post尾時間戳  
31         std::string strContent("--71gxjun--\r\n\r\n");
32         char temp[64] = { 0 };
33         //注意下面這個參數Content-Length,這個參數值是:http請求頭長度+請求尾長度+文件總長度
34         // 就分塊傳送 
35         sprintf(temp, "Content-Length: %d\r\n\r\n",
36             p_content.length()+u_content.length() + strContent.length());
37         header += temp;
38         std::string str_http_request;
39         str_http_request.append(header);
40 
41         //----------------------post頭結束-----------------------------------
42         //發送post頭  
43         send(sHost, str_http_request.c_str(), str_http_request.length(), 0);
44         Sleep(0.2);
45         send(sHost, p_content.c_str(), p_content.length(), 0);
46         Sleep(0.2);
47         send(sHost, u_content.c_str(), u_content.length(), 0);
48         Sleep(0.2);
49         ::send(sHost, strContent.c_str(), strContent.length(), 0);
50         Sleep(0.2);
51     }
52 

 對於boundary=abcdegxjun  這部分的數據可以隨意定義,但不要太簡單,不然可能會和數據混淆,上面是兩個字段的發送,所以需要兩部分的正文加正文數據,對於尾部的結束標識,前面需要“--”兩個橫短線後面也需要兩個橫短線“--”,對於中間的分割標誌,只需要前面有“--”就可以了!  還需要注意的是數據發送完之後,需要換行,然後再接上分割標識。

4.然後對於文件和照片的傳輸    ---在linux下,一切接文件,在window下我們也可以將照片看做二進制文件處理

  其實文件的傳輸,都可以作爲二進制文件來傳輸,我們可以將文件

 1     char * ReadFile(char *pathpic, int &pic_len){
 2         //將圖片讀取出來
 3         FILE *fp = fopen(pathpic, "rb");     //打開文件
 4         if (!fp){
 5             MessageBoxA(NULL, "沒有找到文件位置", 0, 0);
 6             return NULL;
 7         }
 8         fseek(fp, 0, SEEK_END);  //一直尋找到文件尾部
 9         pic_len = ftell(fp);  //得到圖片的長度
10         rewind(fp);  //rewind將文件指針指向開頭
11         char *pic_buf = new char[pic_len + 1];  //開闢一個空間在堆上
12         memset(pic_buf, 0, pic_len + 1);  //清空文件指針
13         //讀取文件內容
14         fread(pic_buf,sizeof(char),pic_len,fp);
15         //測試將文件再保存於D:中
16         /*
17         MessageBoxA(NULL, "文件開始", 0, 0);
18         FILE *fpw = fopen("C:\\AA.jpg","wb");
19         fwrite(pic_buf,sizeof(char), pic_len, fpw);
20         fclose(fpw); //關閉文件流
21         MessageBoxA(NULL, "文件結束", 0, 0);
22         */
23         fclose(fp);  
24 
25         return pic_buf;
26     }

對於不同的類型,需要修改不同的Content-Type 比如圖片jpg,jpeg等就是需要這種 ,"Content-Type: image/jpeg,對於其他的的類型,不妨去這兒找找,比較詳細

http://tool.oschina.net/commons

然後下面是一個關於多個字段和多個照片,運用一個form表單,通過一次post,將數據上傳到服務器上!  注: 這裏是在c\s模式, 客戶端是c++ ,服務器是php

  代碼如下:

  1 char * ReadFile(char *pathpic, int &pic_len){
  2         //將圖片讀取出來
  3         FILE *fp = fopen(pathpic, "rb");     //打開文件
  4         if (!fp){
  5             MessageBoxA(NULL, "沒有找到文件位置", 0, 0);
  6             return NULL;
  7         }
  8         fseek(fp, 0, SEEK_END);  //一直尋找到文件尾部
  9         pic_len = ftell(fp);  //得到圖片的長度
 10         rewind(fp);  //rewind將文件指針指向開頭
 11         char *pic_buf = new char[pic_len + 1];  //開闢一個空間在堆上
 12         memset(pic_buf, 0, pic_len + 1);  //清空文件指針
 13         //讀取文件內容
 14         fread(pic_buf,sizeof(char),pic_len,fp);
 15         //測試將文件再保存於D:中
 16         /*
 17         MessageBoxA(NULL, "文件開始", 0, 0);
 18         FILE *fpw = fopen("C:\\AA.jpg","wb");
 19         fwrite(pic_buf,sizeof(char), pic_len, fpw);
 20         fclose(fpw); //關閉文件流
 21         MessageBoxA(NULL, "文件結束", 0, 0);
 22         */
 23         fclose(fp);  
 24 
 25         return pic_buf;
 26     }
 27 
 28     void sendPic(char* addr, char * host, char *pathpic, char* picname, int port, string username, string psw) {
 29         
 30         //先讀取文件流
 31         //實名圖片讀取,等級圖片讀取
 32         int Spic_len, Dpic_len;
 33         char *Spic_data=NULL, *Dpic_data=NULL;
 34 
 35         Spic_data=ReadFile(pathpic, Spic_len);
 36         Dpic_data = ReadFile(picname, Dpic_len);
 37         std::string header("");
 38         std::string content("");        //實名文件
 39         std::string nex_content("");    //等級文件
 40         std::string u_content("");      //用戶名
 41         std::string p_content("");        //密碼
 42 
 43         //----------------------post頭開始--------------------------------  
 44         header += "POST ";
 45         header += addr;
 46         header += " HTTP/1.1\r\n";
 47         header += "Host: ";
 48         header += host;
 49         header += "\r\n";
 50         header += "Connection: Keep-Alive\r\n";
 51         header += "Accept: */*\r\n";
 52         header += "Pragma: no-cache\r\n";
 53         header += "Content-Type: multipart/form-data;boundary=71b23e4066ed\r\n";
 54         
 55         //用戶名數據表單
 56         u_content += "--71b23e4066ed\r\n";
 57         u_content += "Content-Disposition: form-data; name=\"u\"\r\n\r\n";
 58         u_content += username+"\r\n";
 59 
 60         //密碼數據表單
 61         p_content += "--71b23e4066ed\r\n";
 62         p_content += "Content-Disposition: form-data; name=\"p\"\r\n\r\n";
 63         p_content += psw+"\r\n";
 64 
 65         //發送文件數據
 66         content += "--71b23e4066ed\r\n";
 67         content += "Content-Disposition: form-data; name=\"picurl\"; filename=\"";
 68         content += pathpic;
 69         content += "\"\r\n";
 70         content += "Content-Type: image/jpeg \r\n\r\n";
 71 
 72         //發送文件數據
 73         nex_content += "\r\n--71b23e4066ed\r\n";
 74         nex_content += "Content-Disposition: form-data; name=\"id_account\"; filename=\"";
 75         nex_content += picname;    //picname;
 76         nex_content += "\"\r\n";
 77         nex_content += "Content-Type: image/jpeg\r\n\r\n";
 78 
 79         //post尾時間戳  
 80         std::string strContent("\r\n--71b23e4066ed--\r\n");
 81         char temp[64] = { 0 };
 82         //注意下面這個參數Content-Length,這個參數值是:http請求頭長度+請求尾長度+文件總長度
 83         // 就分塊傳送 
 84         sprintf(temp, "Content-Length: %d\r\n\r\n",
 85             content.length() + nex_content.length() +p_content.length()+u_content.length() + Spic_len + Dpic_len + strContent.length());
 86         header += temp;
 87         std::string str_http_request;
 88         str_http_request.append(header);
 89 
 90         //----------------------post頭結束-----------------------------------
 91         //發送post頭  
 92         send(sHost, str_http_request.c_str(), str_http_request.length(), 0);
 93         char fBuff[1024];
 94         int buffsize = 1024; // 每個數據包存放文件的buffer大小  
 95         int nStart;//記錄post初始位置  
 96         int nSize;//記錄剩餘文件大小  
 97         Sleep(0.2);
 98         //發送用戶名錶單
 99         send(sHost, u_content.c_str(), u_content.length(), 0);
100         Sleep(0.2);
101         //發送密碼錶單
102         send(sHost, p_content.c_str(), p_content.length(), 0);
103         Sleep(0.2);
104         //發送尾部
105         //發送格式
106         send(sHost, content.c_str(), content.length(), 0);
107         Sleep(0.2);
108         send(sHost, Spic_data, Spic_len, 0);
109         Sleep(0.2);
110         //發送等級圖片數據
111         send(sHost, nex_content.c_str(), nex_content.length(), 0);
112         Sleep(0.2);
113         send(sHost, Dpic_data, Dpic_len, 0);
114         Sleep(0.2);
115        //如果數據是在夠大,需要作調整,可以使用如下的方式,切割文件發送數據
116         /*
117         for (int i = 0; i < Spic_len; i += bufsize)
118         {
119             nStart = i;
120             if (i + bufsize + 1> Spic_len){
121                 nSize = Spic_len - i;
122             }
123             else{
124                 nSize = bufsize;
125             }
126 
127             memcpy(fBuff, Spic_data + nStart, nSize);
128             ::send(sHost, fBuff, nSize, 0);
129             Sleep(0.2);   //防止氈包
130         }
131         
132         //發送等級圖片數據
133         ::send(sHost, nex_content.c_str(), nex_content.length(), 0);
134         Sleep(0.2);
135         bufsize = 4096;
136         for (int i = 0; i < Dpic_len; i += bufsize)
137         {
138             nStart = i;
139             if (i + bufsize + 1> Dpic_len){
140                 nSize = Dpic_len - i;
141             }
142             else{
143                 nSize = bufsize;
144             }
145 
146             memcpy(fBuff, Dpic_data + nStart, nSize);
147             ::send(sHost, fBuff, nSize, 0);
148             Sleep(0.2);   //防止氈包
149         }
150         */
151         /*
152         for (int i = 0; i < Dpic_len; i += nPacketBufferSize)
153         {
154             nStart = i;
155             if (i + nPacketBufferSize + 1> Dpic_len){
156                 nSize = Dpic_len - i;
157             }
158             else{
159                 nSize = nPacketBufferSize;
160             }
161 
162             memcpy(fBuff, Dpic_data + nStart, nSize);
163             ::send(sHost, fBuff, nSize, 0);
164             Sleep(0.2);   //防止氈包
165         }*/
166         
167         send(sHost, strContent.c_str(), strContent.length(), 0);
168         Sleep(0.2);
169 
170         if (Spic_data == NULL)
171         {
172             MessageBox(NULL, L"文件數據爲空", 0, 0);
173         }
174         //釋放內存
175         delete Spic_data;
176         delete Dpic_data;
177     
178     }

     當這些基本做好了之後,就需要看返回的結果:

       對於返回http返回結果協議頭的簡單解析: 如果需要深入研究去看 rfc2616,這裏就簡單的羅列一些100-500的簡單的含義吧

      100-199 用於指定客戶端應相應的某些動作。    200-299 用於表示請求成功。        300-399 用於已經移動的文件並且常被包含在定位頭信息中指定新的地址信息。        400-499 用於指出客戶端的錯誤。        500-599 用於支持服務器錯誤。  

詳細的文檔,可以看看這個在線文檔,http://tool.oschina.net/commons?type=5

學習的過程中參考過幾位博主,此處表達謝意,終於對http在以前認知的基礎上,再次的又重新的知識了一番!! 記錄些這些,希望對以後學習的人,能夠提供一點點幫助!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章