通过fifo写入/读取数据的正确方法

时间:2011-10-23 12:24:58

标签: c unix fifo

作为家庭作业,我创建了一个模拟邮箱服务器的大项目(只能通过同一台计算机上的进程,所以通过fifo,这是一个功课)

我无法发布项目,因为它很大(有很多文件),但我可以说有时我丢失了一些数据,或者它不能保持它的完整性。

我使用这些代码片段来传输数据,有点不对吗?Network_IO是我正在谈论的功能:

#include "Network.h"

int Network_Open(const char* path,int oflag)
{
    return open(path,oflag);
}

ssize_t Network_IO(int fifo,NetworkOpCodes opcode,void* data,size_t dataSize)
{
    ssize_t retsize = 0;
    ssize_t tmpDataSize = (ssize_t)dataSize;
    errno = 0;

    if (tmpDataSize == 0) return 0;

    while ((retsize = (opcode == NetworkOpCode_Write? write(fifo,data,tmpDataSize) : read(fifo,data,tmpDataSize))) != tmpDataSize)
    {
        if (errno != EINTR) break;
    }

    return retsize;
}

Boolean Network_Send(int fifo,const void* data,size_t dataSize)
{
    return ((ssize_t)dataSize) == Network_IO(fifo,NetworkOpCode_Write,(void*)data,dataSize);
}

Boolean Network_Receive(int fifo,void* data,size_t dataSize)
{
    return ((ssize_t)dataSize) == Network_IO(fifo,NetworkOpCode_Read,data,dataSize);
}

Boolean Network_Close(int fifo)
{
    if (fifo >= 0)
        return close(fifo) == 0;
}

修改1:我用来实际测试的代码片段

Boolean Network_IO(int fifo,NetworkOpCodes opcode,void* data,size_t dataSize)
{
    ssize_t retsize = 0;
    ssize_t tmpDataSize = (ssize_t)dataSize;
    ssize_t sentDataSize = 0;
    errno = 0;

    if (tmpDataSize == 0) return True;

    while (sentDataSize < tmpDataSize)
    {
        switch(opcode)
        {
            case NetworkOpCode_Write:
                retsize = write(fifo,data + sentDataSize,tmpDataSize - sentDataSize);
                break;
            case NetworkOpCode_Read:
                retsize = read(fifo,data + sentDataSize,tmpDataSize - sentDataSize);
                break;
        }
        if (retsize < 0)
        {
            if (errno != EINTR) return False;
            else
            {
                errno = 0;
                continue;
            }
        }
        sentDataSize += retsize;
    }

    if (errno != 0)
        return False;

    return sentDataSize == tmpDataSize;
}

Boolean Network_Send(int fifo,const void* data,size_t dataSize)
{
    return Network_IO(fifo,NetworkOpCode_Write,(void*)data,dataSize);
}

Boolean Network_Receive(int fifo,void* data,size_t dataSize)
{
    return Network_IO(fifo,NetworkOpCode_Read,data,dataSize);
}

2 个答案:

答案 0 :(得分:2)

对于write案例,您的代码归结为

while ((retsize = write(fifo,data,tmpDataSize)) != tmpDataSize) { ... }

想象一下,在第一个write上,只写入一个字节。如果发生这种情况,您需要下一个write尝试从tmpDataSize-1开始推送data+1个字节。但是你现在所做的将重新发送所有内容,包括第一个字节。

在伪代码中,逻辑应该是这样的:

while (bytesLeftToSend > 0) {
 sent = write(fifo, data, bytesLeftToSend);
 if (sent == -1) {
   // report error and bail out
 }
 bytesLeftToSend -= sent;
 data += sent;
}

阅读案例也是如此。

顺便说一下,虽然有一个作业和一个?:结构,但实际上很难阅读。

答案 1 :(得分:2)

恕我直言,Network_IO()函数没有用处。它的唯一目的是“解复用”读取/写入调用的操作码,这些操作由Network_Send()和Network_Receive()函数提供给它。更好的方法是调用read()并直接在Network_Send()和Network_Receive()函数中编写。您选择的返回类型(布尔值)也很奇怪。

read()和write()的错误条件可能不同,将来可能不仅仅需要在其中一个中处理EINTR。另外:您的功能阻止,这意味着:在实际发送或接收到所需金额之前,它们不会返回。另请注意,对于管道和fifos,内核提供的缓冲区空间量非常有限,通常为1个内存页。这增加了读取器或写入器在读取或写入时阻塞的可能性,并且每个传输数据块导致(至少)两次上下文切换。

“循环完成”方法;由Mat提供的是标准的做事方式。也可以准备读/写返回零。

编辑:Mat的意思是你需要处理部分读/写:你需要从你离开的开始,发送/接收缓冲区的剩余部分。这是一个开始:

int mywrite(int fd, char *buff, size_t size)
{
int rc;
size_t done, todo;

for (done=0; done < size; ) {
    todo = size - done;
    rc = write (fd, buff+done, todo);
    switch (rc) {
    case -1: /* some read error: check it */
        switch(errno) {
        case EINTR: continue;
        /* ... maybe some other cases you need to handle */
        default: return -1;
            }
        break;
    case 0: /* (in some cases) the other side closed the connection */
        /* handle it here; possibly return error */
        break;
    default: /* the normal case */
        done += rc;
        break;
        }
    }
return done;
}