在程序設計中,涉及數據存儲和數據交換的時候,不論是B/S仍是C/S模式,都有這樣一個概念:數據庫服務器。這要求一臺性能和配置都比較好的主機做爲服務器,以知足數目衆多的客戶端進行頻繁訪問。可是對於一些數據交換的要求不主同,並且涉及到的通信個體數目很少,若是還採用「一主機多客戶機」的模式,便要求一臺硬件配置良好並且軟件上安裝了相關數據服務軟件,這樣會形成硬件和軟件上的不少沒必要要的成本,這時Socket在點對點的平行對象之間的網絡通信的優點就就發揮出來了。html
其實對於Socket通信來講,服務器和客戶端的界定不像數據庫服務器與客戶端那樣明顯,甚至能夠說Socket通信裏面的服務器和客戶端只是相對的,由於網絡通信的對象基本上是處於平等層面的,只是爲了方便對兩臺聯網通信的主機的描述才這樣定義稱謂的。數據庫
因爲在.NET中Socket通信的創建很容易,因此本文主要介紹一個Socket的比較典型的應用的流程:客戶端向服務器發送圖片請求,圖片服務器接收到請求,並將服務器硬盤上的圖片編碼,發送到客戶端,客戶端獲得圖片數據後,再將這些數據寫成圖片文件,保存在客戶端上。數組
本文主要是對Socket的一個應用進行介紹,因此至於其原理在此沒有深究,至於如何創建Socket還有如何實現網絡的七層協議在此都沒有進行相關研究和介紹,本文主要介紹如何實現一個用戶想要的功能,即在兩臺主機之間進行通信,經過網絡來收發用戶想要收發的數據。安全
2、通信相關的代碼服務器
本文以Windows控制檯程序爲例來實現引功能。網絡
不論是通信服務器或者通信客戶端,本文均以一個不斷運行的線程來實現對端口的偵聽,將通信相關的變量的函數作成一個類,在Program.cs中只負責初始化一些參數,而後創建通信的線程。具體代碼以下:socket
2.1服務器端ide
Program.cs:函數
using System; using System.Net; using System.Net.Sockets; using System.Threading; namespace ConsoleSocketsDemo { class Program { static void Main(string[] args) { int sendPicPort = 600;//發送圖片的端口 int recvCmdPort = 400;//接收請求的端口開啓後就一直進行偵聽 SocketServer socketServerProcess = new SocketServer(recvCmdPort, sendPicPort); Thread tSocketServer = new Thread(new ThreadStart(socketServerProcess.thread));//線程開始的時候要調用的方法爲threadProc.thread tSocketServer.IsBackground = true;//設置IsBackground=true,後臺線程會自動根據主線程的銷燬而銷燬 tSocketServer.Start(); Console.ReadKey();//直接main裏邊最後加個Console.Read()不就行了。要按鍵才退出。 } } }
SocketServer.cs:性能
using System; using System.Text; using System.Net; using System.Net.Sockets; using System.IO; namespace ConsoleSocketsDemo { class SocketServer { Socket sRecvCmd; int recvCmdPort;//接收圖片請求命令 int sendPicPort;//發送圖片命令 public SocketServer(int recvPort,int sendPort) { recvCmdPort = recvPort; sendPicPort = sendPort; //創建本地socket,一直對4000端口進行偵聽 IPEndPoint recvCmdLocalEndPoint = new IPEndPoint(IPAddress.Any, recvCmdPort); sRecvCmd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sRecvCmd.Bind(recvCmdLocalEndPoint); sRecvCmd.Listen(100); } public void thread() { while (true) { System.Threading.Thread.Sleep(1);//每一個線程內部的死循環裏面都要加個「短期」睡眠,使得線程佔用資源獲得及時釋放 try { Socket sRecvCmdTemp = sRecvCmd.Accept();//Accept 以同步方式從偵聽套接字的鏈接請求隊列中提取第一個掛起的鏈接請求,而後建立並返回新的 Socket sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);//設置接收數據超時 sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//設置發送數據超時 sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024); //設置發送緩衝區大小 1K sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024);//設置接收緩衝區大小1K byte[] recvBytes = new byte[1024];//開啓一個緩衝區,存儲接收到的信息 sRecvCmdTemp.Receive(recvBytes); //將讀得的內容放在recvBytes中 string strRecvCmd = Encoding.Default.GetString(recvBytes);// //程序運行到這個地方,已經能接收到遠程發過來的命令了 //************* //解碼命令,並執行相應的操做----以下面的發送本機圖片 //************* string[] strArray = strRecvCmd.Split(';'); if (strArray[0] == "PicRequest") { string[] strRemoteEndPoint =sRecvCmdTemp.RemoteEndPoint.ToString().Split(':');//遠處終端的請求端IP和端口,如:127.0.0.1:4000 string strRemoteIP = strRemoteEndPoint[0]; SentPictures(strRemoteIP, sendPicPort); //發送本機圖片文件 recvBytes = null; } } catch(Exception ex) { Console.Write(ex.Message); } } } /// <summary> /// 向遠程客戶端發送圖片 /// </summary> /// <param name="strRemoteIP">遠程客戶端IP</param> /// <param name="sendPort">發送圖片的端口</param> private static void SentPictures(string strRemoteIP, int sendPort) { string path = "D:\\images\\"; string strImageTag = "image";//圖片名稱中包含有image的全部圖片文件 try { string[] picFiles = Directory.GetFiles(path, strImageTag + "*", SearchOption.TopDirectoryOnly);//知足要求的文件個數 if (picFiles.Length == 0) { return;//沒有圖片,不作處理 } long sendBytesTotalCounts = 0;//發送數據流總長度 //消息頭部:命令標識+文件數目+……文件i長度+ string strMsgHead = "PicResponse;" + picFiles.Length + ";"; //消息體:圖片文件流 byte[][] msgPicBytes = new byte[picFiles.Length][]; for (int j = 0; j < picFiles.Length; j++) { FileStream fs = new FileStream(picFiles[j].ToString(), FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(fs); msgPicBytes[j] = new byte[fs.Length]; strMsgHead += fs.Length.ToString() + ";"; sendBytesTotalCounts += fs.Length; reader.Read(msgPicBytes[j], 0, msgPicBytes[j].Length); } byte[] msgHeadBytes = Encoding.Default.GetBytes(strMsgHead);//將消息頭字符串轉成byte數組 sendBytesTotalCounts += msgHeadBytes.Length; //要發送的數據流:數據頭+數據體 byte[] sendMsgBytes = new byte[sendBytesTotalCounts];//要發送的總數組 for (int i = 0; i < msgHeadBytes.Length; i++) { sendMsgBytes[i] = msgHeadBytes[i]; //數據頭 } int index = msgHeadBytes.Length; for (int i = 0; i < picFiles.Length; i++) { for (int j = 0; j < msgPicBytes[i].Length; j++) { sendMsgBytes[index + j] = msgPicBytes[i][j]; } index += msgPicBytes[i].Length; } //程序執行到此處,帶有圖片信息的報文已經準備好了 //PicResponse;2;94223;69228; //+圖片1比特流+……圖片2比特流 try { #region 發送圖片 Socket sSendPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ipAddress = IPAddress.Parse(strRemoteIP);//remoteip = "127.0.0.1" try { sSendPic.Connect(ipAddress, sendPort);//鏈接無故客戶端主機 sSendPic.Send(sendMsgBytes, sendMsgBytes.Length, 0);//發送本地圖片 } catch (System.Exception e) { System.Console.Write("SentPictures函數在創建遠程鏈接時出現異常:" +e.Message); }finally { sSendPic.Close(); } #endregion } catch { } } catch(Exception ex) { Console.Write(ex.Message); } } } }
2.2客戶端端
Program.cs:
using System; using System.Text; using System.Net; using System.Net.Sockets; using System.IO; namespace ConsoleClientSocketDemo { class RecvPic { Socket sRecvPic;//接收圖片的socket int recvPicPort;//接收圖片端口 public RecvPic(int recvPort) { recvPicPort = recvPort; IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, recvPicPort); sRecvPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sRecvPic.Bind(localEndPoint); sRecvPic.Listen(100); } public void thread() { while (true) { System.Threading.Thread.Sleep(1);//每一個線程內部的死循環裏面都要加個「短期」睡眠,使得線程佔用資源獲得及時釋放 try { Socket sRecvPicTemp = sRecvPic.Accept();//一直在等待socket請求,並創建一個和請求相同的socket,覆蓋掉原來的socket sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000); //設置接收數據超時 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//設置發送數據超時 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024);//設置發送緩衝區大小--1K大小 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024); //設置接收緩衝區大小 #region 先取出數據頭部信息---並解析頭部 byte[] recvHeadBytes = new byte[1024];//先取1K的數據,提取出數據的頭部 sRecvPicTemp.Receive(recvHeadBytes, recvHeadBytes.Length, 0); string recvStr = Encoding.UTF8.GetString(recvHeadBytes); string[] strHeadArray = recvStr.Split(';');//PicResponse;2;94223;69228; string strHeadCmd = strHeadArray[0];//頭部命令 int picCounts = Convert.ToInt32(strHeadArray[1]) ;//數據流中包含的圖片個數 int[] picLength=new int[picCounts];//每一個圖片的長度 for (int i = 0; i < picCounts;i++ ) { picLength[i] = Convert.ToInt32(strHeadArray[i+2]); } #endregion int offset=0;//數據頭的長度 for (int k = 0; k < strHeadArray.Length - 1;k++ ) { offset += strHeadArray[k].Length + 1;//由於後面的分號 } int picOffset = recvHeadBytes.Length - offset;//第一張圖片在提取數據頭的時候已經被提取了一部分了 if (strHeadCmd == "PicResponse") { #region 儲存圖片--爲了節約內存,能夠每接收一次就保存一次圖片 for (int i = 0; i < picCounts; i++) { byte[] recvPicBytes = new byte[(picLength[i])];//每次只接收一張圖片 if (i == 0)//第一幅圖片有一部分在提取數據頭的時候已經提取過了。 { byte[] recvFirstPicBuffer = new byte[picLength[i] -picOffset]; sRecvPicTemp.Receive(recvFirstPicBuffer, recvFirstPicBuffer.Length, 0); for (int j = 0; j < picOffset; j++) { recvPicBytes[j] = recvHeadBytes[offset + j];//第一幅圖片的前一部分 } for (int j = 0; j < recvFirstPicBuffer.Length; j++)//第一張圖片的後半部分 { recvPicBytes[picOffset + j] = recvFirstPicBuffer[j]; } //將圖片寫入文件 SavePicture(recvPicBytes, "-0"); } else { sRecvPicTemp.Receive(recvPicBytes, recvPicBytes.Length, 0);//每次取一張圖片的長度 SavePicture(recvPicBytes, "-"+i.ToString()); //將圖片數據寫入文件 } } #endregion } } catch(Exception ex) { Console.Write(ex.Message); } finally { } } } /// <summary> /// 保存圖片到指定路徑 /// </summary> /// <param name="picBytes">圖片比特流</param> /// <param name="picNum">圖片編號</param> public void SavePicture(byte[] picBytes, string picNum) { string filename = "receivePic"; if (!Directory.Exists("E:\\images\\")) Directory.CreateDirectory("E:\\images\\"); if (File.Exists("E:\\images\\" + filename + picNum + ".jpg")) return; FileStream fs = new FileStream("E:\\images\\" + filename + picNum + ".jpg", FileMode.OpenOrCreate, FileAccess.Write); fs.Write(picBytes, 0, picBytes.Length); fs.Dispose(); fs.Close(); } } }
3、測試socket的鏈接方法,telnet遠程登陸
用戶能夠同時對客戶端和服務器端的Socket程序進行編寫,而後進行聯調,也能夠一次只編寫一個,而後經過下面的方法來測試Socket鏈接。
通常經過遠程登陸來測試鏈接是否成功,好比測試本機的400端口是否能鏈接成功:
「運行->cmd->telnet 127.0.0.1 400」
在沒有運行對本機的400端口進行不斷偵聽的程序時,會出現鏈接失敗的提示:
Socket sRecvCmdTemp = sRecvCmd.Accept();
以後的語句上斷點。
--------------------------------------------------------------------------------------------------
http://shihuan830619.iteye.com/blog/1113837 (我JavaEye的博客, 有附件)
附近演示程序的說明:
1.使用VS2005建立。
2.主要實現的功能是:主機A向主機B發圖片請求,主機B將D盤image目錄下的image0.jpg,image1.jpg文件編碼發送到主機B,主機B再解碼並寫成圖片文件到E盤的image目錄下。
3.爲了方便調試,演示程序將服務器和客戶端同時放在本機上,即localhost或者127.0.0.1,即本程序最終實現的效果就是將本機的D盤image目錄下的兩個指定名稱的圖片傳送到E盤image目錄下。因此在運行本程序前,先在D:/image目錄下放置兩張命名爲image0.jpg,image1.jpg的圖片文件
4.先運行服務器程序,再運行客戶端程序
特別聲明:目前,對於傳輸和圖片數據的報文格式存在必定的小問題,由於目前是用的分號「;」做爲分隔符,因此在當圖片數據流中存在和分號的ASCII碼值相同的數時,在客戶端解碼是便會出現問題,比較穩妥的方法是嚴格限定死數據頭報文的長度(寧肯多花幾位爲空均可以,但要有嚴格的編碼格式),而後在解碼的時候,按照位來解碼,而不是按照分號的分隔符來解碼。因此應用Byte數組來進行編碼,而不該該是string字符串,用string字符串的話會出現不少問題的:好比,遇到空字符串就認爲是結尾了,遇到「;」就表示是編碼分隔符號,而這些字符都是有可能在圖片數據中存在的,因此用sting字符串會有安全隱患的。
出處:http://blog.sina.com.cn/s/blog_4f925fc3010186mf.html