调用readn和writen的原因
在Socket编程中,调用readn和writen的主要原因是确保在网络通信过程中数据的可靠性和完整性。这是因为在网络通信中,数据传输往往是分片的,也就是说,发送方可能会将一个大的数据包分成多个小的数据包进行发送,而接收方可能需要多次接收这些小的数据包并组装成完整的数据包。
如果在接收数据时,使用传统的read函数,可能会存在以下问题:
-
读取的数据可能不完整,因为数据可能会被分成多个小包发送,而read函数一次只能读取一个小包。
-
读取的数据可能包含多个数据包,因为多个小包可能会在底层网络协议中合并成一个大的数据包,而read函数一次只能读取一个数据包。
-
读取的数据可能包含错误数据,因为在网络传输过程中,数据可能会出现丢失、重复、乱序等问题。
类似地,如果使用传统的write函数发送数据,也可能存在类似的问题。
为了解决这些问题,可以使用readn和writen函数。这两个函数都是自定义的函数,通常会在底层调用read和write函数,但是会进行额外的处理,确保数据的完整性和可靠性。具体而言,readn函数会反复调用read函数,直到读取到指定长度的数据;而writen函数会反复调用write函数,直到写入指定长度的数据。这样就可以避免上述问题的出现,使得数据的传输更加可靠。
writen和readn函数
ssize_t readn(int fd,void*buffer,size_t count);
ssize_t writen(int fd,void*buffer,size_t count);
readn()参数含义:
fd:表示要读取数据的文件描述符,可以是打开文件的文件描述符,也可以是网络套接字的文件描述符。
buf:表示一个缓冲区,用于存放读取的数据。
count:表示要读取的字节数。
writen()参数含义如下:
fd:表示文件描述符,指向需要写入数据的文件或者套接字。
buf:表示指向缓冲区的指针,指向需要写入的数据。
count:表示需要写入的数据长度,以字节为单位。
函数readn()和writen()的参数与read()和write()相同,但是这两个函数使用的循环来重新启用这些系统调用,确保了请求的字节数总是能够全部得到运输(除非出现错误或者在read()中检测到了文件结尾符。
write函数的返回值是:
如果返回值大于等于0,表示成功写入了指定字节数的数据。
如果返回值等于0,表示写入结束,即写入的数据长度为0。
如果返回值小于0,表示写入失败。此时,可以通过查看errno变量来获取具体的错误原因。常见的错误原因包括:EINTR(写入被信号中断)、EAGAIN(写入被阻塞)等。
read函数的返回值是:
返回一个正整数时,它表示已经成功读取了指定数量的字节。
如果返回值为0,则表示到达文件末尾或者网络连接已经关闭。
返回-1时,表示读取出错,此时可以通过errno全局变量获取错误码。常见的错误码包括EAGAIN/EWOULDBLOCK(表示读取操作会阻塞)、EINTR(表示读取操作被信号中断)、EBADF(表示文件描述符无效)、EINVAL(表示参数无效)等。
readn()和writen()的实现
//从文件描述符fd中读取n个字节的数据
ssize_t Readn(int fd,void*buffer,size_t n)
{
ssize_t numRead;//本次读取的数据
ssize_t totRead;//已经成功读取的数据
char* buf;//缓冲区指针
buf=(char*)buffer;
//循环读取数据,直到读取到n个字节的数据
for(totRead=0;totRead<n;)
{
numRead=read(fd,buf,n-totRead);//从fd中读取数据
if(numRead==0)//如果读取到文件末尾,返回已读取的字节数
{
return totRead;
}
if(numRead==-1)//如果读取出错
{
if(errno==EINTR)//如果是由于中断导致的错误,继续读取
{
continue;
}
else//其他错误。返回-1
return -1;
}
totRead+=numRead;//更新已读取的字节数
buf+=numRead;//更新缓冲区指针
}
return totRead;//返回已读取的字节数
}
//从内存缓冲区buf中向文件描述符fd所代表的文件中写入n个字节序
ssize_t Writen(int fd, const void *buffer, size_t n)
{
ssize_t numWritten; //本次写入的字节数
size_t totWritten; //记录已经成功写入的字节数
const char *buf;
buf = (char *)buffer; //指向缓冲区的下一个未写入的位置
//用循环保证写入n个字节的数据
for (totWritten = 0; sWritten < n;)
{
numWritten = write(fd, buf, n - totWritten);
if (numWritten <= 0)
{
if (numWritten == -1 && errno == EINTR)//为了在信号中断的情况下,能够重新尝试写入
continue;
else
return -1;
}
//已经成功写入的字节数增加了numWritten
totWritten += numWritten;
//指针buf向后移动numWritten个字节
buf += numWritten;
}
return totWritten;
}