Linux网络编程中,针对套接字读写的函数有很多,下面介绍一些函数的常见用法;这里介绍的函数都是linux系统中的特有函数,与C标准库中的IO函数存在一定的区别。
1、read() 和 write()函数
read() 和 write() 函数是Linux中最基本的读写函数,可以用于各种数据的读写。
/* 返回值:成功则为读取或写入的字节个数,失败为-1;
* 定义:
*/
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbyte);
ssize_t write(int fd, const void* buf, size_t nbytes);
/* 说明
* fd 为描述符,可以是文件描述符,也可以是套接字描述符;
* buf 用于存储读取或写入的数据;
* nbyte 记录字节数;
* 根据不同的应用情况,返回值可能小于请求的字节数
*/
一般来说,对于常规文件的读写不会阻塞,函数一定会在有限的时间内返回,但是从终端设备或网络读取时就不一定了,如果网络通信消息一直没有到达,那么读取函数就一直阻塞等待,终端还没有进行输入时,也是处于阻塞状态。返回的字节数包含换行符。
阻塞和非阻塞read/write ;
2、recv() 和 send() 函数
这两个函数类似于read() 和 write() 函数,但是这两个函数只用于套接字相关的数据处理。
/* 返回值:成功则为读取或写入的字节个数,失败为-1;
* 定义:
*/
#include <sys/socket.h>
ssize_t recv(int fd, void *buf, size_t n, int flags);
ssize_t send(int fd, const void *buf, size_t n, int flags);
/* 前面3个参数与read/write中参数一样,flags的值可为0或下面的值:
* 1、MSG_DONTROUTE:绕过路由表查找,从直接连接的本机网络上处理数据;
* 2、MSG_DONTWAIT:仅对本操作执行非阻塞;
* 3、MSG_OOB:即将发送或接受带外数据;
* 4、MSG_PEEK:查看已可读取的数据,且系统在recv或recvfrom返回后不丢弃这些数据;
* 5、MSG_WAITALL:尚未读取请求数目的字节之前读取操作不要返回;
*/
3、readv() 和 writev() 函数
read() 和 write() 函数在处理数据时数据是连块的,即处理的数据是保存在一个连续的地址空间中的,但是有时候需要一次处理多个在不同地址空间的数据,readv函数可以将数据的输入分散到多个缓冲区中,称为分散读;writev函数可以将多个缓冲区的输出数据写入一个连续的缓冲区内,称为集中写;
/* 返回值:成功则为读入或写出的字节数,出错则为-1;
* 定义:
*/
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec{
void *iov_base;
size_t iov_len;
}
/* 说明:
* 参数fd为文件或套接字描述符,iovcnt为结构指针iov中元素个数,最大一般不超过1024;
* 结构iovec表示一个数据元素,iov_base为数据起始地址,iov_len为数据区大小;
* 数组指针iov中有几个元素就有几块分散的缓冲区地址块;
* 两个函数可以用于任何描述符。不仅限于套接字;
*/
4、recvmsg() 和 sendmsg() 函数
这两个函数只用于套件字的数据处理,是最通用的两个函数,包含前面的几个函数具有的各项功能。
/* 返回值:成功则为读入或写出的字节数,出错则为-1;
* 定义:
*/
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr{
void *msg_name; /*protocol address*/
socklen_t msg_namelen; /*size of protocol address*/
struct iovec *msg_iov; /*scatter/gather array*/
int msg_iovlen; /*#elements in msg_iov*/
void *msg_control; /*ancillary data (cmsghdr struct)*/
socklen_t msg_controllen; /*length of ancillary data */
int msg_flags; /*flags returned by recvmsg()*/
}
/* 说明:
* sockfd为套接字描述符,msg为结构参数指针,flags为标志;
* msghdr中msg_name,msg_namelen为未连接的套接字地址及其长度;
* 对于已连接的套接字msg_name直接设为NULL;
* msgiov,msg_iovlen与readv函数第2,3个参数类似;
* msg_control,msg_controllen为辅助数据,与结构cmsghdr相关;
* msg_flags为标志,只有函数recvmsg使用;
* flags用于输入一定的控制信息,而msg_falgs一般用于返回标志信息;
* 函数send,recv;sendto,recvfrom,sengmsg,recvmsg都存在flags标志,部分标志在
* recv函数介绍时已列出,其它参考UNIX网络编程卷1;
*/
5组IO函数比较,yes表示符合说明,空格表示不符合。
函数 | 任何描述符 | 仅套接字描述符 | 单个读写缓冲区 | 分散/集中读写 | 可选标志 | 可选对端地址 | 可选控制信息 |
---|---|---|---|---|---|---|---|
read,write | yes | yes | |||||
readv,writev | yes | yes | |||||
send,recv | yes | yes | yes | ||||
sendto,recvfrom | yes | yes | yes | yes | |||
sendmsg,recvmsg | yes | yes | yes | yes | yes |
上面介绍的几组IO函数都是围绕描述符工作的,通常是作为UNIX内核中的系统调用实现;执行IO的另一种方式是使用标准IO函数库,由ANSI C标准规范,便于移植到支持C的非Unix系统上。
标准IO函数也可以用于套接字上:通过调用fdopen从任何描述符创建一个标准IO流;相反可以通过fileno获取任何一个给定标准IO流的描述符。
标准IO函数执行以下3类缓冲:
1、完全缓冲,只在下列情况下才发生IO(数据输出):缓冲区满,进程显式调用fflush,进程调用exit终止自身;标准缓冲区大小一般为8192字节。
2、行缓冲,只在下列情况下才发生IO:碰到一个换行符,进程调用fflush,进程调用exit终止自身。
3、不缓冲,每次调用标准IO输出函数都发生IO。
标准IO函数大多数Unix实现遵循以下规则:
- 标准错误输出总是不缓冲;
- 标准输入标准输出完全缓冲,除非他们指代终端设备(这种情况下行缓冲);
- 所有其他IO流都是完全缓冲,除非他们指代终端设备(这种情况下行缓冲);
因为套接字并不是终端设备,因此在用标准IO处理套接字数据IO时,需要考虑缓冲问题,然而这样可能会引起一些潜在问题,所以一般情况下尽量避免在套接字上使用标准IO函数库。