主题:同步与异步发送\接收的问题!
luochen601
[专家分:0] 发布于 2006-11-04 22:10:00
做了一个与三菱PLC通讯的程序,使用TCP协议,我是用流来发送和接收的。通讯的具体规则如下:当PLC发送一个数据给计算机后,计算机必须发回一个“响应”(十六进制的E0),然后PLC才接着发送下一个数据;当计算机给PLC发送数据,PLC接收完后会自动给计算机发回一个“响应”(十六进制的00);我现在是写的程序是同步接收和发送,用了一个线程负责接收,可以顺利接收到从PLC发过来的数据;用一个button_click事件来发送数据,但却不能实现;请问这是什么原因?一定要用异步发送和接收吗?同步和异步到底有什么区别?
回复列表 (共7个回复)
沙发
highandblue [专家分:120] 发布于 2006-11-05 14:02:00
你好,你的邮箱是多少?(可以给我发站内短消息).我写一个程序发给你.
关于同步与异步的区别,我刚刚查过一个资料,转载如下:
操作系统发展到今天已经十分精巧,线程就是其中一个杰作。操作系统把 CPU 处理时间划分成许多短暂时间片,在时间 T1 执行一个线程的指令,到时间 T2 又执行下一线程的指令,各线程轮流执行,结果好象是所有线程在并肩前进。这样,编程时可以创建多个线程,在同一期间执行,各线程可以“并行”完成不同的任务。
在单线程方式下,计算机是一台严格意义上的冯·诺依曼式机器,一段代码调用另一段代码时,只能采用同步调用,必须等待这段代码执行完返回结果后,调用方才能继续往下执行。有了多线程的支持,可以采用异步调用,调用方和被调方可以属于两个不同的线程,调用方启动被调方线程后,不等对方返回结果就继续执行后续代码。被调方执行完毕后,通过某种手段通知调用方:结果已经出来,请酌情处理。
计算机中有些处理比较耗时。调用这种处理代码时,调用方如果站在那里苦苦等待,会严重影响程序性能。例如,某个程序启动后如果需要打开文件读出其中的数据,再根据这些数据进行一系列初始化处理,程序主窗口将迟迟不能显示,让用户感到这个程序怎么等半天也不出来,太差劲了。借助异步调用可以把问题轻松化解:把整个初始化处理放进一个单独线程,主线程启动此线程后接着往下走,让主窗口瞬间显示出来。等用户盯着窗口犯呆时,初始化处理就在背后悄悄完成了。程序开始稳定运行以后,还可以继续使用这种技巧改善人机交互的瞬时反应。用户点击鼠标时,所激发的操作如果较费时,再点击鼠标将不会立即反应,整个程序显得很沉重。借助异步调用处理费时的操作,让主线程随时恭候下一条消息,用户点击鼠标时感到轻松快捷,肯定会对软件产生好感。
异步调用用来处理从外部输入的数据特别有效。假如计算机需要从一台低速设备索取数据,然后是一段冗长的数据处理过程,采用同步调用显然很不合算:计算机先向外部设备发出请求,然后等待数据输入;而外部设备向计算机发送数据后,也要等待计算机完成数据处理后再发出下一条数据请求。双方都有一段等待期,拉长了整个处理过程。其实,计算机可以在处理数据之前先发出下一条数据请求,然后立即去处理数据。如果数据处理比数据采集快,要等待的只有计算机,外部设备可以连续不停地采集数据。如果计算机同时连接多台输入设备,可以轮流向各台设备发出数据请求,并随时处理每台设备发来的数据,整个系统可以保持连续高速运转。编程的关键是把数据索取代码和数据处理代码分别归属两个不同的线程。数据处理代码调用一个数据请求异步函数,然后径自处理手头的数据。待下一组数据到来后,数据处理线程将收到通知,结束 wait 状态,发出下一条数据请求,然后继续处理数据。
异步调用时,调用方不等被调方返回结果就转身离去,因此必须有一种机制让被调方有了结果时能通知调用方。在同一进程中有很多手段可以利用,笔者常用的手段是回调、event 对象和消息。
回调方式很简单:调用异步函数时在参数中放入一个函数地址,异步函数保存此地址,待有了结果后回调此函数便可以向调用方发出通知。如果把异步函数包装进一个对象中,可以用事件取代回调函数地址,通过事件处理例程向调用方发通知。
event 是 Windows 系统提供的一个常用同步对象,以在异步处理中对齐不同线程之间的步点。如果调用方暂时无事可做,可以调用 wait 函数等在那里,此时 event 处于 nonsignaled 状态。当被调方出来结果之后,把 event 对象置于 signaled 状态,wait 函数便自动结束等待,使调用方重新动作起来,从被调方取出处理结果。这种方式比回调方式要复杂一些,速度也相对较慢,但有很大的灵活性,可以搞出很多花样以适应比较复杂的处理系统。
借助 Windows 消息发通知是个不错的选择,既简单又安全。程序中定义一个用户消息,并由调用方准备好消息处理例程。被调方出来结果之后立即向调用方发送此消息,并通过 WParam 和 LParam 这两个参数传送结果。消息总是与窗口 handle 关联,因此调用方必须借助一个窗口才能接收消息,这是其不方便之处。另外,通过消息联络会影响速度,需要高速处理时回调方式更有优势。
如果调用方和被调方分属两个不同的进程,由于内存空间的隔阂,一般是采用 Windows 消息发通知比较简单可靠,被调方可以借助消息本身向调用方传送数据。event 对象也可以通过名称在不同进程间共享,但只能发通知,本身无法传送数据,需要借助 Windows 消息和 FileMapping 等内存共享手段或借助 MailSlot 和 Pipe 等通信手段。
异步调用原理并不复杂,但实际使用时容易出莫名其妙的问题,特别是不同线程共享代码或共享数据时容易出问题,编程时需要时时注意是否存在这样的共享,并通过各种状态标志避免冲突。Windows 系统提供的 mutex 对象用在这里特别方便。mutex 同一时刻只能有一个管辖者。一个线程放弃管辖权后,另一线程才能接管。当某线程执行到敏感区之前先接管 mutex,使其他线程被 wait 函数堵在身后;脱离敏感区之后立即放弃管辖权,使 wait 函数结束等待,另一个线程便有机会光临此敏感区。这样就可以有效避免多个线程进入同一敏感区。
由于异步调用容易出问题,要设计一个安全高效的编程方案需要比较多的设计经验,所以最好不要滥用异步调用。同步调用毕竟让人更舒服些:不管程序走到哪里,只要死盯着移动点就能心中有数,不至于象异步调用那样,总有一种四面受敌、惶惶不安的感觉。必要时甚至可以把异步函数转换为同步函数。方法很简单:调用异步函数后马上调用 wait 函数等在那里,待异步函数返回结果后再继续往下走。
板凳
franchdream [专家分:1450] 发布于 2006-11-05 14:35:00
从控件的非创建线程调用的控件上的任何方法必须被封送到(在其上执行)该控件的创建线程.
用REQUIRINVOKE这个属性.
看了你这几个问题,似乎不是很麻烦,要不要我帮忙?
3 楼
luochen601 [专家分:0] 发布于 2006-11-05 20:43:00
当然需要帮忙了,先谢过了!我的邮箱:luochen601@163.com
4 楼
franchdream [专家分:1450] 发布于 2006-11-06 00:03:00
franchdream@sina.com
记得加分,把关键的,你遇到的问题打个包扔过来,我帮你做个WINFORM下的DOME.
5 楼
jackbaba [专家分:0] 发布于 2006-11-06 11:24:00
jackbaba0203@sina.com
6 楼
luochen601 [专家分:0] 发布于 2006-11-06 18:09:00
private void Form1_Load(object sender, EventArgs e)
{
int port = 8000;
listener = new TcpListener(port);
listener.Start();
threadlisten = new Thread(new ThreadStart(startlisten));
threadlisten.Start();
}
private void startlisten()
{
while (true)
{
client = listener.AcceptTcpClient();
threadreceive = new Thread(new ThreadStart(receive));
threadreceive.Start();
}
}
public void receive()
{
while (true)
{
ns=client .GetStream ();
byte[] rec = new byte[20];
int i = ns.Read(rec, 0, rec.Length);
if (i != 0)
{
if (rec[0] == 0x60)
{
//数据处理过程
}
ns.Write(msg, 0, msg.Length);
}
}
}
private void button2_Click(object sender, EventArgs e)
{
byte[] snd = { 0x60, 0x00, 0x00, 0x01, 0x00,0x0F };
client.streamSend(snd);
ns.Write(snd, 0, snd.Length);
ns.Flush();
}
这是我编的程序,可以正常接收,但按下button2既不能发送连接收也不行了,然后过了一会儿显示出错,强制关闭了连接,麻烦大家看看这个程序到底出了什么问题?
7 楼
highandblue [专家分:120] 发布于 2006-11-08 15:33:00
楼主,你好!
我认真的看了你上面写的程序,谈一谈我的看法,说错了不要见怪哈哈!
1.楼主使用的数据侦听程序:
private void startlisten()
{
while (true)
{
client = listener.AcceptTcpClient();
threadreceive = new Thread(new ThreadStart(receive));
threadreceive.Start();
}
}
不断的初始化线程threadreceive,并且楼主似乎在后来的程序中也没有结束该线程.我
感觉不好,不如使用下面的程序:
private void StartListen()
{
try
{
tcpListener = new TcpListener(8000);//端口号为8000
//开始监听
tcpListener.Start();
//改变状态栏,我自己在界面中加了个statusBr1
statusBar1.Text = "正在监听...";
//设置监听状态
this.listen = true;
while(listen)
{
//用于接受挂起的连接请求
TcpClient s=tcpListener.AcceptTcpClient();
//用于获得接收数据
NetworkStream nc=s.GetStream();
//数据接收缓存器,用于存储数据
byte[] bytes = new byte[1024];
//用于接收数据
int bytesRead=nc.Read(bytes,0,bytes.Length);
//将数据转化为字符串形式,此处使用.UTF8可以正确显示中文,你可以试试使用别的,如ASC2码等等
string message=System.Text.Encoding.UTF8.GetString(bytes,0,bytesRead);
//在文本框中写入字符串
this.richTextBox2.AppendText(message);
}
}
//错误处理
catch(Exception error)
{
//忽略异常错误,若有错误在状态栏中显示
statusBar1.Text = "已停止监听";
}
}
这里使用了try-catch,并且没有使用专门的接收数据线程,而只有一个不断运行的侦听
线程,使得程序稳定.
2.关于发送数据,楼主的程序写的似乎不完整,也没有关闭网络流的程序.我也看不大
懂,但是我自己写了一个,不过是使用的button3而已,整个程序我会发到你邮箱里的.
private void button3_Click(object sender, System.EventArgs e)
{
try
{
//richTextBox1中获取消息
string msg = "<" + this.textBox2.Text + ">" + this.richTextBox1.Text + "\n";
//根据目标地址建立连接,textBox1是用户写入的对方机器IP地址
TcpClient tcpClient = new TcpClient(textBox1.Text,8000);
//获取用于网络传输的数据流
NetworkStream tcpStream = tcpClient.GetStream();
//"streamW"对象用于写入数据
StreamWriter streamW = new StreamWriter(tcpStream);
//将字符串写入流
streamW.Write(msg);
//将缓冲数据写入网络流
streamW.Flush();
//关闭网络流
tcpStream.Close();
//关闭
tcpClient.Close();
//将输出数据也写入richTextBox2中
this.richTextBox2.AppendText(msg);
//再将richTextBox1中的数据内容清空
this.richTextBox1.Clear();
}
//异常处理程序
catch
{
statusBar1.Text = "目标计算机拒绝连接请求!";
}
}
3.提醒楼主注意发送端口号和侦听端口号都要一样,如本例使用8000.
罗嗦了这么多,希望对楼主有帮助:-)
我来回复