网编(12):多种读写函数

send & recv 函数

#include <sys/socket.h>
ssize_t send(int sockfd, canst void * buf, size_t nbytes, int flags);
//成功时返回发送的字节数,失败时返回-1。
#sockfd 表示与数据传输对象的连接的套接字文件描述符。
#but    保存待传输数据的缓冲地址值。
#nbytes 待传输的字节数。
#flags  传输数据时指定的可选项信息。
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);
//成功时返回接收的字节数(收到EDF 时返回0)' 失败时返回-1 。
#sockfd 表示数据接收对象的连接的套接字文件描述符。
#buf    保存接收数据的缓冲地址值。
#nbytes 可接收的最大字节数。
#flags  接收数据时指定的可选项信息。

send函数和recv函数的最后一个参数是收发数据时的可选项。该可选项可利用位或( bit OR )运算( l 运算符) 同时传递多个信息。通过表整理可选项的种类及含义。

另外,不同操作系统对上述可选项的支持也不同。因此, 为了使用不同可选项,各位需要对实际开发中采用的操作系统有一定了解。下面选取表中的一部分(主要是不受操作系统差异影响的)进行详细讲解。

MSG 00B: 发送紧急消息

MSG_OOB可选项用于发送“带外数据“紧急消息。假设医院里有很多病人在等待看病, 此时若有急诊患者该怎么办?
“当然应该优先处理。”
如果急诊患者较多,需要得到等待看病的普通病人的谅解。正因如此, 医院一般会设立单独的急诊室。需紧急处理时,应采用不同的处理方法和通道。MSG_OOB可选项就用于创建特殊发送方法和通道以发送紧急消息。

发送端:

send(sock, "890", strlen("890"), MSG_OOB);

接收端:

act.sa handler=urg_handler;
recv_sock=accept(acpt_sock, (struct sockaddr*)&serv_adr, &serv_adr_sz);

fcntl(recv_sock, F _SETOWN, getpid());
state=sigaction(SIGURG, &act, 0);

void urg_handler(int signo)
{
	int str_len;
	char buf[BUF_SIZE];
	str_len=recv(recv_sock, buf, sizeof(buf)-1, M5G_OOB);
	buf[str_len]=0;
	printf("Urgent message: %s \n", buf);
}

fcntl 函数用于控制文件描述符,但上述调用语句的含义如下:
“将文件描述符recv_sock指向的套接字拥有者(F_SETOWN) 改为把getpid 函数返回值用作1D 的进程。”

上述描述中的“处理SIGURG信号”指的是“调用SIGURG信号处理函数" 。但之前讲过,多个进程可以共同拥有1个套接字的文件描述符。例如,通过调用fork函数创建子进程并同时复制文件描述符。此时如果发生SIGURG信号,应该调用哪个进程的信号处理函数呢?可以肯定
的是,不会调用所有进程的信号处理函数(想想就知道这会引发更多问题)。因此,处理SIGURG信号时必须指定处理信号的进程,而getpid函数返回调用此函数的进程1D 。上述调用语句指定当前进程为处理SIG口也信号的主体。该程序中只创建了1个进程, 因此,理应由该进程处理SIGURG信号。

 

紧急模式工作原理

由于TCP不存在真正意义上的"带外数据" 。实际上, MSG_OOB中的00B是指Out-of-band, 而“带外数据”的含义是:
“通过完全不同的通信路径传输的数据。”
即真正意义上的Out-of-band需要通过单独的通信路径高速传输数据,但TCP不另外提供,只利用TCP的紧急模式(Urgent mode ) 进行传输。

send(sock, "890", strlen("890"), MSG_OOB);

上面的语句会在缓冲区形成这样的结构:

如果将缓冲最左端的位置视作偏移量为0, 字符0保存于偏移量为2的位置。另外, 字符0右侧偏移量为3 的位置存有紧急指针( UrgentPointer )。紧急指针指向紧急消息的下一个位置(偏移量加1), 同时向对方主机传递如下信息:
"紧急指针指向的偏移量为3之前的部分就是紧急消息!”
也就是说实际只用1个字节表示紧急消息信息即——(偏移量为2的“0”)

用于传输数据的TCP数据包(段)的结构。

  • URG= l: 载有紧急消息的数据包
  • URG指针:紧急指针位于偏移量为3 的位置

“通过MSG OOB可选项传递数据时只返回1个字节?而且也不是很快啊!"

的确!令人遗憾的是,通过MSGOOB可选项传递数据时不会加快数据传输速度,而且读取数据时也只能读1个字节。

 

 

检查输入缓冲

同时设置MSG_PEEK选项和MSG_DONTWAIT选项,以验证输入缓冲中是否存在接收的数据。设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。因此,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数。

while(1)
{
	str_len=recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK | MSG_OONTWAIT);
	if(str_len>0 )
		break;
}

使用readv & writev 函数

readv & writev函数的功能可概括如下:
“对数据进行整合传轮及发送的函数。”
也就是说,通过writev 函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这2个函数可以减少I/O函数的调用次数。下面先介绍writev 函数。

#include <sys/uio.h>
ssize t writev(int filedes, const struct iovec * iov, int iovcnt);
//成功时返回发送的字节数,失败时返回-1 。

#filedes 表示数据传输对象的套接字文件描述符。但该函数并不只限于套接字,因此,可以像read函数一样向其传递文件或标准输出描述符。
#iov iovec结构体数组的地址值,结构体 iovec 中包含待发送数据的位置和大小信息。
#iovcnt 向第二个参数传递的数组长度。

上述函数的第二个参数中出现的数组iovec结构体的声明如下。

struct iovec
{
	void * iov_base;  //缓冲地址
	size_t iov_len;  //缓冲大小
}

可以看到, 结构体iovec由保存待发送数据的缓冲( char型数组) 地址值和实际发送的数据长度信息构成。给出上述函数的调用示例前, 先通过图了解该函数的使用方法。

  • 第一个参数:1是文件描述符,因此向控制台输出数据,
  • 第二个参数:ptr是存有待发送数据信息的iovec数组指针
  • 第三个参数:为2, 因此,从ptr指向的地址开始,共浏览2个iovec结构体变量,发送这些指针指向的缓冲数据。

接下来仔细观察图中的iovec结构体数组。

  • ptr[0] (数组第一个元素)的iov_base指向以A开头的字符串,同时iov_len为3 , 故发送ABC 。
  • ptr[1] (数组的第二个元素)的iov_base指向数字 1, 同时iov_len为4 , 故发送1234 。

实例代码:

#include <stdio.h>
#include <sys/uio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
	struct iovec vec[2];
	char buf1[]="ABCDEFG";
	char buf2[]="1234567";
	int str_len;
	vec[0].iov_base=buf1;
	vec[0].iov_len=3;
	vec[1].iov_base=buf2;
	vec[1].iov_len=4;
	str_len=writev(STDOUT_FILENO, vec, 2);
	puts("");
	printf("Write bytes: %d \n", str_len);
	return 0;
}

运行结果:

$ ./a.out 
ABC1234
Write bytes: 7 

#include <sys/uio.h>
ssize t readv(int filedes, const struct iovec * iov, int iovcnt);
//成功时返回接收的字节数,失败时返回-1 。

#filedes 传递接收数据的文件(或套接字)描述符。
#iov     包含数据保存位置和大小信息的ovec结构体数组的地址值。
#iovcnt 第二个参数中数组的长度。

实例代码:

#include <stdio.h>
#include <sys/uio.h>
#include <unistd.h>
#define BUF_SIZE 64
int main(int argc, char *argv[])
{
	struct iovec vec[2];
	char buf1[BUF_SIZE]={0};
	char buf2[BUF_SIZE]={0};
	int str_len;
	vec[0].iov_base=buf1;
	vec[0].iov_len=3;
	vec[1].iov_base=buf2;
	vec[1].iov_len=BUF_SIZE;
	str_len=readv(STDIN_FILENO, vec, 2);
	printf("Read bytes: %d\n", str_len);
	printf("First message: %s \n", buf1);
	printf("Second message: %s \n", buf2);
	return 0;
}

运行结果:

$ ./a.out 
123456789
Read bytes: 9
First message: 123 
Second message: 456789

合理使用readv & writev 函数

哪种情况适合使用readv和writev函数? 实际上,能使用该函数的所有情况都适用。例如, 需要传输的数据分别位于不同缓冲(数组)时,需要多次调用write函数。此时可以通过1 次writev函数调用替代操作,当然会提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用read函数,而是利用1 次readv函数就能大大提高效率。

即使仅从C语言角度看,减少函数调用次数也能相应提高性能。但其更大的意义在于减少数据包个数。假设为了提高效率而在服务器端明确阻止了Nagle算法。其实writev函数在不采用Nagle算法时更有价值,如图所示。

上述示例中待发送的数据分别存在3个不同的地方,此时如果使用write 函数则需要3次函数调用。但若为提高速度而关闭了Nagl哆垃~. 则极有可能通过3个数据包传递数据。反之,若使用writev函数将所有数据一次性写入输出缓冲,则很有可能仅通过1个数据包传输数据。所以wri tev 函数和readv函数非常有用。

再考虑一种情况: 将不同位置的数据按照发送顺序移动(复制)到l个大数组,并通过1 次write函数调用进行传输。这种方式是否与调用writev函数的效果相同?当然! 但使用writev函数更为便利。因此,如果遇到writev 函数和readv函数的适用情况,希望各位不要错过机会。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值