none
StreamSocket的LoadAsync()的疑惑? RRS feed

  • 问题

  • 1. 通讯一定要用自定义协议吗? 比如网上例子都是首先读两个字节的长度,然后再根据长度读一个字符串。

         那如果我没有自定义协议,就不能收数据了吗? 比如我就想实现一个类似于串口调试工具的小程序,用一个任务或线程一直在监听socket的inputstream, 如果有数据到来就显示,没有就不显示,对方发送的数据也没有长度标头,反正他随便发什么我就收什么,这个需求用StreamSocket如何实现?

    2. 为什么没有数据时用LoadAsync()就会报错:操作标识符不正确 ? 还是跟上一个问题有关,没有数据时,我的接收方法如何不断的轮询?现在是用LoadAsync()做轮询,但显然是不行的。还有没有别的方法能实现?

    3. StreamSocket或相关的API,为什么没有一个数据到达时的事件通知可以用? 我要怎么样才能知道有数据来了,然后我才去用LoadAsync()读呢?

    说明:我是在win8.1 下做一个与蓝牙设备进行通讯的程序,蓝牙是服务端,PC这边的strore app是客户端。


    nio

    2013年12月27日 3:55

答案

  • 你好 nio Pan,

    使用 LoadAsync 方法来轮询 inputstream 没有错误,如果你看过 StreamSocket这个例子,你也应该看到,在这个例子里面例子编写者也是使用该方法轮询 inputstream 是否为 broken.

    另外,这里没有自定义协议一说。不过将字符串长度放在首位置一直是我们编程习惯,这也是一个良好的习惯。因为我们只需要判断一个整形值就可以知道这次接收的字符串长度,当然包括该字符串是不是有效字符串。

    对于自定义协议,估计你还没有参考StreamSocket 例子里面的发送数据的方法。现我将方法内容示例在下面。你可以进行参考。其实仅仅是 DataWriter.MeasureString 方法将字符串长度放入了outputstream.    

    void Scenario3::SendHello_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        if (!CoreApplication::Properties->HasKey("connected"))
        {
            rootPage->NotifyUser("Please run previous steps before doing this one.", NotifyType::ErrorMessage);
            return;
        }
    
        StreamSocket^ socket = dynamic_cast<StreamSocket^>(CoreApplication::Properties->Lookup("clientSocket"));
        DataWriter^ writer;
    
        // If possible, use the DataWriter we created previously. If not, then create new one.
        if (!CoreApplication::Properties->HasKey("clientDataWriter"))
        {
            writer = ref new DataWriter(socket->OutputStream);
            CoreApplication::Properties->Insert("clientDataWriter", writer);
        }
        else
        {
            writer = dynamic_cast<DataWriter^>(CoreApplication::Properties->Lookup("clientDataWriter"));
        }
    
        // Write first the length of the string a UINT32 value followed up by the string. The operation will just store the data locally.
        String^ stringToSend("Hello");
        writer->WriteUInt32(writer->MeasureString(stringToSend));
        writer->WriteString(stringToSend);
    
        // Write the locally buffered data to the network.
        task<unsigned int>(writer->StoreAsync()).then([this, socket, stringToSend] (task<unsigned int> writeTask)
        {
            try
            {
                // Try getting an exception.
                writeTask.get();
                SendOutput->Style = dynamic_cast<Windows::UI::Xaml::Style^>(rootPage->Resources->Lookup("StatusStyle"));
                SendOutput->Text = "\"" + stringToSend + "\" sent successfully";
            }
            catch (Exception^ exception)
            {
                rootPage->NotifyUser("Send failed with error: " + exception->Message, NotifyType::ErrorMessage);
            }
        });
    

    这是该例子的链接地址:

    http://code.msdn.microsoft.com/windowsapps/StreamSocket-Sample-8c573931

    希望上述对你有所帮助。

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • 已标记为答案 nio Pan 2014年1月3日 10:17
    2013年12月30日 7:32
    版主

全部回复

  • 你好 nio Pan,

    使用 LoadAsync 方法来轮询 inputstream 没有错误,如果你看过 StreamSocket这个例子,你也应该看到,在这个例子里面例子编写者也是使用该方法轮询 inputstream 是否为 broken.

    另外,这里没有自定义协议一说。不过将字符串长度放在首位置一直是我们编程习惯,这也是一个良好的习惯。因为我们只需要判断一个整形值就可以知道这次接收的字符串长度,当然包括该字符串是不是有效字符串。

    对于自定义协议,估计你还没有参考StreamSocket 例子里面的发送数据的方法。现我将方法内容示例在下面。你可以进行参考。其实仅仅是 DataWriter.MeasureString 方法将字符串长度放入了outputstream.    

    void Scenario3::SendHello_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        if (!CoreApplication::Properties->HasKey("connected"))
        {
            rootPage->NotifyUser("Please run previous steps before doing this one.", NotifyType::ErrorMessage);
            return;
        }
    
        StreamSocket^ socket = dynamic_cast<StreamSocket^>(CoreApplication::Properties->Lookup("clientSocket"));
        DataWriter^ writer;
    
        // If possible, use the DataWriter we created previously. If not, then create new one.
        if (!CoreApplication::Properties->HasKey("clientDataWriter"))
        {
            writer = ref new DataWriter(socket->OutputStream);
            CoreApplication::Properties->Insert("clientDataWriter", writer);
        }
        else
        {
            writer = dynamic_cast<DataWriter^>(CoreApplication::Properties->Lookup("clientDataWriter"));
        }
    
        // Write first the length of the string a UINT32 value followed up by the string. The operation will just store the data locally.
        String^ stringToSend("Hello");
        writer->WriteUInt32(writer->MeasureString(stringToSend));
        writer->WriteString(stringToSend);
    
        // Write the locally buffered data to the network.
        task<unsigned int>(writer->StoreAsync()).then([this, socket, stringToSend] (task<unsigned int> writeTask)
        {
            try
            {
                // Try getting an exception.
                writeTask.get();
                SendOutput->Style = dynamic_cast<Windows::UI::Xaml::Style^>(rootPage->Resources->Lookup("StatusStyle"));
                SendOutput->Text = "\"" + stringToSend + "\" sent successfully";
            }
            catch (Exception^ exception)
            {
                rootPage->NotifyUser("Send failed with error: " + exception->Message, NotifyType::ErrorMessage);
            }
        });
    

    这是该例子的链接地址:

    http://code.msdn.microsoft.com/windowsapps/StreamSocket-Sample-8c573931

    希望上述对你有所帮助。

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • 已标记为答案 nio Pan 2014年1月3日 10:17
    2013年12月30日 7:32
    版主
  •  谢谢 Xiaoliang,

    你给的例子和那个ListenerContext::ReceiveStringLoop()的例子我之前都看过了,只是这些应用场景跟我的应用场景都不一样,因此没有试成功。 

    也许我没有表达清楚我的场景,整个应用是这样的:

    硬件部分:有一个蓝牙读卡器; 软件部分:一个WinRT DLL 

    WinRT DLL中有下列函数: 枚举蓝牙设备, 连接蓝牙设备, 发送数据和接收数据

    枚举和连接蓝牙设备均没有问题。

    目前发送和接收数据做成了一个函数,即:软件DLL向读卡器发送一个hex格式指令,等待发送task完成后开始接收蓝牙设备的返回数据, 这样的一收一发的流程可以完成基本通讯任务。请注意,这里是DLL主动向蓝牙读卡器发送数据。

    但是,由于读卡器有一个讨厌的行为:它会在插卡和拔卡时主动发送一个"卡片状态数据"出来,因此问题就来了,如果这时我的软件部分不去先把这个数据读出来,而是执行上面的先发再收函数,去发一条指令给读卡器,那么接收到的数据就是读卡器之前主动发送来的"卡片状态数据",而不是针对我发出的指令所返回的数据。因此,现象就是,我每次发送指令,接收到的返回数据都是上一条的返回数据,而不是本条指令应该返回的数据。

    不得已,我又做了一函数:单独读数据。 即:蓝牙连接成功后,先读一次数据,把"卡片状态数据"先读出来,然后之后再发指令得到的就是正确的数据。

    但是这种办法显然不是解决办法,因为用户何时插卡、何时拔卡,是不知道的,因此我需要有一个读线程,一直在后头监控有没有接收到数据,也就是我想把发送数据和接收数据做成两个独立的部分, 发送数据不需要线程,用户每点一次按钮就把相关指令发出去, 而接收部分想做成一直在后台执行的线程,如果有数据到来,就接收并判断是"卡片状态数据"还是指令返回的数据,再进一步处理。

    因此我说我需要一个“数据到达时的事件通知”, 或者是我能一直调用某个函数来轮询有无数据到来,你说的例子里,是先读一个表示后续数据长度的整数,然后再根据这个长度读取后续字符串,这个我理解,但是在我试过,有如下问题:

    1. 如果有数据到来的情况下,调用一次LoadAsync()函数没有问题,但是紧接着再调用一次就会异常。说“操作标识符不正确”。  也就是说StreamSocket的InputStream里没有数据的话,LoadAsync()就会出错。我想要的LoadAsync()是,有数据的话就给我返回数据长度,没有的话就给我返回0,而不是报异常。

    2. 蓝牙读卡器发来的“卡片状态数据”只有2个字节,比如5002、5003,这只是个标识,没有长度信息,虽然我知道它有2个字节,可以在代码里写死。但是,其他指令的返回数据也没有在数据最前面附加长度字节,因此我也无法知道LoadAsync()的参数应该填多少,目前我就想多读一点,比如512字节。读到什么,就显示什么。就像普通的串口调试器,一直在那等着,我不知道什么时候有数据来,但一旦来到,来多少我就读多少,读多少就显示多少,就行了。

    说了这么多,不知道会不会看晕

    希望能得到任何有益的回复,谢谢!


    nio

    2013年12月30日 8:30
  • 你好 nio Pan,

    还好,不是特别晕 :)

    从上述,我看到你遇到的困难是在后台线程轮询 inputstream buffer 的时候, LoadAsync() 函数在buffer length 为0的情况的情况下会报异常。如果我没理解错,该问题应该可以用 DataReader 的一个属性来解决:

    DataReader.UnconsumedBufferLength

    该属性可以读取(只读属性)在inputstream 中还有多少数据没有被读取,数据类型以byte计算。

    这样的话,你在调用 LoadAsync() 方法前可以先判断上述属性,如果属性返回值为0,则表示没有数据进入,不进行任何操作。如果里面有数据,则调用LoadAsync()方法。

    希望上述对你有所帮助。

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    2013年12月31日 1:45
    版主
  • 试了,还是不行。

    在LoadAsync()之前判断DataReader.UnconsumedBufferLength一直是0,这下连5002也读不出来了。

    我发现,只有调一次LoadAsync(),才能把5002读出来。

    也就是说貌似判断DataReader.UnconsumedBufferLength必须放在LoadAsync()之后才行。



    nio

    2013年12月31日 3:52
  • 你好 nio Pan,

    我这里没有蓝牙环境无法测试,所以无法帮助你调试程序,不过我还是很乐意和你一起分析这个问题。

    如果设置一个标记位呢,比如如果还没有取状态值则取状态值后判断buffer 长度,如果已经取过状态值了。则将标记位设置为已取过。然后不取标志位,直接判断长度。比如伪代码如下:

    bool statetag=false;//全局变量

    if(!statetag){

       取状态值

       statetag=true;

    }

    if(length==0) return;

    LoadAsync();


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    2013年12月31日 5:31
    版主
  • 非常感谢!

    首先您说的取状态值是指取“卡状态值”吗?

    问题是,卡状态值不一定什么时候会被发出,比如蓝牙读卡器开机的时候,如果没检测到卡,则会发出5002, 如果检测到卡,则什么都不发。  另外,在某一时刻,如果卡被拔出,则会发出5003。所以说状态值的发出,是可以反复多次的、随时的。我无法用一个变量去标识是否已取过状态值。

    所以程序一运行起来的时候,如果我先调用LoadAsync()去取,这时候,如果之前读卡器发了5002那么就能取出来,如果没发,那么这次调用不会有任何反应,而且下次再调就报错。

    如果我先取unconsumedBufferLength,那么无论读卡器之前是否发过5002,此值都为0,所以以此判断的话,程序根本不会执行LoadAsync().

    我试了先LoadAsync(),然后判断unconsumedBufferLength,如果length为0,则cancel task。 结果还是不行,效果仍然是:这次调用不会有任何反应,下次再调就报错。代码如下:

    		return create_task(_reader->LoadAsync(512*sizeof(BYTE))).then([this](unsigned int size)
    		{
    			Platform::String^ recvStr;
    
    			if ( _reader->UnconsumedBufferLength == 0 )
    			{
    				cancel_current_task();
    			}
    			else
    			{
    				Array<unsigned char>^ recvArr = ref new Array<unsigned char, 1>(size);
    				_reader->ReadBytes(recvArr);
    				std::string strHex = toString(recvArr->Data, recvArr->Length);
    				recvStr = stops(strHex);
    			}
    			return recvStr;
    		});


    nio

    2013年12月31日 6:44
  • 又遇到另一个问题,头都大了,win8的数据收发怎么这么费劲呢?

    问题背景是: 

    1.没有5002,5003状态值的干扰情况下(读卡器开机前就插入卡,整个期间也不拔卡)

    2. 使用一发一收的收发函数,代码附后。

    问题:

    发一条指令,返回值应该是30个字节, 可是调用了下面的收发函数后只返回了10个字节,必须再调用一次收发函数,才能返回后面的20个字节,再第三次调用收发函数,这下才对了,返回了30个字节。 

    例如:

    第一次:   发指令: 6200    返回: 80 14 00 00 00 00 00 00 00 00

    第二次:   发指令: 6200    返回: 3F FA 53 00 00 81 31 FE 45 4A 43 4F 50 34 31 56 32 32 31 D6

    第三次:   发指令: 6200    返回: 80 14 00 00 00 00 00 00 00 00 3F FA 53 00 00 81 31 FE 45 4A 43 4F 50 34 31 56 32 32 31 D6

    实际上应该是:

    每一次:  发指令: 6200    返回: 80 14 00 00 00 00 00 00 00 00 3F FA 53 00 00 81 31 FE 45 4A 43 4F 50 34 31 56 32 32 31 D6

    //功能: 发送和接收
    //输入参数: Hex格式的指令字符串
    //返回结果: 成功则返回包含Hex格式字符串的task
    task<String^>  CBTWinRTDLL::SendAndRecvDataAsync(String^ sendData)
    {
    	//如果_socket不存在,返回提示信息
    	if (!CoreApplication::Properties->HasKey("clientSocket"))
    	{
    		auto op = create_async([]()
    		{
    		});
    
    		return create_task(op).then([]()
    		{
    			String^ retStr = "Client socket does not exist, please connect device first.";
    			return retStr;
    		});
    	}
    
    	//将字符串从String^ 转成 std:string
    	std::string str = pstos(sendData);
    
    	//Trim all white spaces 去掉所有空格
    	std::string searchString(" ");
    	std::string replaceString("");
    	std::string::size_type pos = 0;
    	while ((pos = str.find(searchString, pos)) != std::string::npos)
    	{
    		str.replace(pos, searchString.size(), replaceString);
    		pos++;
    	}
    
    	//校验参数是否合法: 是否符合CCID标头定义,数据长度是否正确等。如果校验失败则立即返回空字符串。
    
    
    	int len = str.length();
    
    	//如果输入参数的长度为0, 函数返回
    	if (0 == len)
    	{
    		auto op = create_async([]()
    		{
    		});
    
    		return create_task(op).then([]()
    		{
    			String^ retStr = "Data for send cannot be empty.";
    			return retStr;
    		});
    	}
    
    	//如果输入参数的长度为奇数,函数返回
    	if ((len % 2) != 0)
    	{
    		auto op = create_async([]()
    		{
    		});
    
    		return create_task(op).then([]()
    		{
    			String^ retStr = "Data for send is invalid.";
    			return retStr;
    		});
    	}
    
    	//将Hex字符串转成Hex格式的byte数组
    	unsigned char * buffer = (unsigned char *)str.c_str();
    	len = len / 2;  //数组的长度
    	Platform::Array<unsigned char>^ sendArray = ref new Platform::Array<unsigned char, 1>(len);
    	StrToHex(sendArray, buffer, len);
    
    
    	//将byte数组写入到输出流
    	_writer->WriteBytes(sendArray);
    
    	//将缓冲区中的数据提交到备份存储区。
    	task<unsigned int> WriteTask(_writer->StoreAsync());
    	return WriteTask.then([this](task<unsigned int> writeTask)
    	{
    		task<bool> FlushTask(_socket->OutputStream->FlushAsync());
    		return FlushTask.then([this](task<bool> flushTask)
    		{
    			_reader->InputStreamOptions = InputStreamOptions::Partial;
    
    			task<unsigned int> ReadTask(_reader->LoadAsync(512*sizeof(byte)));
    			return ReadTask.then([this](task<unsigned int> readTask)
    			{
    				try
    				{
    					unsigned char buffer[2048] = { 0 };
    					int nNow = 0;
    					while (_reader->UnconsumedBufferLength > 0)
    					{
    						Platform::Array<unsigned char>^ recvArray = ref new Platform::Array<unsigned char, 1>(1);
    						_reader->ReadBytes(recvArray);
    						memcpy(buffer + nNow, recvArray->Data, 1);
    						nNow += 1;
    					}
    
    					std::string strHex = toString(buffer, nNow);
    					Platform::String^ recvStr = stops(strHex);
    
    					return recvStr;
    				}
    				catch (Exception^ ex)
    				{
    					return ex->Message;
    				}
    
    				//try
    				//{
    				//	int readSize = readTask.get();
    				//	
    				//	Platform::String^ recvStr;
    				//	if ((readSize) > 0)
    				//	{
    				//		Platform::Array<unsigned char>^ recvArr = ref new Platform::Array<unsigned char, 1>(readSize);
    				//		_reader->ReadBytes(recvArr);
    				//		std::string strHex = toString(recvArr->Data, recvArr->Length);
    				//		recvStr = stops(strHex);
    				//	}
    				//	else
    				//	{
    				//		recvStr = "";
    				//	}
    				//	return recvStr;
    				//}
    				//catch (Exception^ ex)
    				//{
    				//	return ex->Message;
    				//}
    			});
    		});
    	});
    }


    nio

    2013年12月31日 7:36
  • 你好 nio Pan,

    如果你在后台会循环调用该方法,则将InputStreamOptions 设置为partial 并不合适。

    因为该属性导致异步加载操作只有在有值被加载后才会完成。在此期间任何访问inputstream的操作应该都会被拒绝。(因为是异步操作。所以该函数有可能在第一次的加载没有完成时继续要求访问inputstream)。

    至于你后来遇到的情况应该是特殊情况,我在网上看到一段C#代码,上有截图,可以很好的完成该动作。你不妨先按照字符串读取,看是否成功。

    下面是我看到的例子:

    async private void WaitForData(StreamSocket socket)
            {
                var dr = new DataReader(socket.InputStream);
                //dr.InputStreamOptions = InputStreamOptions.Partial;
                var stringHeader = await dr.LoadAsync(4);
    
                if (stringHeader == 0)
                {
                    LogMessage(string.Format("Disconnected (from {0})", socket.Information.RemoteHostName.DisplayName));
                    return;
                }
    
                int strLength = dr.ReadInt32();
    
                uint numStrBytes = await dr.LoadAsync((uint)strLength);
                string msg = dr.ReadString(numStrBytes);
    
                LogMessage(string.Format("Received (from {0}): {1}", socket.Information.RemoteHostName.DisplayName, msg));
    
                WaitForData(socket);
            }

    希望上述能够帮到你!

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    2013年12月31日 10:23
    版主
  • 谢谢Xiaoliang,

    第二个问题:数据读取长度不对的问题,已经找到原因了。是因为发指令的task已完成马上就开始了读操作的task,而这时卡还没有完成把数据给读卡器的动作,也就是说读操作开始的早了。我加了个空循环,让它延时几秒再开始读操作的task, 就行了。

    这个问题就又有点像第一个问题了,发完指令后,我不知道什么时候会有数据回来, 什么时刻开始让程序执行读操作的task呢?

    如果一定要用延时来解决的话,标准的推荐的延时方法应该怎么做呢?调用哪个API呢?


    nio

    2013年12月31日 10:33
  • 你好 nio Pan,

    请查看下面链接:

    http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.streams.datareaderloadoperation

    一般异步操作的返回值都是TOperation 类型。 该类型有通用的方法和属性。该方法中你可以判断存方法的complete 状态。当完成时,再进行读操作就可以了。

    希望对你有所帮助。

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    2013年12月31日 10:57
    版主
  • 谢谢Xiaoliang! 

    问题回头再解决,

    先祝你新年快乐!!!


    nio

    2013年12月31日 14:05
  • 你好 nio Pan,

    请查看下面链接:

    http://msdn.microsoft.com/en-us/library/windows/apps/windows.storage.streams.datareaderloadoperation

    一般异步操作的返回值都是TOperation 类型。 该类型有通用的方法和属性。该方法中你可以判断存方法的complete 状态。当完成时,再进行读操作就可以了。

    希望对你有所帮助。

    Xiaoliang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Xiaoliang  你好,

    这种方法也不行,原因是虽然存的操作完成了,但是硬件设备准备待返回的数据也需要一定的时间,所以这时候开始读操作还是早了。 最好的的读时机还是应该发生在socket的inputstream里有实际数据到来时才行,但是现在苦于没有办法判断inputstream里何时才有数据。


    nio

    2014年1月2日 3:55
  • 问题已解决,非常感谢Xiaoliang的帮助!

    nio

    2014年1月3日 10:17