linux网络编程之IO函数

时间:2022-01-09 08:54:26

Linux操作系统中的IO函数主要有read(),write(),recv(),send(),recvmsg(),sendmsg(),readv(),writev().

接收数据的recv()函数

#include<sys/types.h>

#include<sys/socket.h>

ssize_t recv(int s,void *buf,size_t len,int flags);

该函数从套接字s中接收数据放到缓冲区buf中,buf的长度是len,操作的方式由flags指定。因此,第一个参数s是套接口文件描述符,它是由系统调用socket()返回的,第二个参数buf是一个指针,指向接收网络数据的缓冲区,第三个参数len以字节为单位来表示缓冲区的大小,第四个参数flags用于设置接收数据的方式,其值和含义如下:

MSG_DONOTWAIT 非阻塞操作,立刻返回,不等待

MSG_ERRQUEUE 错误消息从套接字错误队列接收

MSG_OOB 接收带外数据,而不是接收一般数据

MSG_PEEK 查看数据,不进行数据缓冲区的清空

MSG_TRUNC 返回所有的数据,此时如果用户的缓冲区太小,那么数据将被截断。仅仅复制缓冲区大小的数据,其他数据会被丢失

MSG_WAITALL 等待所有消息,它告诉内核在没有读到请求的字节数之前不使读操作返回

recv()函数从内核的接收缓冲区中复制数据到用户指定的缓冲区,当内核中的数据比指定的缓冲区小时,一般情况下,会复制缓冲区中的所有数据到用户缓冲区,并返回数据的度当内核缓冲区中的数据比用户指定的多时,会将用户指定长度len的接收缓冲区中的数据复制到用户指定地址,其余的数据需要下次调用接收函数的时候再复制,内核在复制用户指定的数据之后,会销毁已经复制完毕的数据并进行调整。recv()函数通常用于TCP类型的套接字,UDP使用recvfrom()函数接收数据,不过,如果你的UDP也绑定了地址和端口,那么也可以使用recv()函数接收数据。

recv()函数的返回值是成功接收到的字节数,当返回值为-1时错误发生。可以通过查看errno获取错误码,查看错误信息。

发送数据的send()函数

#include<sys/types.h>

#include<sys/socket.h>

ssize_t send(int s,const void *buf,size_t len,int flags);

与recv()函数相反过来,send()函数将缓冲区buf中大小为len的数据,通过套接字文件描述符按照flags指定的方式发送出去,其中的参数含义与recv()函数中一致,它的返回值是成功发送的字节数。当send()函数返回值小于len时,表明缓冲区中还有部分数据没有成功发送,这时需要重新发送剩余部分的数据,通常的剩余数据发送方法是对原来buf中的数据位置进行偏移,偏移的大小为已发送成功的字节数。函数send()发生错误的时候返回值为-1,这时可以通过查看errno获取错误码,而当另一方使用正常方式关闭连接的时候其返回值为0,例如通过调用close()函数关闭连接。

函数send()只能用于套接字处于连接状态的描述符,之前必须用connect()函数或者其他函数进行连接。对于send()函数和write()函数之间的差别是表示发送方式的flag,当flag为0时。send()函数和write()函数完全一致,而且send(s,buf,len,flags)与sendto(s,buf,len,flags,NULL,0)是等价的。

接收多个缓冲区数据的readv()函数

#include<sys/uio.h>

ssize_t readv(int s,const struct iovec *vector,int count);

readv()函数可以用于接收多个缓冲区数据,它从套接字描述符s中读取count块数据放到缓冲区向量vector中。每个 iovec 描述了一块要传送的数据; 它开始于 iov_base (在用户空间)并且有 iov_len 字节长. count 参数告诉有多少 个iovec 结构. 这些结构由应用程序创建, 但是内核在调用驱动之前拷贝它们到内核空间.参数vector是一个指向向量的指针,结构struct iovec在文件<sys/uio.h>中定义:

struct iovec{

void *iov_base;//向量缓冲区的地址

size_t iov_len;//向量缓冲区的大小,以字节为单位

};

在调用readv()函数的时候必须指定iovec的iov_base的长度,将值放到成员iov_len中。

发送多个缓冲区的writev()函数

#include<sys/uio.h>

ssize_t writev(int fd,const struct iovec *vector,int count);

一个 readv 调用被期望来轮流读取指示的数量到每个缓存. 相反, writev 要收集每个缓存的内容到一起并且作为单个写操作送出它们.它的使用与readv()类似。

下面的程序代码展示了如何使用writev函数将三个独立的C字符串作为一次写操作写入标准输出:

#include 
int main(int argc,char **argv)
{
    static char part2[] = "apple apple";
    static char part3[]    = "computer";
    static char part1[] = "book book";
    struct iovec iov[3];
    iov[0].iov_base = part1;
    iov[0].iov_len = strlen(part1);
    iov[1].iov_base = part2;
    iov[1].iov_len = strlen(part2);
    iov[2].iov_base = part3;
    iov[2].iov_len = strlen(part3);
    writev(1,iov,3);
    return 0;
}

接收数据的recvmsg()函数

#include<sys/types.h>

#include<sys/socket.h>

ssize_t recvmsg(int s,struct msghdr *msg,int flags);

与recv()函数和readv()函数相比u,这个函数的使用要复杂一点,主要区别在于第二个参数,它使用了内核的一个结构体msghdr:

struct msghdr {
    void         *msg_name;  msg_name成员指向我们要发送或是接收信息的套接口地址
    socklen_t    msg_namelen; msg_namelen指明了这个套接口地址的长度
    struct iovec *msg_iov; msg_iov成员指向一个struct iovec数组
    size_t       msg_iovlen;
    void         *msg_control; msg_control指向附属数据缓冲区
    size_t       msg_controllen; msg_controllen指明了缓冲区大小
    int          msg_flags;
};

#include<sys/uio.h>

sszie_t sendmsg(int s,const struct msghdr *msg,int flags);

sendmsg()函数用于发送数据,它的使用与recvmsg()函数类似,recvmsg与sendmsg功能更为强大,当然用起来也更为复杂。

上述函数的比较:

1.函数read()/write()和readv()/writev()可以对所有的文件描述符使用;recv()/send(),recvfrom()/writeto()和recvmsg()/sendmsg()只能操作套接字描述符。

2.函数readv()/writev()和recvmsg()/sendmsg()可以操作多个缓冲区,read()/write(),recv()/send()和recvfrom()/sendto()只能操作单个缓冲区。

3.函数recv()/send(),recvfrom()/sendto()和recvmsg()/sendmsg()具有可选标志。

4.函数recvfrom()/sendto()和recvmsg()/sendmsg()可以选择对方的IP地址。

5.函数recvmsg()/sendmsg()有可以选择的控制信息,能进行高级操作。