//如果之后的长度小于应有的长度,
//说明没有发完整,则应将整条信息,包括元数据,全部缓存
//与下一条数据合并起来再进行处理
temp=input;
//此时程序应该退出,因为需要等待下一条数据到来才能继续处理
}elseif(output.Length>length){
//如果之后的长度大于应有的长度,
//说明消息发完整了,但是有多余的数据
//多余的数据可能是截断消息,也可能是多条完整消息
//截取字符串
output=output.Substring(0,length);
outputList.Add(output);
temp="";
//缩短input的长度
input=input.Substring(startIndex+length);
//递归调用
GetActualString(input,outputList);
}
}else{ //说明“[”,“]”就不完整
temp=input;
}
returnoutputList.ToArray();
}
}
这个方法接收一个满足协议格式要求的输入字符串,然后返回一个数组,这是因为如果出现多次请求合并成一个发送过来的情况,那么就将它们全部返回。
随后简单起见,我在这个类中添加了一个静态的Test()方法和PrintOutput()帮助方法,进行了一个简单的测试,注意我直接输入了length=13,这个是我提前计算好的。
publicstaticvoidTest(){
RequestHandlerhandler=newRequestHandler();
stringinput;
//第一种情况测试-一条消息完整发送
input="[length=13]明天中秋,祝大家节日快乐!
";
handler.PrintOutput(input);
//第二种情况测试-两条完整消息一次发送
input="明天中秋,祝大家节日快乐!
";
input=String.Format
("[length=13]{0}[length=13]{0}",input);
handler.PrintOutput(input);
//第三种情况测试A-两条消息不完整发送
input="[length=13]明天中秋,祝大家节日快乐!
[length=13]明天中秋";
handler.PrintOutput(input);
input=",祝大家节日快乐!
";
handler.PrintOutput(input);
//第三种情况测试B-两条消息不完整发送
input="[length=13]明天中秋,祝大家";
handler.PrintOutput(input);
input="节日快乐!
[length=13]明天中秋,祝大家节日快乐!
";
handler.PrintOutput(input);
//第四种情况测试-元数据不完整
input="[leng";
handler.PrintOutput(input); //不会有输出
input="th=13]明天中秋,祝大家节日快乐!
";
handler.PrintOutput(input);
}
//用于测试输出
privatevoidPrintOutput(stringinput){
Console.WriteLine(input);
string[]outputArray=GetActualString(input);
foreach(stringoutputinoutputArray){
Console.WriteLine(output);
}
Console.WriteLine();
}
运行上面的程序,可以得到如下的输出:
OK,从上面的输出可以看到,这个方法能够满足我们的要求。
对于这篇文章最开始提出的问题,可以很轻松地通过加入这个方法来解决,这里就不再演示了,但在本文所附带的源代码含有修改过的程序。
在这里花费了很长的时间,接下来让我们回到正题,看下如何使用异步方式完成上一篇中的程序吧。
异步传输字符串
在上一篇中,我们由简到繁,提到了服务端的四种方式:
服务一个客户端的一个请求、服务一个客户端的多个请求、服务多个客户端的一个请求、服务多个客户端的多个请求。
我们说到可以将里层的while循环交给一个新建的线程去让它来完成。
除了这种方式以外,我们还可以使用一种更好的方式――使用线程池中的线程来完成。
我们可以使用BeginRead()、BeginWrite()等异步方法,同时让这BeginRead()方法和它的回调方法形成一个类似于while的无限循环:
首先在第一层循环中,接收到一个客户端后,调用BeginRead(),然后为该方法提供一个读取完成后的回调方法,然后在回调方法中对收到的字符进行处理,随后在回调方法中接着调用BeginRead()方法,并传入回调方法本身。
由于程序实现功能和上一篇完全相同,我就不再细述了。
而关于异步调用方法更多详细内容,可以参见C#中的委托和事件(续)。
1.服务端的实现
当程序越来越复杂的时候,就需要越来越高的抽象,所以从现在起我们不再把所有的代码全部都扔进Main()里,这次我创建了一个RemoteClient类,它对于服务端获取到的TcpClient进行了一个包装:
publicclassRemoteClient{
privateTcpClientclient;
privateNetworkStreamstreamToClient;
privateconstintBufferSize=8192;
privatebyte[]buffer;
privateRequestHandlerhandler;
publicRemoteClient(TcpClientclient){
this.client=client;
//打印连接到的客户端信息
Console.WriteLine("\nClientConnected!
{0}<--{1}",
client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
//获得流
streamToClient=client.GetStream();
buffer=newbyte[BufferSize];
//设置RequestHandler
handler=newRequestHandler();
//在构造函数中就开始准备读取
AsyncCallbackcallBack=newAsyncCallback(ReadComplete);
streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);
}
//再读取完成时进行回调
privatevoidReadComplete(IAsyncResultar){
intbytesRead=0;
try{
lock(streamToClient){
bytesRead=streamToClient.EndRead(ar);
Console.WriteLine("Readingdata,{0}bytes...",bytesRead);
}
if(bytesRead==0)thrownewException("读取到0字节");
stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);
Array.Clear(buffer,0,buffer.Length); //清空缓存,避免脏读
string[]msgArray=handler.GetActualString(msg); //获取实际的字符串
//遍历获得到的字符串
foreach(stringminmsgArray){
Console.WriteLine("Received:
{0}",m);
stringback=m.ToUpper();
//将得到的字符串改为大写并重新发送
byte[]temp=Encoding.Unicode.GetBytes(back);
streamToClient.Write(temp,0,temp.Length);
streamToClient.Flush();
Console.WriteLine("Sent:
{0}",back);
}
//再次调用BeginRead(),完成时调用自身,形成无限循环
lock(streamToClient){
AsyncCallbackcallBack=newAsyncCallback(ReadComplete);
streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);
}
}catch(Exceptionex){
if(streamToClient!
=null)
streamToClient.Dispose();
client.Close();
Console.WriteLine(ex.Message); //捕获异常时退出程序
}
}
}
随后,我们在主程序中仅仅创建TcpListener类型实例,由于RemoteClient类在构造函数中已经完成了初始化的工作,所以我们在下面的while循环中我们甚至不需要调用任何方法:
classServer{
staticvoidMain(string[]args){
Console.WriteLine("Serverisrunning...");
IPAddressip=newIPAddress(newbyte[]{127,0,0,1});
TcpListenerlistener=newTcpListener(ip,8500);
listener.Start(); //开始侦听
Console.WriteLine("StartListening...");
while(true){
//获取一个连接,同步方法,在此处中断
TcpClientclient=listener.AcceptTcpClient();
RemoteClientwapper=newRemoteClient(client);
}
}
}
好了,服务端的实现现在就完成了,接下来我们再看一下客户端的实现:
2.客户端的实现
与服务端类似,我们首先对TcpClient进行一个简单的包装,使它的使用更加方便一些,因为它是服务端的客户,所以我们将类的名称命名为ServerClient:
publicclassServerClient{
privateconstintBufferSize=8192;
privatebyte[]buffer;
privateTcpClientclient;
privateNetworkStreamstreamToServer;
privatestringmsg="WelcometoTraceFact.Net!
";
publicServerClient(){
try{
client=newTcpClient();
client.Connect("localhost",8500); //与服务器连接
}catch(Exceptionex){
Console.WriteLine(ex.Message);
return;
}
buffer=newbyte[BufferSize];
//打印连接到的服务端信息
Console.WriteLine("ServerConnected!
{0}-->{1}",
client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
streamToServer=client.GetStream();
}
//连续发送三条消息到服务端
publicvoidSendMessage(stringmsg){
msg=String.Format("[length={0}]{1}",msg.Length,msg);
for(inti=0;i<=2;i++){
byte[]temp=Encoding.Unicode.GetBytes(msg); //获得缓存
try{
streamToServer.Write(temp,0,temp.Length);//发往服务器
Console.WriteLine("Sent:
{0}",msg);
}catch(Except