C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)
C#.NET通過Socket實現平行主機之間網絡通訊(含圖片傳輸的Demo演示)
作者:一點一滴的Beer http://beer.cnblogs.com/
????? 在程序設計中,涉及數據存儲和數據交換的時候,不管是B/S還是C/S模式 ,都有這樣一個概念:數據庫服務器。這要求一臺性能和配置都比較好的主機作為服務器,以滿足數目眾多的客戶端進行頻繁訪問。但是對于一些數據交換的要求不主同,而且涉及到的通訊個體數目不多,如果還采用“一主機多客戶機”的模式,便要求一臺硬件配置良好而且軟件上安裝了相關數據服務軟件,這樣會造成硬件和軟件上的很多不必要的成本,這時Socket在點對點的平行對象之間的網絡通訊的優勢就就發揮出來了。
???? 其實對于Socket通訊來說,服務器和客戶端的界定不像數據庫服務器與客戶端那樣明顯,甚至可以說Socket通訊里面的服務器和客戶端只是相對的,因為網絡通訊的對象基本上是處于平等層面的,只是為了方便對兩臺聯網通訊的主機的描述才這樣定義稱謂的。
???? 由于在.NET中Socket通訊的建立很容易,所以本文主要介紹一個Socket的比較典型的應用的流程:客戶端向服務器發送圖片請求,圖片服務器接收到請求,并將服務器硬盤上的圖片編碼,發送到客戶端,客戶端得到圖片數據后,再將這些數據寫成圖片文件,保存在客戶端上。
???? 本文主要是對Socket的一個應用進行介紹,所以至于其原理在此沒有深究,至于如何建立Socket還有如何實現網絡的七層協議在此都沒有進行相關研究和介紹,本文主要介紹如何實現一個用戶想要的功能,即在兩臺主機之間進行通訊,通過網絡來收發用戶想要收發的數據。
?
一、通訊流程圖
?
二、通訊相關的代碼
????? 本文以Windows控制臺程序為例來實現引功能。
???? 不管是通訊服務器或者通訊客戶端,本文均以一個不斷運行的線程來實現對端口的偵聽,將通訊相關的變量的函數做成一個類,在Program.cs中只負責初始化一些參數,然后建立通訊的線程。具體代碼如下:
?
2.1服務器端
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.Threading;
namespace ConsoleClientSocketDemo
{
class Program
{
static void Main(string[] args)
{
int recvPort = 600;//客戶端一直對600端口進行偵聽---接收圖片的端口
RecvPic recvPic = new RecvPic(recvPort);//監聽接收來自圖片服務器的圖片以及客戶端的命令
Thread tRecvPic = new Thread(new ThreadStart(recvPic.thread));
tRecvPic.IsBackground = true;
tRecvPic.Start();
string strPicServerIP = "127.0.0.1";//圖片服務器的IP----127.0.0.1(localhost)--以本機為例
int sendRequestPort = 400;//發送圖片請求的端口
SendStrMsg(strPicServerIP, sendRequestPort);
Console.ReadKey();//直接main里邊最后加個Console.Read()不就好了。要按鍵才退出。
}
/// <summary>
/// 向目標主機發送字符串 請求圖片
/// </summary>
/// <param name="strPicServerIP">目標圖片服務器IP</param>
/// <param name="sendRequestPort">目標圖片服務器接收請求的端口</param>
private static void SendStrMsg(string strPicServerIP, int sendRequestPort)
{
//可以在字符串編碼上做文章,可以傳送各種信息內容,目前主要有三種編碼方式:
//1.自定義連接字符串編碼--微量
//2.JSON編碼--輕量
//3.XML編碼--重量
string strPicRequest = "PicRequest;Hello world,need some pictures~!";//圖片請求
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(strPicServerIP.ToString()), sendRequestPort);
Socket answerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
answerSocket.Connect(ipEndPoint);//建立Socket連接
byte[] sendContents = Encoding.UTF8.GetBytes(strPicRequest);
answerSocket.Send(sendContents, sendContents.Length, 0);//發送二進制數據
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
finally
{
answerSocket.Close();
}
}
}
}
?
?
RecvPic.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();
}
}
}
?
?
三、測試socket的連接方法,telnet遠程登錄
????? 用戶可以同時對客戶端和服務器端的Socket程序進行編寫,然后進行聯調,也可以一次只編寫一個,然后通過下面的方法來測試Socket連接。
一般通過遠程登錄來測試連接是否成功,比如測試本機的400端口是否能連接成功:
“運行->cmd->telnet 127.0.0.1 400”
??? 在沒有運行對本機的400端口進行不斷偵聽的程序時,會出現連接失敗的提示:
??? 如果連接成功,則會彈出另外一個窗口:
??? 如果在偵聽線程里面設置斷點,通常連接成功后,就會在
??? Socket sRecvCmdTemp = sRecvCmd.Accept();
之后的語句上斷點。
?
附件:SocketDemo.rar
?
附近演示程序的說明:
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字符串會有安全隱患的。
------------------------------------------------------------------
Author:一點一滴的Beer
Email /Gtalk:dreamzsm@gmail.com
From:http://www.cnblogs.com/beer
Notes:歡迎轉貼,但請在頁面中加個鏈接注明出處
Time:2010-8-17
總結
以上是生活随笔為你收集整理的C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Silverlight数据加载时,等待图
- 下一篇: 在不重装XP系统,增加系统盘剩余空间