最近找了一本“C#网络编程高级篇之网页游戏辅助程序设计”的书在看,在第二章套接字编程中,书中讲解了同步套接字TCP客户端程序和异步套接字TCP客户端编程(暂时先只理解客户端,服务器端暂时不管),以下是书中同步与异步的程序(疑惑的地方我会注明),我主要疑惑的是在同步中连接服务器后就开一个线程去接收数据,在异步中,是连接服务器后,通过回调自身来实现数据的不断接受,按照对异步的理解,当使用异步回调时相当于另开了一个后台子线程,而主程序可以接着往下执行。但这样我感觉就和同步中开一个接收子线程的就没什么区别了,都是开了子线程去接收数据。。。是否是由于同步中开了一个子线程后是“抢答”式的在主线程、子线程之间切换,而异步就是开了一个后台子线程,是并行的?还是在占用资源上有区别?
同步接收程序:
private void AccepMessage()//接收数据的函数
{
NetworkStream netStream = new NetworkStream(socket);
while (true)
{
try
{
byte[] datasize = new byte[4];
netStream.Read(datasize, 0, 4);
int size = System.BitConverter.ToInt32(datasize, 0);
Byte[] message = new byte[size];
int dataleft = size;
int start = 0;
while (dataleft > 0)
{
int recv = netStream.Read(message, start, dataleft);
start += recv;
dataleft -= recv;
}
this.rchTxtBoxReceive.Rtf = System.Text.Encoding.Unicode.GetString(message);
}
catch
{
break;
}
}
}
private void btnRequest_Click(object sender, EventArgs e)
{
IPEndPoint server = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6888);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Connect(server);
}
catch
{
MessageBox.Show("与服务器连接失败!");
return;
}
this.btnRequest.Enabled = false;
this.lstBoxState.Items.Add("与服务器连接成功");
thread = new Thread(new ThreadStart(AccepMessage));//开启线程,接收数据,就是此处与异步的区别???
thread.Start();
}
异步接收程序:
private void ConnectCallback(IAsyncResult ar)
{
try
{
//Socket client1 = (Socket)ar.AsyncState;
client.EndConnect(ar);
lstBoxMessage.Invoke(AppendString, String.Format("已经成功连接到服务器{0}!", client.RemoteEndPoint.ToString()));
lstBoxMessage.Invoke(AppendString, String.Format("本地端接点为{0}!", client.LocalEndPoint.ToString()));
Rcvbuffer = new byte[client.SendBufferSize];
//开始回调接收,但也是相当于开了一个线程,与同步的区别???
AsyncCallback callback = new AsyncCallback(ReceiveCallback);
client.BeginReceive(Rcvbuffer, 0, Rcvbuffer.Length, SocketFlags.None, callback, client);
}
catch
{
lstBoxMessage.Invoke(AppendString, String.Format("无法建立与服务器的连接!"));
}
}
private void ReceiveCallback(IAsyncResult ar)//异步回调接收
{
try
{
int i = client.EndReceive(ar);
string data = string.Format("收:{0}", Encoding.UTF8.GetString(Rcvbuffer, 0, i));
lstBoxMessage.Invoke(AppendString, data);
Rcvbuffer = new byte[client.SendBufferSize];
AsyncCallback callback = new AsyncCallback(ReceiveCallback);
client.BeginReceive(Rcvbuffer, 0, Rcvbuffer.Length, SocketFlags.None, callback, client);
}
catch (Exception ex)
{
if (client != null)
{
client.Shutdown(SocketShutdown.Both);
client.Close();
client = null;
lstBoxMessage.Invoke(AppendString, ex.Message);
}
}
}
------解决思路----------------------
同步会阻塞你的主线程,让你的程序假死,异步是让子线程去做事,你还可以干其他的,不影响,子线程干完了,通知你
------解决思路----------------------
Begin-End模式是利用委托交给托管线程池去执行,本质上都是利用子线程去异步执行
不同的是,托管线程池内部作了优化,通常情况都会有空闲的线程直接去跑,要比你手动去开一个线程快一点(当然你是感觉不到的)
Begin-End相对固化,用起来方便,线程资源回收更高效
自己开线程当然更加灵活,要不然就等于捆住手脚了。
大多数情况还是推荐用封装好的,维护起来方便,最起码大大降低了初学者犯错的机率
------解决思路----------------------
2 楼dongxinxi朋友的回复不是很准确。
------解决思路----------------------
Socket的异步接收不一定要用到线程。如果异步接收用IOCP来完成,那么并不需要一个线程来等待数据(但一旦有了数据,则需要一个线程来运行回调函数)。具体看如下例子(例子用Socket.Accept,但Socket.Receive道理相同)。
static void Main(string[] args)
{
bool sync = true;
for(int i = 0; i < 20; i++)
{
int workerThreads, iocpThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
Console.WriteLine("worker:{0} iocp:{1}", workerThreads, iocpThreads);
Socket socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
socket.Listen(3);
if (sync)
{
ThreadPool.QueueUserWorkItem(state => { socket.Accept(); });
}
else
{
socket.BeginAccept((ir) => { Console.WriteLine("connection accepted"); }, null);
}
Thread.Sleep(1000);
}
}
在我的机器上,当sync=true的时候,运行结果如下(注意每个同步接收占用一个工作线程):
worker:1023 iocp:1000
worker:1022 iocp:1000
worker:1021 iocp:1000
worker:1020 iocp:1000
worker:1019 iocp:1000
worker:1018 iocp:1000
worker:1017 iocp:1000
worker:1016 iocp:1000
...
当sync=false的时候,运行结果如下(注意可用worker线程数量并没有减少)。
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
------解决思路----------------------
谢谢LS的更正,线程池中的线程分两个工种。之前只是针对LZ的问题对比了下
IAsyncResult接口中有一个CompletedSynchronously属性,可以告诉你是否同步完成,这个还是知道的