(源码下载地址,http://download.youkuaiyun.com/source/406996)
在这个实例中,客房端和服务端的数据交换,有许多类型,比如请求文件列表的、请求文件大小的。命令的传输,实际上是将字符串以流的形式写入NetWorkStream;而命令字符串的组成,类似于这样的格式:
001|参数1|参数2|参数3
命令字符串以代表命令类型的代码开始,后面加上所需要的参数,中间以“|”分隔。
为了方便程序的可读性,需要定义一个枚举,来记录命令的类型。打开vs2005,新建一个类库项目TConst,再将解决方案重命名为TcpTest。在TConst类库项目中添加一个枚举CommandStyleEnum:
public
enum
CommandStyleEnum

...
{
cNone = 0,
cList = 1,//请示文件列表
cListReturn = 2,//文件列表返回
cGetFileLength = 3,//请示文件长度
cGetFileLengthReturn = 4,//返回文件长度
cGetFileLengthReturnNone = 5,//返回文件长度失败
cGetFile = 6,//请求文件
cGetFileReturn = 7,//请求文件返回
cGetFileReturnNone = 8,//请示文件返回失败
}
再添加一个类TCommand,这个类用于命令字符串、字节数组的相互转换(字节数组在NetWorkStream.Write中用到):
public
class
TCommand

...
{
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中获取服务器地址和端口:
private
void
Form1_Load(
object
sender, EventArgs e)

...
{
_sysConfig = new TSysConfig();
_port = _sysConfig.GetIniInt("ServerPort", 9999);
_server = _sysConfig.GetIniString("ServerAddress", "ie");
}
然后,在“获取文件列表”按钮的Click事件中,增加如下的代码:
private
void
button1_Click(
object
sender, EventArgs e)

...
{
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的构造:
public
TclientConnection(TcpClient tcp,ListBox listBox)

...
{
_tcp = tcp;
_listBox = listBox;
}
再看GetFileList()的实现:
private
delegate
void
dd();
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事件中,读取端口号,并启动监听线程:
private
void
Form1_Load(
object
sender, EventArgs e)

...
{
_sysConfig = new TSysConfig();
_port = _sysConfig.GetIniInt("Port", 9999);

Thread t = new Thread(new ThreadStart(WaitForConnect));
t.IsBackground = true;
t.Start();
}

监听线程实际上执行的是类函数WaitForConnect中的代码:
private
void
WaitForConnect()

...
{
TListener lis = new TListener(_port);
lis.StartListening();
}
WaitForConnect生成了类TListener的对象,并调用TListener.StartListening()。(在这里,函数WaitForConnect是不必要的,线程的构造参数中直接传递TListener.StartListening即可。)
看TListener.StartListening()的代码:
public
void
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()的代码:
public
void
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)函数:
private
void
ExtractRecStr(TCommand command, NetworkStream 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:
private
void
OnGetFileList(NetworkStream stream)

...
{
TCommand command = GetData.GetFileListCommand();
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);
}
OnGetFileList通过类GetData中的GetFileListCommand返回了一个TCommand对象,并从它得到字节数组,写入NetWorkStream。
GetData类在项目TConst中,它包含一些用于返回数据的函数,看GetFileListCommand函数的代码:
private
static
string
[] GetFileList(
string
FilePath)

...
{
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