【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类: Linux --- 应用程序设计 2014-12-11 14:41 73人阅读 评论(0) 收藏


【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用


1. UDP网络编程基础

UDP通信流程


对于UDP方式,发送数据时需要显示指定数据包的目的地址,因此不能使用read/write/send/recv函数。

使用sendto和recvfrom


第一个参数为发送的目标socket对象。
第二个参数为欲发送的数据信息。
第三个参数为发送数据的大小。
第四个参数为flags,如send函数所示。
第五个参数欲发送数据的目标地址,其结构体前面已经介绍。
第六个参数为此结构体的大小。


使用AF_INET实现UDP点对点通信示例

接收端代码

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <errno.h>

#include <stdlib.h>

#include <arpa/inet.h>



int main(int argc, char **argv)

{

	struct sockaddr_in s_addr;

	struct sockaddr_in c_addr;

	int sock;

	socklen_t addr_len;

	int len;

	char buff[128];



	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 

	{

		perror("socket");

		exit(errno);

	} else

		printf("create socket.\n\r");



	memset(&s_addr, 0, sizeof(struct sockaddr_in));



	s_addr.sin_family = AF_INET;

	s_addr.sin_port = htons(7838);

	s_addr.sin_addr.s_addr = INADDR_ANY;



	if ((bind(sock, (struct sockaddr *) &s_addr, sizeof(s_addr))) == -1) 

	{

		perror("bind");

		exit(errno);

	} else

		printf("bind address to socket.\n\r");



	addr_len = sizeof(c_addr);

	while (1) 

	{

		len = recvfrom(sock, buff, sizeof(buff) - 1, 0,

				(struct sockaddr *) &c_addr, &addr_len);

		if (len < 0) 

		{

			perror("recvfrom");

			exit(errno);

		}



		buff[len] = '\0';

		printf("recive come from %s:%d message:%s\n\r",

			inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port), buff);

	}

	return 0;

}<span style="color:#339999;">
</span>

发送端代码

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
	struct sockaddr_in s_addr;
	int sock;
	int addr_len;
	int len;
	char buff[128];

	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 
	{
		perror("socket");
		exit(errno);
	} else
		printf("create socket.\n\r");

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(7838);
	if (argv[1])
		s_addr.sin_addr.s_addr = inet_addr(argv[1]);
	else {
		printf("input sever ip!\n");
		exit(0);
	}

	addr_len = sizeof(s_addr);
	strcpy(buff, "hello i'm here");
	len = sendto(sock, buff, strlen(buff), 0,
			(struct sockaddr *) &s_addr, addr_len);
	if (len < 0) {
		printf("\n\rsend error.\n\r");
		return 3;
	}

	printf("send success.\n\r");
	return 0;
}<span style="color:#339999;">
</span>

运行结果

接收端

$ ./udp_simple_rcv
create socket.
bind address to socket.
recive come from 172.18.229.60:38412 message:hello i'm here
发送端

$ ./udp_simple_send 172.18.229.60
create socket.
send success.<span style="color:#339999;">
</span>

2. UDP广播通信

单播、组播与广播基本概念

单播:点对点的传送,即一对一的。TCP方式和UDP方式都可以实现单播,且TCP只能是单播的方式。
广播:处于同一个广播域的所有主机都将收到消息,是一点对多点的方式,广播只能由UDP完成。
组播:消息只会从主机发到加入到同一个组播组(例如230.1.1.1)的主机的对应端口,组播也只能由UDP完成。

广播地址是某网段中主机位全为1的IP地址,例如:

10.0.0.0/8网段的广播地址为10.255.255.255。
172.168.0.0/16的广播地址为172.168.255.255。
202.115.1.0/24的广播地址为202.115.1.255。
202.115.0.0/23的广播地址为202.115.1.255。

单播数据帧格式

广播数据帧格式


允许某个socket发送广播消息

使某个socket可以发送广播消息(修改发送端),需要设置该socket属性为SO_BROADCAST,如下所示:
   setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
广播信息(目的MAC地址为FF:FF:FF:FF:FF:FF)会被复制并发到同一个广播域内的每个主机的网卡,网卡收到消息后提交给操作系统去处理,操作系统发现有程序在对应端口接收UDP数据则把消息转给相应的程序去处理,如果没有程序接收来自该端口的UDP消息,则操作系统丢弃该消息。
因此,不管主机是否有程序接收广播消息,广播消息一定会被网卡收到并提交给操作系统去处理,所以会造成网络上流量增大,对不接收广播消息的主机造成一定的负担。

UDP广播通信示例

发送端流程:
以UDP方式创建sokcet对象;
设置socket对象为可发送广播消息属性;
将消息以广播方式发送。
接收端流程:
以UDP方式创建socket对象;
绑定接收数据的端口和IP地址,接收端绑定的该主机的IP地址必须设置为INADDR_ANY。否则不能收到消息;
以阻塞方式接收UDP数据;
输出接收到的广播消息。 

发送端代码

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <errno.h>

#include <stdlib.h>

#include <arpa/inet.h>

int main(int argc, char **argv)

{

        struct sockaddr_in s_addr;

        int sock;

        int addr_len;

        int len;

        char buff[128];

        int yes;

        if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 	

        {

                perror("socket");

                exit(EXIT_FAILURE);

        } else

                printf("create socket.\n\r");

        yes = 1;

        setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));	

										

        s_addr.sin_family = AF_INET;

        s_addr.sin_port = htons(8080);

        if (argv[1])

                s_addr.sin_addr.s_addr = inet_addr(argv[1]); 

        else {

                printf("input sever ip!\n");

                exit(0);

        }

        addr_len = sizeof(s_addr);

        strcpy(buff, "hello message");		

        len = sendto(sock, buff, strlen(buff), 0,		

                        (struct sockaddr *) &s_addr, addr_len);

        if (len < 0) {

                printf("\n\rsend error.\n\r");

                exit(EXIT_FAILURE);

        }

        printf("send success.\n\r");

        return 0;

}

接收端代码

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <errno.h>

#include <stdlib.h>

#include <arpa/inet.h>

int main(int argc, char **argv)

{

	struct sockaddr_in s_addr;

	struct sockaddr_in c_addr;

	int sock;

	socklen_t addr_len;

	int len;

	char buff[128];

	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 

	{

		perror("socket");

		exit(EXIT_FAILURE);

	} else

		printf("create socket.\n\r");

	memset(&s_addr, 0, sizeof(struct sockaddr_in));



	s_addr.sin_family = AF_INET;							

	s_addr.sin_port = htons(8080);	

	s_addr.sin_addr.s_addr = INADDR_ANY;

	if ((bind(sock, (struct sockaddr *) &s_addr, sizeof(s_addr))) == -1) 

	{

		perror("bind");

		exit(EXIT_FAILURE);

	} else

		printf("bind address to socket.\n\r");

	addr_len = sizeof(c_addr);

	while (1) 

	{

		len = recvfrom(sock, buff, sizeof(buff) - 1, 0,	

				(struct sockaddr *) &c_addr, &addr_len);

		if (len < 0) 

		{

			perror("recvfrom");

			exit(EXIT_FAILURE);

		}

		buff[len] = '\0';

		printf("recive come from %s:%d message:%s\n\r",

			inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port), buff);

	}

	return 0;

}

运行结果

发送端(运行四次)

$ ./udp_brodcast_send 172.18.229.60
create socket.
send success.
接收端(可以在同一网段下运行多个接收端)

$ ./udp_brodcast_rcv
create socket.
bind address to socket.
recive come from 172.18.229.60:41864 message:hello message
recive come from 172.18.229.60:58463 message:hello message
recive come from 172.18.229.60:46442 message:hello message
recive come from 172.18.229.60:59744 message:hello message<span style="color:#339999;">
</span>

3. UDP组播通信

组播地址 

组播地址范围是D类IP地址,即224.0.0.1-239.255.255.255。 

组播MAC地址产生办法 


组播数据帧


组播通信编程

在传播时,和广播一样,组播消息会被复制的发到网络上所有主机的网卡,但只有宣布加入该组(例如230.1.1.1)的主机的网卡才会把数据提交给操作系统去处理。如果没有加入组,则网卡直接将数据丢弃。

如果某socket期望接收组播消息,需要设置该socket对象属性,如下所示:
 setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(struct ip_mreq));

组播通信示例

发送端流程如下:
以UDP方式创建socket对象;
初始化发送数据所目的地址和端口;
绑定本机IP地址和端口;
向组播组内所有主机发送数据。
接收端流程:
以UDP方式创建socket,获取组播地址和本机地址,将当前主机加入到该组中;
绑定本机IP地址和端口;
接收消息并输出。

发送端代码

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define BUFLEN 255

int main(int argc, char **argv)

{

        struct sockaddr_in peeraddr, myaddr;

        int sockfd;

        char recmsg[BUFLEN + 1];

        unsigned int socklen;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);	

        if (sockfd < 0)

        {

                printf("socket creating error\n");

                exit(EXIT_FAILURE);

        }

        socklen = sizeof(struct sockaddr_in);

        memset(&peeraddr, 0, socklen);

        peeraddr.sin_family = AF_INET;

        peeraddr.sin_port = htons(8080);		

        if (argv[1]) {					

                if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0) {

                        printf("wrong group address!\n");

                        exit(EXIT_FAILURE);

                }

        } 

        else {

                printf("no group address!\n");

                exit(EXIT_FAILURE);

        }

        memset(&myaddr, 0, socklen);

        myaddr.sin_family = AF_INET;

        myaddr.sin_port = htons(23456);		

        if (argv[2]) {				

                if (inet_pton(AF_INET, argv[2], &myaddr.sin_addr) <= 0) 

                {

                        printf("self ip address error!\n");

                        exit(EXIT_FAILURE);

                }

        } else

                myaddr.sin_addr.s_addr = INADDR_ANY;

        if (bind(sockfd, (struct sockaddr *) &myaddr,sizeof(struct sockaddr_in)) == -1) 

        {								

                printf("Bind error\n");

                exit(EXIT_FAILURE);

        }

        for (;;) {

                bzero(recmsg, BUFLEN + 1);

                printf("input message to send:");

                if (fgets(recmsg, BUFLEN, stdin) == (char *) EOF)	

                        exit(EXIT_FAILURE);;

                if (sendto(sockfd, recmsg, strlen(recmsg), 0,(struct sockaddr *) &peeraddr,

						sizeof(struct sockaddr_in)) < 0) 			

                {

                        printf("sendto error!\n");

                       exit(EXIT_FAILURE);;

                }

                printf("sned message:%s", recmsg);	

        }

}

接收端代码

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <netdb.h>

#include <errno.h>

#define BUFLEN 255



int main(int argc, char **argv)

{

	struct sockaddr_in peeraddr;

	struct in_addr ia;

	int sockfd;

	char recmsg[BUFLEN + 1];

	unsigned int socklen, n;

	struct hostent *group;

	struct ip_mreq mreq;



	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	if (sockfd < 0) 

	{

		printf("socket creating err in udptalk\n");

		exit(EXIT_FAILURE);

	}

	bzero(&mreq, sizeof(struct ip_mreq));

	if (argv[1]) 

	{

		if ((group = gethostbyname(argv[1])) == (struct hostent *) 0) 

		{

			perror("gethostbyname");

			exit(EXIT_FAILURE);

		}

	} 

	else 

	{

		printf("you should give me a group address, 224.0.0.0-239.255.255.255\n");

		exit(EXIT_FAILURE);

	}

	bcopy((void *) group->h_addr, (void *) &ia, group->h_length);

	bcopy(&ia, &mreq.imr_multiaddr.s_addr, sizeof(struct in_addr));	

	//mreq.imr_interface.s_addr = htonl(INADDR_ANY);

	if (argv[2]) {

		if (inet_pton(AF_INET, argv[2], &mreq.imr_interface.s_addr) <= 0) 

		{	

			printf("Wrong dest IP address!\n");

			exit(EXIT_FAILURE);

		}

	} 
	

	if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(struct ip_mreq)) == -1) 

	{

		perror("setsockopt");

		exit(EXIT_FAILURE);

	}

	socklen = sizeof(struct sockaddr_in);

	memset(&peeraddr, 0, socklen);

	peeraddr.sin_family = AF_INET;

	peeraddr.sin_port = htons(8080);

	

	if (argv[1]) {

		if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0) 

		{	

			printf("Wrong dest IP address!\n");

			exit(EXIT_FAILURE);

		}

	} 

	else 

	{

		printf("no group address given, 224.0.0.0-239.255.255.255\n");

		exit(EXIT_FAILURE);

	}

	if (bind(sockfd, (struct sockaddr *) &peeraddr,sizeof(struct sockaddr_in)) == -1) 

	{

		printf("Bind error\n");

		exit(EXIT_FAILURE);

	}


	for (;;) 

	{

		bzero(recmsg, BUFLEN + 1);

		n = recvfrom(sockfd, recmsg, BUFLEN, 0,(struct sockaddr *) &peeraddr, &socklen);

		if (n < 0) 

		{

			printf("recvfrom err in udptalk!\n");

			exit(EXIT_FAILURE);

		} 

		else 

		{



			recmsg[n] = 0;

			printf("peer:%s", recmsg);

		}

	}

} 

运行结果

接收端

$ ./udp_group_brodcast_rcv 230.1.1.1 172.18.229.60
peer:hello
peer:send test
peer:yes
peer:end
发送端

$ ./udp_group_brodcast_send 230.1.1.1 172.18.229.60
input message to send:hello
sned message:hello
input message to send:send test
sned message:send test
input message to send:yes
sned message:yes
input message to send:end
sned message:end<span style="color:#339999;">
</span>

4. socket信号驱动(UDP)

SIGIO信号处理机制 

为了使一个套接字使用信号驱动I/O操作,需要至少以下三步操作:
(1)安装SIGIO信号,在该处理函数中设定处理办法。
(2)套接字的拥有者必须被设定。一般来说是使用fcntl 函数的F_SETOWN 参数来进行设定拥有者。
(3)套接字必须被允许使用异步I/O。一般是通过调用fcntl 函数的F_SETFL 命令,将即设置为O_ASYNC。
SIGIO 的缺省动作是被忽略。在设置套接字的属主之前必须将SIGIO 的信号处理函数设好,如果以相反的顺序调用这两个函数调用,那么在fcntl 函数调用之后,signal 函数调用之前就有一小段时间程序可能接收到SIGIO 信号。那样的话,信号将会被丢弃。

UDP 套接字的SIGIO 信号

套接字收到了一个数据报的数据包。
套接字发生了异步错误。

TCP 套接字的SIGIO 信号 

对于一个TCP 套接字来说, SIGIO信号发生的几率太高了, SIGIO 信号不能告诉究竟发生了什么事情。在TCP连接中, SIGIO信号将会在这个时候产生:
在一个监听某个端口的套接字上成功的建立了一个新连接。
一个断线的请求被成功的初始化。
一个断线的请求成功的结束。
套接字的某一个通道(发送通道或是接收通道)被关闭。
套接字接收到新数据。
套接字将数据发送出去。
发生了一个异步I/O 的错误。

信号驱动方式处理UDP数据示例

服务器代码

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <string.h>

#include <signal.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <sys/ioctl.h>

#define MAX_LENTH 1500



static int nqueue = 0;

void sigio_handler(int signum)

{

	if (signum == SIGIO)

		nqueue++;

	printf("signum=%d,nqueue=%d\n",signum,nqueue);



	return;

} 

static recv_buf[MAX_LENTH];

int main(int argc, char *argv[]) 

{

	int sockfd, on = 1;

	struct sigaction action;

	sigset_t newmask, oldmask; 

	struct sockaddr_in ser_addr;


	if(argc!=3)

	{

		printf("use: %s ip_add port\n",argv[0]);

		exit(EXIT_FAILURE);

	}

	memset(&ser_addr, 0, sizeof(ser_addr));

	ser_addr.sin_family = AF_INET;



	if (inet_aton(argv[1], (struct in_addr *) & ser_addr.sin_addr.s_addr) == 0)

	{	

		perror(argv[1]);

		exit(EXIT_FAILURE);

	}

	ser_addr.sin_port = htons(atoi(argv[2]));	

	

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

		perror("Create socket failed");

		exit(EXIT_FAILURE);

	}

	if (bind(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) == -1) {

		perror("Bind socket failed");

		exit(EXIT_FAILURE);

	}


	memset(&action, 0, sizeof(action));

	action.sa_handler = sigio_handler;

	action.sa_flags = 0;

	sigaction(SIGIO, &action, NULL);

	if (fcntl(sockfd, F_SETOWN, getpid()) == -1) {

		perror("Fcntl F_SETOWN ");

		exit(EXIT_FAILURE); 

	}

	if (ioctl(sockfd, FIOASYNC, &on) == -1) {

		perror("Ioctl FIOASYNC");

		exit(EXIT_FAILURE); 

	}

	sigemptyset(&oldmask);

	sigemptyset(&newmask);

	sigaddset(&newmask, SIGIO);

	printf("get ready\n");

	while (1) 

	{

		int len;

		sigprocmask(SIG_BLOCK, &newmask, &oldmask);

		while (nqueue == 0)

			sigsuspend(&oldmask);

		memset(recv_buf,'\0',MAX_LENTH);

		len = recv(sockfd, recv_buf, MAX_LENTH, MSG_DONTWAIT);

		if (len == -1 && errno == EAGAIN) 

			nqueue = 0;

		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		if (len >= 0)

			printf("recv %d byte(s),msg is %s\n", len,recv_buf);

	}

}<span style="color:#339999;">
</span>

客户端代码

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <errno.h>

#include <netinet/in.h> 		/*socket address struct*/

#include <arpa/inet.h>			/*host to network convertion*/

#include <sys/socket.h>

#include <signal.h>

#define MAX_LENTH 1500



int main(int argc,char *argv[])

{

	struct sockaddr_in addr;

	int sock_fd,ret;

	char snd_buf[MAX_LENTH];

	if(argc!=3)

	{

		printf("use: %s ip_add port\n",argv[0]);

		exit(EXIT_FAILURE);			

	}

	memset(&addr,0,sizeof(addr));

	addr.sin_family =  AF_INET;

	if (inet_aton(argv[1], (struct in_addr *)&addr.sin_addr.s_addr) == 0)

	{	

		perror(argv[1]);

		exit(EXIT_FAILURE);

	}

	addr.sin_port = htons(atoi(argv[2]));		

	if((sock_fd = socket(AF_INET,SOCK_DGRAM,0))==-1)

	{

		perror("socket");

		exit(EXIT_FAILURE);

	}

	if(ret = connect(sock_fd,(struct sockaddr *)&addr,sizeof(addr))==-1)

	{

		perror("Connect");

		exit(EXIT_FAILURE);		

	}		

	while(1)

	{

		printf("input msg to send:");

		memset(snd_buf,'\0',MAX_LENTH);

		fgets(snd_buf,MAX_LENTH-1,stdin);

		write(sock_fd,snd_buf,MAX_LENTH-1);

	}
}

运行结果

服务器

$ ./sigio_server 172.18.229.60 9000
get ready
signum=29,nqueue=1
recv 1499 byte(s),msg is hello

signum=29,nqueue=1
recv 1499 byte(s),msg is client test

signum=29,nqueue=1
recv 1499 byte(s),msg is yes

signum=29,nqueue=1
recv 1499 byte(s),msg is end
客户端

$ ./sigio_client 172.18.229.60 9000
input msg to send:hello
input msg to send:client test
input msg to send:yes
input msg to send:end<span style="color:#339999;">
</span>

原文链接

http://blog.csdn.net/geng823/article/details/41865507


版权声明:本文为博主原创文章,未经博主允许不得转载。

posted @ 2014-12-11 14:41  GengLUT  阅读(213)  评论(0编辑  收藏  举报