是否有更清晰的方法可靠地使用write()函数?

时间:2012-04-26 19:34:25

标签: c linux sockets gcc

我阅读了man个网页,我的理解是如果write()失败并将errno设置为EAGAINEINTR,我可以执行再次write(),所以我想出了以下代码:

  ret = 0;
  while(ret != count) {
    write_count = write(connFD, (char *)buf + ret,  count);
    while (write_count < 0) {
      switch(errno) {
        case EINTR:
        case EAGAIN:
          write_count = write(connFD, (char *)buf + ret,  count -ret);
          break;
        default:
          printf("\n The value of ret is : %d\n", ret);
          printf("\n The error number is : %d\n", errno);      
          ASSERT(0);
      }
    }
    ret += write_count;
  }

我在套接字上执行read()write()并按上述方式处理read()。我正在使用Linux,gcc编译器。

4 个答案:

答案 0 :(得分:6)

你有一点“不要重复自己”的问题 - 不需要两次单独调用write,也不需要两次嵌套循环。

我的正常循环看起来像这样:

for (int n = 0; n < count; ) {
    int ret = write(fd, (char *)buf + n, count - n);
    if (ret < 0) {
         if (errno == EINTR || errno == EAGAIN) continue; // try again
         perror("write");
         break;
    } else {
        n += ret;
    }
}

// if (n < count) here some error occurred

答案 1 :(得分:3)

EINTREAGAIN处理通常应略有不同。 EAGAIN总是某种表示套接字缓冲区状态的瞬态错误(或者更确切地说,可能是您的操作可能会阻塞)。

一旦你点击EAGAIN,你可能想要睡一会儿或将控制权返回给事件循环(假设你正在使用)。

EINTR情况有点不同。如果您的应用程序不停地接收信号,那么它可能是您的应用程序或环境中的问题,因此我倾向于使用某种内部eintr_max计数器,因此我不会陷入理论情况我只是继续无限循环EINTR

Alnitak的答案(对大多数情况来说足够了)也应该在某个地方保存errno,因为它可能被perror()破坏(尽管为简洁起见可能已经省略了)。

答案 2 :(得分:2)

我希望在poll的情况下EAGAIN描述符,而不是仅仅忙于循环和烧毁CPU而没有充分的理由。对于我使用的非阻塞write,这是一种“阻止包装”:

ssize_t written = 0;

while (written < to_write) {
    ssize_t result;
    if ((result = write(fd, buffer, to_write - written)) < 0) {
        if (errno == EAGAIN) {
            struct pollfd pfd = { .fd = fd, .events = POLLOUT };
            if (poll(&pfd, 1, -1) <= 0 && errno != EAGAIN) {
                break;
            }
            continue;
        }
        return written ? written : result;
    }
    written += result;
    buffer += result;
}
return written;

请注意,除了返回值之外,我实际上并未检查poll的结果;我认为如果描述符存在永久性错误,则以下write将失败。

您可能希望将EINTR包含为可重试错误,只需将其添加到EAGAIN的条件中,但我更喜欢它实际中断I / O.

答案 3 :(得分:0)

是的,有更简洁的方法可以使用write():以FILE*为参数的写函数类。最重要的是,fprintf()fwrite()。在内部,这些库函数使用write()系统调用来完成工作,并处理EAGAINEINTR等内容。

如果你只有一个文件描述符,你总是可以通过FILE*将它包装到fdopen()中,这样你就可以将它与上面的函数一起使用。

然而,有一个陷阱:FILE*流通常是缓冲的。如果您正在与其他程序通信并等待其响应,则可能会出现问题。这可能会使两个程序死锁,即使没有逻辑错误,只是因为fprintf()决定推迟相应的write()。您可以在实际需要执行fflush()调用时关闭缓冲区或write()输出流。