(源碼下載地址,http://download.csdn.net/source/406996)
在這個實例中,客房端和服務端的數據交換,有許多類型,比如請求文件列表的、請求文件大小的。命令的傳輸,實際上是將字符串以流的形式寫入NetWorkStream;而命令字符串的組成,類似於這樣的格式:
001|參數1|參數2|參數3
命令字符串以代表命令類型的代碼開始,後面加上所需要的參數,中間以“|”分隔。
爲了方便程序的可讀性,需要定義一個枚舉,來記錄命令的類型。打開vs2005,新建一個類庫項目TConst,再將解決方案重命名爲TcpTest。在TConst類庫項目中添加一個枚舉CommandStyleEnum:
{
cNone = 0,
cList = 1,//請示文件列表
cListReturn = 2,//文件列表返回
cGetFileLength = 3,//請示文件長度
cGetFileLengthReturn = 4,//返回文件長度
cGetFileLengthReturnNone = 5,//返回文件長度失敗
cGetFile = 6,//請求文件
cGetFileReturn = 7,//請求文件返回
cGetFileReturnNone = 8,//請示文件返回失敗
}
再添加一個類TCommand,這個類用於命令字符串、字節數組的相互轉換(字節數組在NetWorkStream.Write中用到):
{
private CommandStyleEnum _commandStyle;
//命令類型
public CommandStyleEnum commandStyle
{
get{return _commandStyle;}
}
private ArrayList _argList;
public ArrayList argList
{
get{return _argList;}
}
public void AppendArg(string arg)
{
_argList.Add(arg);
}
public TCommand(CommandStyleEnum style)
{
_commandStyle = style;
_argList = new ArrayList();
}
public TCommand(byte[] bytesCommand, int len)
{
//
}
public byte[] ToBytes()
{
//
}
}
類TCommand有兩種使用方法:
1。通過構造TCommand(CommandStyleEnum style)來生成對象,再通過AppendArg(string arg)來附加參數,最後通過 ToBytes()轉換成字節數組。
2。通過構造TCommand(byte[] bytesCommand, int len)來生成對象,再通過屬性commandStyle、argList來得到命令類型、參數。
具體的實現請看項目源代碼。
下面開始客戶端的設計。
首先要新增一個Windows窗體應用程序,命名爲Client。
Client引用了一個dll文件:SysConfig.dll。這個dll文件提供了系統配置服務,有興趣的可以在我的另一組文章,MyLog3開發日誌裏看到實現方法。
首先,在窗口的Load事件裏,我們從系統配置文件SysConfig.ini中獲取服務器地址和端口:
{
_sysConfig = new TSysConfig();
_port = _sysConfig.GetIniInt("ServerPort", 9999);
_server = _sysConfig.GetIniString("ServerAddress", "ie");
}
然後,在“獲取文件列表”按鈕的Click事件中,增加如下的代碼:
{
try
{
TcpClient tcp = new TcpClient();
tcp.Connect(_server, _port);
TclientConnection con = new TclientConnection(tcp, listBox1);
Thread t = new Thread(new ThreadStart(con.GetFileList));
t.IsBackground = true;
t.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
首先生成一個TcpClient對象並連接到服務端,再啓動一個線程,線程執行的是類TclientConnection中的函數GetFileList(),請看TclientConnection的構造:
{
_tcp = tcp;
_listBox = listBox;
}
再看GetFileList()的實現:
public void GetFileList()
{
if (_tcp == null)
return;
if (_listBox == null)
return;
NetworkStream stream = _tcp.GetStream();
try
{
TCommand command = new TCommand(CommandStyleEnum.cList);
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);
byte[] recData = new byte[9999];
int recLen = stream.Read(recData, 0, recData.Length);
if (recLen == 0)
return;
command = new TCommand(recData, recLen);
if (command.commandStyle != CommandStyleEnum.cListReturn)
return;
dd a = delegate()
{
_listBox.Items.Clear();
for (int i = 0; i < command.argList.Count; i++)
{
_listBox.Items.Add((string)command.argList[i]);
}
};
_listBox.Invoke(a);
}
finally
{
stream.Close();
_tcp.Close();
}
}
函數首先將請求文件列表的命令發送到服務端,然後接收返回數據,再根據返回的數據生成TCommand對象,返回的文件名列表就存儲在TCommand.argList中。(這裏假定服務端收到文件列表命令,就發回一個字符串,這個字符串是這樣的組成形式:命令代碼|文件名1|文件名2|文件名3。。。,而且轉換成Unicode後長度不超過9999,即原始長度不超過9999/2。)
下面看服務端的設計。
首先新建一個Windows窗體應用程序,命名爲Server。
在窗口的Load事件中,讀取端口號,並啓動監聽線程:
{
_sysConfig = new TSysConfig();
_port = _sysConfig.GetIniInt("Port", 9999);
Thread t = new Thread(new ThreadStart(WaitForConnect));
t.IsBackground = true;
t.Start();
}
監聽線程實際上執行的是類函數WaitForConnect中的代碼:
{
TListener lis = new TListener(_port);
lis.StartListening();
}
WaitForConnect生成了類TListener的對象,並調用TListener.StartListening()。(在這裏,函數WaitForConnect是不必要的,線程的構造參數中直接傳遞TListener.StartListening即可。)
看TListener.StartListening()的代碼:
{
try
{
TcpListener tcpl = new TcpListener(IPAddress.Any, _port);//新建一個TcpListener對象
tcpl.Start();
while (true)//開始監聽
{
TcpClient tcp = tcpl.AcceptTcpClient();
TserverConnection con = new TserverConnection(tcp);
Thread t = new Thread(new ThreadStart(con.WaitForSendData));
t.IsBackground = true;
t.Start();
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
它建立了一個TcpListener對象並開始監聽,每當建立連接時,便將連接寫入到類TserverConnection的對象,並啓動新的線程,以執行類TserverConnection中的函數WaitForSendData()。
下面看TserverConnection.WaitForSendData()的代碼:
{
NetworkStream stream = _tcp.GetStream();
try
{
while (true)
{
try
{
byte[] data = new byte[1024];
int recLen = stream.Read(data, 0, 1024);
if (recLen == 0)
break;
TCommand command = new TCommand(data, recLen);
ExtractRecStr(command, stream);
}
catch (Exception)
{
//MessageBox.Show(ex.Message);
break;
}
}
}
finally
{
stream.Close();
_tcp.Close();
}
}
它不斷地從NetWorStream中讀取客戶端發送過來的消息並放入字節數組data中(這裏假定客戶端發送過來的消息長度小於1024),然後以data爲參數生成類TCommand的對象,最後調用ExtractRecStr(command, stream)函數:
{
switch (command.commandStyle)
{
case CommandStyleEnum.cList:
OnGetFileList(stream);
break;
case CommandStyleEnum.cGetFileLength:
//OnGetFileLength(list, stream);
break;
case CommandStyleEnum.cGetFile:
//OnGetFile(list, stream);
break;
default:
break;
}
}
ExtractRecStr函數根據命令的類型,將控制權轉交到相應的控制函數中,這是是用於發送文件列表的函數OnGetFileList:
{
TCommand command = GetData.GetFileListCommand();
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);
}
OnGetFileList通過類GetData中的GetFileListCommand返回了一個TCommand對象,並從它得到字節數組,寫入NetWorkStream。
GetData類在項目TConst中,它包含一些用於返回數據的函數,看GetFileListCommand函數的代碼:
{
string[] files = System.IO.Directory.GetFiles(FilePath);
for (int i = 0; i < files.Length; i++)
{
files[i] = System.IO.Path.GetFileName(files[i]);
}
return files;
}
public static TCommand GetFileListCommand()
{
TSysConfig sysConfig = new TSysConfig();
string path = sysConfig.GetIniString("path", "-1");
if (path == "-1")
{
MessageBox.Show("沒有從系統配置文件中找到目錄");
return null;
}
string[] files = GetFileList(path);
TCommand command = new TCommand(CommandStyleEnum.cListReturn);
foreach (string s in files)
command.AppendArg(s);
return command;
}
GetFileListCommand函數以CommandStyleEnum.cListReturn爲參數生成一個TCommand對象,然後將某個目錄裏的文件名一個個附加到TCommand對象的參數中。(目錄的路徑存放在SysConfig.ini中。)
最終的流程圖像下面這樣:
ie.2008-04-09