异步传输字符串
前面说过服务端的四种方式:
1.服务一个客户端的一个请求
2.服务一个客户端的多个请求
3.服务多个客户端的一个请求
4.服务多个客户端的多个请求
前面说到第三种了,对于最后一种最实际的情况前面说的是将内层的while循环交给一个新建的线程去让它来完成.
除了这种方式以外,还可以使用一张更好地方式----使用线程池中的线程来完成.可以使用BeginRead(),BeginWrite()等异步方法,同时让BeginRead()方法和它的回调方法形成一个类似于while的无限循环:首先在第一层循环中,接收到一个客户端后,调用BeginRead(),然后为该方法提供一个读取完成后的回调方法,接下来在毁掉方法中对收到的字符进行处理,随后在回调方法中接着调用BeginRead()方法,并传入回调方法本身.
服务端的实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Server
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Server is running...");
IPAddress ip = new IPAddress(new byte[] { 192,168,3,19});
TcpListener listener = new TcpListener(ip,9271);
listener.Start();//开始监听
Console.WriteLine("Start Listening...");
while (true)
{
//获取一个连接,同步方法,在此处中断
TcpClient client = listener.AcceptTcpClient();
Server wapper = new Server(client);
}
}
}
public class Server
{
private TcpClient client;
private NetworkStream streamToClient;
private const int BufferSize = 8192;
private byte[] buffer;
private RequestHandler handler;
public Server(TcpClient client)
{
this.client = client;
//打印连接到的客户端信息
Console.WriteLine("\nClient Connected! Local: {0}<--Client: {1}",client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
//获得流
streamToClient = client.GetStream();
buffer = new byte[BufferSize];
//设置ResquestHandler
handler = new RequestHandler();
//在构造函数中就开始准备读取
AsyncCallback callBack = new AsyncCallback(ReadComplete);
streamToClient.BeginRead(buffer,0,BufferSize, callBack, null);
}
//在读取完成时进行回调
private void ReadComplete(IAsyncResult ar)
{
int bytesRead = 0;
try
{
bytesRead = streamToClient.EndRead(ar);
if (bytesRead==0)
{
Console.WriteLine("Client offline");
return;
}
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} [{1} bytes]",m,bytesRead);
string back = m.ToUpper();
//将得到的字符串改为大写并重新发送
byte[] temp = Encoding.Unicode.GetBytes(back);
streamToClient.Write(temp,0,temp.Length);
streamToClient.Flush();
Console.WriteLine("Sent: {0}",back);
}
//再次调用BeginRead(),完成时调用自身,形成无限循环
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);
}
}
}
}
因为前面关于RequestHandler的代码说过了,这里就不说了.
客户端的实现
与服务端类似,对TcpClient进行了一个简单的包装,使它的使用更加方便一下,将类命名为Client:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Client
{
class Program
{
static void Main(string[] args)
{
ConsoleKey key;
Client c = new Client();
c.SendMessage("Hello,Thanks you for reading!!!");
Console.WriteLine("\n\n按Q退出");
do
{
key = Console.ReadKey(true).Key;
} while (key!=ConsoleKey.Q);
}
}
public class Client
{
private const int BufferSize = 8192;
private byte[] buffer;
private TcpClient client;
private NetworkStream streamToServer;
public Client()
{
try
{
client = new TcpClient();
client.Connect(new IPAddress(new byte[] { 192, 168, 3, 19 }), 9271);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
buffer = new byte[BufferSize];
//打印连接到的服务端信息
Console.WriteLine("Server Connected! Local: {0}-->Server: {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 < 3; i++)
{
byte[] temp = Encoding.Unicode.GetBytes(msg);//获得缓存
try
{
streamToServer.Write(temp, 0, temp.Length);
Console.WriteLine("Sent: {0}", msg);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
break;
}
}
AsyncCallback callBack = new AsyncCallback(ReadComplete);
streamToServer.BeginRead(buffer,0,BufferSize,callBack,null);
}
//读取完成时的回调方法
private void ReadComplete(IAsyncResult ar)
{
int bytesRead;
try
{
bytesRead = streamToServer.EndRead(ar);
if (bytesRead == 0)
{
Console.WriteLine("Server offline");
return;
}
string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: {0} [{1}bytes]", msg, bytesRead);
Array.Clear(buffer, 0, buffer.Length);
AsyncCallback callBack = new AsyncCallback(ReadComplete);
streamToServer.BeginRead(buffer, 0, BufferSize, callBack, null);
}
catch (Exception ex)
{
if (streamToServer!=null)
{
streamToServer.Dispose();
}
client.Close();
Console.WriteLine(ex.Message);
}
}
}
}
因为楼主为了大家看的明白,把所有的类都写在同一个文件中了,这样不利于阅读,不管怎么样,程序现在能跑起来了,看一下程序的运行效果:
//服务端:
Server is running...
Start Listening...
Client Connected! Local: 192.168.3.19:9271<--Client: 192.168.3.19:29144
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
Client Connected! Local: 192.168.3.19:9271<--Client: 192.168.3.19:29147
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
Received: Hello,Thanks you for reading!!! [252 bytes]
Sent: HELLO,THANKS YOU FOR READING!!!
开启了两个客户端:
//客户端1:
Server Connected! Local: 192.168.3.19:29144-->Server: 192.168.3.19:9271
Sent: [length=31]Hello,Thanks you for reading!!!
Sent: [length=31]Hello,Thanks you for reading!!!
Sent: [length=31]Hello,Thanks you for reading!!!
按Q退出
Received: HELLO,THANKS YOU FOR READING!!! [62bytes]
Received: HELLO,THANKS YOU FOR READING!!!HELLO,THANKS YOU FOR READING!!! [124bytes]
//客户端2:
Server Connected! Local: 192.168.3.19:29147-->Server: 192.168.3.19:9271
Sent: [length=31]Hello,Thanks you for reading!!!
Sent: [length=31]Hello,Thanks you for reading!!!
Sent: [length=31]Hello,Thanks you for reading!!!
按Q退出
Received: HELLO,THANKS YOU FOR READING!!! [62bytes]
Received: HELLO,THANKS YOU FOR READING!!! [62bytes]
Received: HELLO,THANKS YOU FOR READING!!! [62bytes]
看到有个客户端在接受消息的时候出现了问题了吗?客户端将来自服务端的两个消息合并成了一个消息.想想怎么样解决?
在客户端没有采取刚才我们编写的协议来处理,因此当客户端收到应答时,仍然会发生文本合并的情况.
使用这种定义协议的方式有它本身的优点,但缺点也很明显,如果客户端知道了这个协议,有意的输入[length=XXX],但是后面的长度却不匹配,此时程序就会出错.可选的解决办法是对”[”和”]”进行编码,当客户端有意输入这两个字符时,将它替换成”\[”和”\]”或者别的字符,在读取之后再将它还原.
现在应该可以看出服务端可以连接多个客户端,同时可以处理他们的多次请求.
从实用性的角度看,这些示例不完善,在服务端,接收到字符串之后,将其转换为大写后立即发送给了客户端.流程总是:客户端-->服务端-->客户端.在实际中,他们之间的交互是无序的,可能客户端发送多条消息,服务端只回复一条消息.可以尝试将服务端改为使用Console.ReadLine()接受用户输入,然后在发回给客户端.