TCP/IP网络编程 (十三):多种I/O函数

send & recv函数

Linux中的send & recv


send函数:

recv函数:


send函数和recv函数的最后一个参数是收发数据的可选项。利用位或运算同时传递多个信息。



MSG_OOB:发送紧急消息

就例如医院的急诊病人优先处理以及单独的急诊室。

MSG_OOB可选项用于创建特殊发送方法俄通道以发送紧急消息。


MSG_OOB使用示例:

服务器端:

/* MSG_OOB:紧急接收使用示例*/ 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>

#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);

int acpt_sock;
int recv_sock;

int main(int argc,char *argv[])
{
	struct sockaddr_in recv_adr, serv_adr;
	int str_len , state;
	socklen_t serv_adr_sz;
	struct sigaction act;
	char buf[BUF_SIZE];
	if (argc != 2) {
		printf("Usage: %s <port> \n",argv[0]);
		exit(1);
	}

	act.sa_handler = urg_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	acpt_sock = socket(PF_INET,SOCK_STREAM,0);
	memset(&recv_adr,0,sizeof(recv_adr));
	recv_adr.sin_family = AF_INET;
	recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	recv_adr.sin_port = htons(atoi(argv[1]));

	if (bind(acpt_sock,(struct sockaddr*)&recv_adr,sizeof(recv_adr)) == -1)
		error_handling("bind() error!");
	listen(acpt_sock,5);

	serv_adr_sz = sizeof(serv_adr);
	recv_sock = accept(acpt_sock,(struct sockaddr*)&serv_adr,&serv_adr_sz);

	fcntl(recv_sock,F_SETOWN,getpid());		//getpid返回调用此函数的进程ID,此语句指定当前进程为处理SIGURG信号的主体
	state = sigaction(SIGURG,&act,0);		//收到MSG_OOB紧急消息时,操作系统产生SIGURG信号,并调用Handler

	while((str_len=recv(recv_sock,buf,sizeof(buf),0)) != 0)
	{
		if (str_len == -1)
			continue;
		buf[str_len] = 0;
		puts(buf);
	}
	close(recv_sock);
	close(acpt_sock);
	return 0;
}

void urg_handler(int signo)
{
	int str_len;
	char buf[BUF_SIZE];
	str_len = recv(recv_sock,buf,sizeof(buf)-1,MSG_OOB);	//调用紧急接收
	buf[str_len] = 0;
	printf("Urgent message: %s \n",buf);
}

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

客户端:

/* MSG_OOB:紧急发送使用示例*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc,char *argv[])
{
	int sock;
	struct sockaddr_in recv_adr;
	if (argc != 3) {
		printf("Usage: %s <IP> <port>\n",argv[0]);
		exit(1);
	}

	sock = socket(PF_INET,SOCK_STREAM,0);
	memset(&recv_adr,0,sizeof(recv_adr));
	recv_adr.sin_family = AF_INET;
	recv_adr.sin_addr.s_addr= inet_addr(argv[1]);
	recv_adr.sin_port = htons(atoi(argv[2]));

	if (connect(sock,(struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1)
		error_handling("connect() error!");

	write(sock,"123",strlen("123"));
	send(sock,"4",strlen("4"),MSG_OOB);		//紧急传输
	write(sock,"567",strlen("567"));
	send(sock,"890",strlen("890"),MSG_OOB);	//紧急传输
	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

运行结果:


运行结果不符预期。这是因为TCP不存在真正意义上的"带外数据“。实际上,MSG_OOB中的OOB是指Out-of-band,而”带外数据“的含义是:"通过完全不同的通信路径传输的数据"。

真正意义上的Out-of-band需要通过单独的通信路径告诉传输数据。只利用TCP的紧急模式进行传输。


紧急模式工作原理

下面给出设置MSG_OOB可选项状态下的数据传输过程

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

                                            


字符0右侧偏移量为3的位置存有紧急指针。紧急指针指向紧急消息的下一个位置(偏移量加1)。


实际只用一个字节表示紧急消息信息。通过用于数据传输的TCP数据包(段)的结构看得清楚。

                                     


TCP头含有如下两种消息:
URG = 1:载有消息的数据包

URG指针:紧急指针位于偏移量为3的位置


紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息。


检查输入缓冲

同时设置MSG_PEEK选项和MSG_DONTWAIT选项,以验证输入缓冲中是否存在接收的数据。


设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。因此,该选项通常与

MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读数据存在与否的函数。

示例了解二者含义:

peek_send.c

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

int main(int argc, char *argv[])
{
	int sock;
	struct sockaddr_in send_adr;
	if(argc != 3) {
		printf("Usage: %s <IP> <port> \n",argv[0]);
		exit(1);
	}

	sock = socket(PF_INET,SOCK_STREAM,0);
	memset(&send_adr,0,sizeof(send_adr));
	send_adr.sin_family = AF_INET;
	send_adr.sin_addr.s_addr = inet_addr(argv[1]);
	send_adr.sin_port = htons(atoi(argv[2]));

	if (connect(sock,(struct sockaddr*)&send_adr,sizeof(send_adr)) == -1)
		error_handling("connect() error!");

	write(sock,"123",strlen("123"));
	close(sock);
	return 0;
}

下面给出使用MSG_PEEK和MSG_DONTWAIT选项的结果:

peek_recv.c

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#define BUF_SIZE 30
void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

int main(int argc,char *argv[])
{
	int acpt_sock, recv_sock;
	struct sockaddr_in acpt_adr,recv_adr;
	int str_len, state;
	socklen_t recv_adr_sz;
	char buf[BUF_SIZE];
	if (argc != 2) {
		printf("Usage: %s <port>\n",argv[0]);
		exit(0);
	}

	acpt_sock = socket(PF_INET,SOCK_STREAM,0);
	memset(&acpt_adr,0,sizeof(acpt_adr));
	acpt_adr.sin_family = AF_INET;
	acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	acpt_adr.sin_port = htons(atoi(argv[1]));

	if (bind(acpt_sock,(struct sockaddr*)&acpt_adr, sizeof(acpt_adr)) == -1)
		error_handling("bind() error");
	listen(acpt_sock,5);

	recv_adr_sz = sizeof(recv_adr);
	recv_sock = accept(acpt_sock,(struct sockaddr*)&recv_adr,&recv_adr_sz);

	while(1)
	{
		str_len = recv(recv_sock,buf,sizeof(buf)-1,MSG_PEEK | MSG_DONTWAIT);
		//MSG_PEEK和MSG_DONTWAIT保证即使不存在待读取数据也不会进入阻塞状态
		if (str_len > 0)
			break;
	}

	buf[str_len] = 0;
	printf("Buffering %d bytes: %s \n",str_len,buf);

	str_len = recv(recv_sock,buf,sizeof(buf)-1,0);
	buf[str_len] = 0;
	printf("Read again: %s \n",buf);
	close(acpt_sock);
	close(recv_sock);
	return 0;
}

运行结果:


通过运行结果验证:

仅发送一次的数据被读取了两次,因为第一次调用recv函数时设置了MSG_PEEK可选项,以上就是MSG_PEEK可选项的功能。


readv & writev函数

readv & writev 函数有助于提高数据通信效率。


使用readv & writev函数

readv & writev 函数的功能可概括如下:对数据进行整合传输及发送的函数。

writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。


writev函数:


第二个参数数组iovec结构体的声明如下:

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

函数的使用方法:

                                

第三个参数为2,所以浏览两个iovec结构体变量,发送这这些指针指向的缓冲数据。


示例:writev.c

#include<stdio.h>
#include<sys/uio.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(1,vec,2);	//输出到标准输出
	puts(" ");
	printf("Write bytes: %d \n",str_len);
	return 0;
}

运行结果:



readv函数:


示例:

#include<stdio.h>
#include<sys/uio.h>

#define BUF_SIZE 100

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  = 5;			//第一个数据保存在buf1,长度为5
	vec[1].iov_base = buf2;
	vec[1].iov_len  = BUF_SIZE;		//第二个数据保存在buf2,长度为100

	str_len = readv(0,vec,2);
	printf("Read bytes: %d \n",str_len);
	printf("First message: %s \n",buf1);
	printf("Second message: %s \n",buf2);
	return 0;
}

运行结果:

由结果可知,第8行的vec数组保存了数据。


合理使用readv & writev函数

当需要传输的数据分别位于不同的缓冲时,需要多次调用write函数,此时就可以通过1次writev函数调用替代操作,会提高效率.....

减少函数调用次数也能提高性能,更大的意义在于减少数据包个数。


猜你喜欢

转载自blog.csdn.net/amoscykl/article/details/80296308