css
運行環境: vs2013html
框架: .net4.5chrome
上次實驗中已經實現了單線程下的socket的tcp服務器編程
因爲使用瀏覽器並不能直觀的顯示socket之間的交互相應,因此此次實驗咱們先完成客戶端部分的編程再進行服務端編程的完善瀏覽器
一樣的先創建好一個新的項目服務器
客戶端部分須要兩個操做才能成功鏈接網絡
套接字創建部分與服務端一致多線程
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);併發
而後就是進入鏈接部分框架
先創建待鏈接的ip與端口對象
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7777);
s.Connect(ipe);
知道這些後咱們編程實現一下鏈接功能
編碼完成進行一下測試,先啓動咱們的server程序等待鏈接
再打開client程序請求鏈接
此時咱們觀察到兩個程序直接已經經過socket創建通訊
此時再完善客戶端的讀寫操做
對於客戶端而已讀寫操做跟服務端是沒有任何區別的,本質是這個階段並不區分客戶端與服務端,其讀寫是對等的
s.Send(buf, buf.Length, SocketFlags.None);
bytes = s.Receive(buf, buf.Length, SocketFlags.None);
發送與接收
肯定沒問題後咱們進行測試,測試方法同上
上面是服務端下面是客戶端
過程當中客戶端發送接受一次數據,服務端接受發送一次數據
因爲服務器部分並無使用多線程的設置致使該程序不能同時鏈接多個客戶端,這時候咱們須要把服務器的Accept()方法放入線程中,經過while(true)方式循環屢次接受客戶端的鏈接,爲了能更快的處理客戶端的一次鏈接,以及處理長時間鏈接的客戶端,還雖然再產生新的線程去處理接收和發送部分的工做
肯定設計思路後咱們先引入線程的引用部分
using System.Threading; 
對原先的代碼該如何修改呢
首先要修改最大鏈接數
s.Listen(10);//表明最大可提供10個鏈接隊列
而後本來Socket temp = s.Accept();放入線程中處理
這時候就要創建一個線程函數
須要傳入服務器的socket對象,循環接受對象的鏈接,暫時未處理讀寫部分
Thread listen = new Thread(Listen);
listen.Start(s);
主函數中建立線程與帶server對象的啓動線程
註釋掉以前的代碼
而後測試一下是否能多個進程鏈接到該服務器
四個單獨的客戶端均可以鏈接到服務器,因爲服務器並無作接收和發送處理,客戶端都處於阻塞狀態等待數據接收
接下來處理服務端的數據接收與發生
建立一個用於線程的函數,其中只是簡單複製一下前面的發送接收的代碼用於測試
在原來的監聽線程上,建立一個數據的接收與發生線程用於跟客戶端鏈接,實際上客戶端可能須要一個長鏈接屢次處理數據,若是都放入監聽線程中處理會影響下一個客戶端的鏈接
嘗試對三個客戶端同時鏈接,皆成功鏈接,但實際上我手動操做並不能體現客戶端的高併發的效果,這事咱們嘗試修改客戶端線程併發鏈接服務器看起是否能正常使用
在客戶端主線程中
只加入一個循環的線程客戶端建立
而且每建立一個阻塞10ms防止客戶端過多致使內存溢出
線程函數裏面複製原來的客戶端代碼,並加入異常處理防止出錯程序退出
運行程序咱們能夠看到客戶端與服務器快速鏈接
啓動兩個客戶端併發操做會出現大量的服務器拒絕鏈接,由於同一時間服務器僅僅提供指定數量的客戶端鏈接,因此大規模的併發會有部分客戶端沒法成功鏈接而拋出異常
對此咱們完成了客戶端與服務端的併發多線程處理,下面咱們嘗試客戶端長鏈接服務器並提交請求
對於http協議而言有個http請求頭
Connection: Keep-Alive
這個請求頭決定服務器是否提供長鏈接仍是返回數據後立馬斷開鏈接,對於請求多個靜態文件的網頁比較有效的減小鏈接次數,可是也會形成性能的下降,必須設定超時並在那段時間內消耗沒必要要的系統資源
接下來咱們要作一個可以經過瀏覽器訪問的服務器
首先咱們來觀察一次http請求瀏覽器到底給咱們服務器發送了什麼數據呢
經過fiddler觀察到其數據的內容
有多個請求內容,因爲咱們只作一個簡單可用的服務器,暫時忽略一些較爲複雜的擴展處理功能,只針對get方法進行處理
string[] split = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
首先第一步對從客戶端接收到的數據中獲取http頭請求信息,再經過\r\n分割信息
經過斷點跟蹤能夠看到具體的內容,取get中的請求內容再切割
string[] get = split[0].Split(new Char[] { ' '});
能夠拿到具體的網頁請求字符串
這裏頭咱們嘗試假設了幾個不一樣的靜態文件,index首頁文件,css文件,已經兩種不一樣的響應頭
而後在程序中
根據不一樣的請求,提供相應的返回內容,非正常請求返回404錯誤
而後進行超時處理
temp.ReceiveTimeout = 500;
能夠對socket的接收設定超時值500ms,只要接收到數據到下一次接收之間時間差距不超過500ms就能夠不斷開鏈接,對於少靜態文件的網頁可能不明顯,可是對於多靜態文件的網站能夠有效減小重複鏈接次數
超時會進入異常,關閉套接字,其餘異常則捕抓輸出
可是實際使用瀏覽器的過程當中,瀏覽器依然是發起了三次鏈接,一個是請求首頁文件,另外一個的經過首頁文件觸發鏈接請求css文件,最後一個是chrome自身對網站圖標的請求,該請求被忽略返回404
經過chrome咱們能夠看到請求狀況
運行效果等
使用ie是隻請求兩次並關閉兩次鏈接,在請求第二次前第一次的鏈接並無關閉,對此有點疑惑,也許是我理解有所誤差
fiddler也能跟蹤到數據的收發
能夠證實該服務器能夠正確相應簡單的http請求已經多線程實現
最後修改客戶端來模仿ie請求http
 
主要是將請求的字符串修改成標準的http頭
這裏只請求127.0.0.1:7777/的內容
也就是服務器的index文件
因爲服務器並無提供其餘更爲高級的功能這裏就不寫過多的請求頭了
接收部分要重複屢次接收才能徹底讀取全部的服務器返回數據
成功獲取服務器的html文本
也能夠併發獲取文本信息
經過該實驗我掌握了socket編程的一些更高級的用法,經過多線程來實現對客戶端的請求處理,經過從客戶端獲取的請求字符串來反饋信息,同步處理信息等,在異步處理上仍有待學習,對於併發過程當中出現一些錯誤有所不理解,仍有待研究,經過該實驗對網絡編程的理解有所提升。