??各位朋友大家好,我是秦元培,歡迎大家關注我的博客,我的博客地址是http://qinyuanpei.com。在我們這個Web服務器有了1個基本的門面以后,我們是時候來用它做點實際的事情了。還記得我們最早提到HTTP協議的用處是甚么嗎?它叫超文本傳輸協議啊,所以我們必須斟酌讓我們的服務器能夠接收到客戶端傳來的數據。由于我們目前完成了大部份的工作,所以對數據傳輸這個問題我們這里選擇以最簡單的GET和POST為例來實現,這樣我們今天的重點就落實在Get和Post的實現這個問題上來。而從原理上來說,不管Get方式要求還是Post方式要求,我們都可以在要求報文中取得其要求參數,不同的是前者出現在要求行中,而后者出現在消息體中。例如我們傳遞的兩個參數num1和num2對應的數值分別是12和24,那末在具體的要求報文中我們都能找到類似“num1=12&num2=24”這樣的字符結構,所以只要針對這個字符結構進行解析,就能夠取得客戶端傳遞給服務器的參數啦。
??首先我們來實現Get要求,Get是HTTP協議中默許的要求類型,我們平時訪問網頁、要求資源實際上都是通過Get方式實現的。Get方式要求需要通過類似“?id=001&option=10”這樣的情勢附加在URL上,因此Get方式對閱讀器來講是透明的,即用戶可以通過閱讀器地址欄知道,這個進程中傳遞了哪些參數和這些參數的值分別是甚么。而由于閱讀器的限制,我們通過這類方式要求的時候能夠傳遞的參數數目和長度都是有限的,而且當參數中存在中文數值的時候還需要對其進行編碼。Get方式要求相對簡單,我們下面來看看它的要求報文:
GET /?num1=23&num2=12 HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
Accept-Encoding: gzip, deflate
Host: localhost:4040
Connection: Keep-Alive
Cookie: _ga=GA1.1.1181222800.1463541781
此時我們可以注意到在要求報文第1行,即要求行中出現了“/?num1=23&num2=12”這樣的字樣,這就是客戶端傳遞給服務器的參數,我們很容易想到只需要將這個字段串中的“鍵”和“值”都解析出來,服務器就能夠對這些數據進行處理然后返回給客戶端了。所以下面我們通過這樣的方式來實現,我們為HtttpRequest類增加了1個Parms屬性,它是1個鍵和值均為字符串類型的字典,我們使用這個字典來存儲和管理客戶端傳遞來的參數。
//獲得要求參數
if(this.Method == "GET" && this.URL.Contains('?'))
this.Params = GetRequestParams(lines[0].Split(' ')[1].Split('?')[1]);
明顯我們首先需要判斷要求類型是不是為GET和要求中是不是帶有參數,其方法是判斷要求地址中是不是含有“?”字符。這里的lines是指將報文信息按行分割以后的數組,明顯要求地址在第1行,所以我們根據“?”分割該行數據以后就能夠得到“num1=23&num2=12”這樣的結果,這里我們使用1個方法GetRequestParms來返回參數字典,這樣作做是為了復用方法,由于在處理Post要求的時候我們會繼續使用這個方法。該方法定義以下:
/// <summary>
/// 從內容中解析要求參數并返回1個字典
/// </summary>
/// <param name="content">使用&連接的參數字符串</param>
/// <returns>如果存在參數則返回參數否則返回null</returns>
protected Dictionary<string, string> GetRequestParams(string content)
{
//防御編程
if(string.IsNullOrEmpty(content))
return null;
//依照&對字符進行分割
string[] reval = content.Split('&');
if(reval.Length <= 0)
return null;
//將結果添加至字典
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach(string val in reval)
{
string[] kv = val.Split('=');
if(kv.Length <= 1)
dict.Add(kv[0], "");
dict.Add(kv[0],kv[1]);
}
//返回字典
return dict;
}
??Post要求相對Get要求比較安全,由于它克服了Get要求參數長度的限制問題,而且由于它的參數是寄存在消息體中的,所以在傳遞參數的時候對用戶而言是不可見的,我們平時接觸到的網站登錄都是這類類型,而復雜點的網站會通過驗證碼、Cookie等情勢來避免爬蟲程序摹擬登錄,在Web開發中Post要求可以由1個表單發起,可以由爬蟲程序如HttpWebRequest、WebClient等發起,下面我們重點來分析它的要求報文:
POST / HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
Accept-Encoding: gzip, deflate
Host: localhost:4040
Connection: Keep-Alive
Cookie: _ga=GA1.1.1181222800.1463541781
num1=23&num2=12
我們可以注意到此時要求行的要求方法變成了POST,而在報文結尾增加了1行內容,我們稱其為“消息體”,這是1個可選的內容,請注意它前面有1個空行。所以,當我們處理1個Posst要求的時候,通過最后1行就能夠解析出客戶端傳遞過來的參數,和Get要求相同,我們這里繼續使用GetRequestParams來完成解析。
if(this.Method == "POST")
this.Params = GetRequestParams(lines[lines.Length-1]);
??現在我們來完成1個簡單地實例,服務器自然由我們這里設計的這個服務器來完成咯,而客戶端則由Unity來完成由于Unity有簡單的WWW可使用。首先來編寫服務端,這個繼承HttpServer就行了,我們主要來寫這里的方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HttpServerLib;
using System.IO;
namespace HttpServer
{
public class ExampleServer : HttpServerLib.HttpServer
{
/// <summary>
/// 構造函數
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <param name="port">端口號</param>
public ExampleServer(string ipAddress, int port)
: base(ipAddress, port)
{
}
public override void OnPost(HttpRequest request)
{
//獲得客戶端傳遞的參數
int num1 = int.Parse(request.Params["num1"]);
int num2 = int.Parse(request.Params["num2"]);
//設置返回信息
string content = string.Format("這是通過Post方式返回的數據:num1={0},num2={1}",num1,num2);
//構造響應報文
HttpResponse response = new HttpResponse(content, Encoding.UTF8);
response.StatusCode = "200";
response.Content_Type = "text/html; charset=UTF⑻";
response.Server = "ExampleServer";
//發送響應
ProcessResponse(request.Handler, response);
}
public override void OnGet(HttpRequest request)
{
//獲得客戶端傳遞的參數
int num1 = int.Parse(request.Params["num1"]);
int num2 = int.Parse(request.Params["num2"]);
//設置返回信息
string content = string.Format("這是通過Get方式返回的數據:num1={0},num2={1}",num1,num2);
//構造響應報文
HttpResponse response = new HttpResponse(content, Encoding.UTF8);
response.StatusCode = "200";
response.Content_Type = "text/html; charset=UTF⑻";
response.Server = "ExampleServer";
//發送響應
ProcessResponse(request.Handler, response);
}
}
}
由于這里需要對Get和Post進行響應,所以我們這里對OnGet和OnPost兩個方法進行了重寫,這里的處理方式非常簡單,依照1定格式返回數據便可。下面我們來講說Unity作為客戶端這邊要做的工作,這里給出關鍵代碼:
//采取GET方式要求數據
IEnumerator Get()
{
WWW www = new WWW ("http://127.0.0.1:4040/?num1=12&num2=23");
yield return www;
Debug.Log(www.text);
}
//采取POST方式要求數據
IEnumerator Post()
{
WWWForm form = new WWWForm ();
form.AddField ("num1", 12);
form.AddField ("num2", 23);
WWW www = new WWW ("http://127.0.0.1:4040/", form);
yield return www;
Debug.Log (www.text);
}
而運行這個實例,我們可以得到下面的結果:
上一篇 HDU 3572 網絡流
下一篇 怎么才能有寫程序的思路