网络编程——进阶篇

网络超时 :网络通信中,好多超时会使进程阻塞,这里共有三种处理方法
方法一:设置sock的超时属性SO_RCVTIMEO
参考代码:struct timeval tv;
tv.tv_sec=5
tv.tv_usec=0 
setsockopt(fd,SOL_SOCKET,SO_RECVTIMEO,&tv,sizeof(tv))//设置5s后超时
recv/recvfrom //5s后若还在阻塞,则返回,同时置errno=11
方法二:并发IO的select,设置超时属性
参考代码:struct timeval tv;
fd_set rdfs;
tv.tv_sec=5
tv.tv_usec=0
FD_ZERO(&rdfs);
FD_SET(sockfd,&rdfs);
if(select(sockfd+1,&rdfs,NULL,NULL,&tv)>0)
{recv/recvfrom}
方法三、SIGALRM信号捕捉(最常用)
在读操作前(可能阻塞),启动定时器,过一定的时刻发送SIGALRM信号,设置信号属性,信号函数处理完后,从阻塞的下一句开始执行
信号处理函数:void handler(int signo){return }
设置信号属性:struct sigaction act //定义sigaction 结构体变量,临时保存信号属性
signaction(SIGALRM,NULL,&act)//读取当前属性到act中
act.sa_handler=handler;
act.sa_flags &=~SA_RESTART;//清零,设置信号函数执行完后,从系统调用(读时阻塞)的下一句开始执行
sigaction(SIGALRM,&act,NULL);
使用:alarm(5)
if(recv()<0)如果在这里阻塞5s,则系统发出SIGALRM,默认杀死进程,经过设置后,可以从这条语句的后面开始执行
思考:其实信号默认的SA_RESTART为0,即信号处理函数执行完后,就从下一个语句开始执行,当然,前提是信号没有杀死进程,所以,是不是可以用 signal(SIGALRM,hander)来代替?
答案:不可以,signal会修该SA_RESTART,所以,执行完后,会继续返回到阻塞的地方执行

广播 :可以向局域网中所有的主机发送消息(根据ip和port),数据报的特点就是以太网头的目标MAC为FFFFFFF。
发送端:建立socket,通过setsockopt设置为可以发广播,设定sockaddr,然后就sendto发送
接收端:建立socket,绑定ip(发送端可以不用绑定ip,但是接收端需要知道自己所绑定的ip后才能接收)
若绑定的是自己主机的ip,则只能接收单播,绑定为广播ip,则只可以接收广播,绑定0,单播组播广播都可以接收



组播 :向特定的组发消息,它的数据帧以太头的目标MAC格式:10:00:5e:*:*:*,后面三字节是组播地址的后几位有关。
发送端:建立socket,指定组播地址,向组播发送消息。(socket默认是可以发送组播消息的)
接收端:建立socket,绑定ip(ip选择和上述一样),允许数据链路层接收组播消息,接收消息。
允许链路层接收组播消息是难点:默认的以太网卡只能接收两种类型的目标MAC地址:自身MAC(单播),FFFFFF(组播)。
而组播数据帧目标MAC的地址为:10:00:5e:*:*:*,可以通过setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mrep,sizeof(mrep))里的mrep来指定以太网卡可以接收的组播。



问题:组播接受程序可以接受广播包吗?那反过来呢?(老师说,组播接受程序可以接收广播包,不甚理解,甚至觉得老师说错了)
(后来发现是我错了!在教室坐的久了脑袋秀逗了。在吃饭的路上才恍然大悟)
原因:链路层可以接收并向上传递两个类型的数据帧:目标MAC是自身(单播)、FFFFFF(广播)。其余的数据帧虽然接收,但是在链路层比较后都已经丢弃。
在组播中,接收程序需要设定链路层网卡可以处理组播的数据帧。所以他不仅能接收广播,还能接收组播(就是因为它比广播多设定了接收端MAC)。

在执行过程中,组播的接收程序不能再绑定组播地址,而是0.

实例:

接收端:组播接收端

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<sys/select.h>
#define N 64

int main(int argc,char *argv[])
{
	int fd;
	struct sockaddr_in recad,peerad;
	char buf[N]={};
	int len;
	len=sizeof(peerad);

	memset(&recad,0,sizeof(recad));
	memset(&peerad,0,sizeof(peerad));
	recad.sin_family=PF_INET;
	recad.sin_port=htons(atoi(argv[2]));
	recad.sin_addr.s_addr=inet_addr(argv[1]);

	if((fd=socket(PF_INET,SOCK_DGRAM,0))<0)
	{
		perror("socket");
		exit(-1);
	}
	if((bind(fd,(struct sockaddr*)&recad,sizeof(recad)))<0)
	{
		printf("bind");
		exit(-1);
	}
	struct ip_mreq mreq;
	memset(&mreq,0,sizeof(mreq));
	mreq.imr_multiaddr.s_addr=inet_addr("224.10.10.1");
	mreq.imr_interface.s_addr=htons(INADDR_ANY);
	
	if(setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
	{
		perror("setsockopt");
		exit(-1);
	}



	while(1)
	{
		memset(buf,0,N);
		recvfrom(fd,buf,N,0,(struct sockaddr*)&peerad,&len);
		printf("[%s %d] %s\n",inet_ntoa(peerad.sin_addr),ntohs(peerad.sin_port),buf);
	}

}

发送端:广播的发送端

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<sys/select.h>
#define N 64

int main(int argc,char *argv[])
{
	int fd;
	struct sockaddr_in broad;
	char buf[N]={"hello everyone!"};
	int val=1;
	int len;
	len=sizeof(val);

	memset(&broad,0,sizeof(broad));
	broad.sin_family=PF_INET;
	broad.sin_port=htons(atoi(argv[2]));
	broad.sin_addr.s_addr=inet_addr(argv[1]);

	if((fd=socket(PF_INET,SOCK_DGRAM,0))<0)
	{
		perror("socket");
		exit(-1);
	}
	if(setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&val,len)<0)
	{
		printf("getsockopt");
		exit(-1);
	}


	while(1)
	{
		sendto(fd,buf,N,0,(struct sockaddr*)&broad,sizeof(broad));
		sleep(1);

	}

}

发送端:编译:gcc -o sender sender.c

./send 192.168.1.255 8888

接收端:编译:gcc -o recever recever.c

./recever 0 8888

接收端执行时ip一定为0,这样,虽然他是一个组播接收程序,但是还可以接收广播。

你可能感兴趣的:(网络编程——进阶篇)