C 是否有更干净的方法可靠地使用write()函数?
我阅读了C 是否有更干净的方法可靠地使用write()函数?,c,linux,sockets,gcc,C,Linux,Sockets,Gcc,我阅读了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:
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;
}
ret=0;
while(ret!=计数){
写入计数=写入(connFD,(char*)buf+ret,计数);
while(写入计数<0){
开关(错误号){
案例中心:
案例伊根:
写入计数=写入(connFD,(char*)buf+ret,count-ret);
打破
违约:
printf(“\n ret的值为:%d\n”,ret);
printf(“\n错误号为:%d\n”,errno);
断言(0);
}
}
ret+=写入计数;
}
我正在套接字上执行read()
和write()
,并像上面一样处理read()
。我使用的是带有gcc
编译器的Linux。这里有一个“不要重复自己”的问题-不需要两个单独的编写调用,也不需要两个嵌套循环
我的正常循环看起来像这样:
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
for(int n=0;n
EINTR
和EAGAIN
处理通常应该略有不同EAGAIN
始终是表示套接字缓冲区状态的某种暂时性错误(或者更准确地说,可能是您的操作可能会阻塞的)
一旦你点击了一个EAGAIN
,你可能会想睡一会儿或者将控制返回到一个事件循环(假设你正在使用一个)
使用EINTR
时,情况有所不同。如果您的应用程序不停地接收信号,那么这可能是您的应用程序或环境中的一个问题,因此我倾向于使用某种内部eintr\u max
计数器,因此我不会陷入理论状态,继续无限循环eintr
Alnitak的答案(对于大多数情况来说足够了)也应该是将errno
保存在某个地方,因为它可能会被perror()
(尽管为了简洁起见可能会被省略)。是的,有更干净的方法来使用write()
:将文件*
作为参数的写函数类。最重要的是,fprintf()
和fwrite()
。在内部,这些库函数使用write()
如果您只有一个文件描述符,则始终可以通过fdopen()
将其包装成文件*
,以便将其与上述函数一起使用
然而,有一个陷阱:文件*
流通常是缓冲的。如果您正在与其他程序通信并等待其响应,则可能会出现问题。这可能会使两个程序都死锁,即使没有逻辑错误,这仅仅是因为fprintf()
决定将相应的write()
延迟一点。您可以在实际需要执行write()
调用时关闭缓冲或fflush()
输出流。在EAGAIN
的情况下,我更喜欢poll
描述符,而不是无缘无故地忙着循环和烧掉CPU。这是一种非阻塞写入的“阻塞包装器”,我使用:
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;
ssize\u t write=0;
while(写入 如果(轮询和pfd,1,-1)检查EAGAIN表示您使用的是非阻塞套接字。我可能会将其抽象为一个单独的函数,该函数提供写入量以及是否失败的统计信息。@我使用的是阻塞套接字。我不知道只有在使用非阻塞套接字时才需要检查EAGAIN。这就是我提到的原因为了以防万一,我只检查了非阻塞套接字上的EAGAIN,因为这就是我理解它工作的方式。如果有人知道它发生在阻塞套接字上,请大声说出来。@Ioan您是正确的。它不会发生在阻塞套接字上。您可以使用select()调用,当套接字准备好写入时会立即通知您。好吧,我看这更优雅。什么是“不要重复自己的问题”?@AnkurVj它不必要地重复相同的代码-DRY(不要重复自己)这是本书中采用的一个原则。它意味着避免两次编写相同的代码。在这里,只要一次就足够了,您就可以编写两次write
语句。在EAGAIN/ewoldblock的情况下,只烧掉CPU是不合适的。
此状态的解决方案取决于对等方读取的内容。它不会在i之后消失t正确的程序是select()
直到套接字变为可写。我同意@EJP。如果启用了SA_RESTART
标志,就不会有EINTR
,如果使用多路复用器,就知道什么时候适合读取。便携式应用程序应该检查EAGAIN
和ewoodblock
,如man
页面中所述:“POSIX.1-2001允许在这种情况下返回任何一个错误,并且不要求这些常量具有相同的值,因此便携式应用程序应该检查这两种可能性。“EAGAIN
不一定意味着对O_NONBLOCK
文件描述符的阻塞调用,它依赖于实现。@Barracuda您最后的