【问题标题】:Windows writeFileEx and recvfrom Crashes programWindows writeFileEx 和 recvfrom 崩溃程序
【发布时间】:2013-08-09 10:00:39
【问题描述】:

我正在尝试编写一个非常简单的 C 程序。出于某种原因,我不能在同一个程序中使用 writeFileEx 和 recvfrom,因为每次调用 writeFileEx 都会导致 recvfrom 崩溃。

这里是相关代码sn -p

struct sockaddr_in server, client_address;
int client_length, recv_len;
char buf[BUFLEN];
WSADATA wsa;
if (WSAStartup(MAKEWORD(2,2),&wsa) != 0 )
{
  //Error
}

SOCKET s = socket (AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET)
{
 //invalid socket error
}

server.sin_family = AF_INET;
server.sin_addr.s_addr= INADDR_ANY;
server.sin_port = htons (PORT);

if (bind(s, (stuct sockaddr* ) &server, sizeof(server)) == SOCKET_ERROR)
{
  //bind error
}

OVERLAPPED ovWrite;
memset(&ovWrite,0,sizeof(ovWrite));
ovWrite.offset=0;
ovWrite.OffsetHigh=0;
ovWrite.hEvent = CreateEvent (0,TRUE,0,0);

memset(buf,'\0',BUFLEN);


while (1) 
{
  if (!recvfrom(s, buf, BUFLEN,0,(struct sockaddr *) &client_address, &client_length)
  {
    fprintf(stderr, "Recvfrom Failed %d\n", WSAGetlastError());
  }

  if (!WriteFileEx(serialHandle, buf, strlen(buf),&ovWrite,NULL))
  {
     fprintf(stderr, "Error writing to COM port %d\n", GetlastError());
  }
   memset(buf,'\0', BUFLEN);
}

我正在使用 lcc 编译器,结果程序在循环的第二次迭代中崩溃,访问冲突 0xc0000005。 当我用另一个输入源(如标准输入)替换 recvfrom 时,程序运行良好。 这是一个已知问题还是 2 个函数调用根本不能同时存在?

【问题讨论】:

  • '这是一个已知问题还是两个函数调用根本不能同时存在?' - 两者都不。您的代码有错误。
  • 为什么完成例程的地址为NULL?
  • 我已经尽可能简化以消除任何错误,但仍然崩溃。您能找出上述 sn-p 中的错误吗?
  • 它为空,因为目前我不打算在完成 WriteFileEx 后做任何事情 - 继续监听更多数据。
  • 请说明 buf 和 ovWrite 在何处声明为已分配 - 重叠 I/O 调用必须为每个调用使用不同的 OVL 块,并且几乎总是使用不同的缓冲区。通常,我将缓冲区和 OVL 结构作为一个类的成员,用 new 实例化一个,用“this”指针加载 OVL 的 hEvent 参数,然后发出调用。在完成例程中,可以通过再次转换 hEvent 轻松检索实例。

标签: c windows sockets serial-port


【解决方案1】:

recvfrom() 不会将'\0' 添加到buf,所以至少() 如果recvfrom() 读取BUFLEN 字节,或者在第一次调用时,如果buf 没有在您的外部初始化代码片段,调用 strlen() 会导致 UB。此外,您的if( recvfrom( ... ) ) 是错误的,因为在成功的情况下,`recvfrom() 返回接收到的字节数,因此条件为真。相反,您可以这样做:

size_t received;

....

if ((received = recvfrom(s, buf, BUFLEN,0,(struct sockaddr *) &client_addres, &client_length)) <= 0 )
{
     fprintf(stderr, "Recvfrom Failed %d\n", WSAGetlastError());
}

if (!WriteFileEx(serialHandle, buf, received,&ovWrite,NULL))
{
     fprintf(stderr, "Error writing to COM port %d\n", GetlastError());
}

【讨论】:

  • IIRC,WriteFileEx() 需要一个有效的完成例程指针,而不是 NULL。
  • @Martin James:我刚刚搜索过:如果文件不是用FILE_FLAG_OVERLAPPED 打开的,最后一个参数lpOverlapped 可能是NULL
  • @Martin James 请忘记最后一条评论,我看的不对:-(
  • @user2667394:这也是错误的:如果出现错误()recvfrom() 返回 -1,这将被解释为“真”
  • 感谢英戈。让我做出改变。
【解决方案2】:

好的,我会宣传自己来提供答案:) 几点:

1) 与 WASRecv 不同,WriteFileEx() 总是需要一个有效的完成例程来回调异步写入调用的完成。其他信令机制,如使用 hEvent 同步,不适用于此调用。传递 NULL 可能会导致 AV。

2) 一般来说,UDP 消息可以是 0..64K 的任意长度。 recvFrom() 返回此类消息的实际长度,并且应该使用该值来处理它 - 不是任何 BUFFER_LEN 或 strlen() 调用,(请参阅 Ingo 答案:)。

3) IP 堆栈可以同时处理来自一个线程的阻塞读取和来自另一个线程的写入 - 并非绝对需要求助于异步、重叠 I/O。

4) 如果要使用重叠 I/O,强烈建议为每个调用使用不同的 OVL 块和缓冲区 - 通常,多个重叠操作可以同时排队,并且它们必须每个有自己的缓冲区/OVL。我使用 malloced 结构(或者,如果是 C++,则为新类实例)或从池中分配的 *struct 来确保每次调用。结构/实例可以在处理后在完成例程中馈送/删除/重新池化,(或从线程排队并稍后在其他地方处理/释放/删除/重新池化)。

5) 希望使用完成例程执行重叠 I/O 的线程应该看起来像一个围绕可警报等待调用的循环。 WaitForSingleObjectEx() 很方便,因为它可以在构成生产者-消费者队列基础的信号量上以可提醒的方式等待,从而允许其他线程向执行重叠 I/O 的线程发送命令/缓冲区/任何内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 1970-01-01
    • 2018-10-22
    • 2013-04-11
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多