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