我阅读了man
个网页,我的理解是如果write()
失败并将errno
设置为EAGAIN
或EINTR
,我可以执行再次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
编译器。
答案 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)
EINTR
和EAGAIN
处理通常应略有不同。 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()
系统调用来完成工作,并处理EAGAIN
和EINTR
等内容。
如果你只有一个文件描述符,你总是可以通过FILE*
将它包装到fdopen()
中,这样你就可以将它与上面的函数一起使用。
然而,有一个陷阱:FILE*
流通常是缓冲的。如果您正在与其他程序通信并等待其响应,则可能会出现问题。这可能会使两个程序死锁,即使没有逻辑错误,只是因为fprintf()
决定推迟相应的write()
。您可以在实际需要执行fflush()
调用时关闭缓冲区或write()
输出流。