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



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


1. UDP网络编程基础 UDP通信流程 网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


对于UDP方式,发送数据时需要显示指定数据包的目的地址,因此不能使用read/write/send/recv函数。
使用sendto和recvfrom 网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


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

网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


使用AF_INET实现UDP点对点通信示例 接收端代码

#include #include #include #include #include #include #include #include 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); } elseprintf("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); } elseprintf("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; }


发送端代码


#include #include #include #include #include #include #include #include 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; }


运行结果

接收端

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

发送端
【网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)】
$ ./udp_simple_send 172.18.229.60 create socket. send success.


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。


单播数据帧格式
网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片

广播数据帧格式 网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


允许某个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 #include #include #include #include #include #include #include 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); } elseprintf("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 #include #include #include #include #include #include #include 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); } elseprintf("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); } elseprintf("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


3. UDP组播通信

组播地址 组播地址范围是D类IP地址,即224.0.0.1-239.255.255.255。
组播MAC地址产生办法
网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


组播数据帧
网络|【原创】《Linux高级程序设计》杨宗德著 - UDP网络编程应用 分类( ...)
文章图片


组播通信编程 在传播时,和广播一样,组播消息会被复制的发到网络上所有主机的网卡,但只有宣布加入该组(例如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 #include #include #include #include #include #define BUFLEN 255int 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); }} elsemyaddr.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 #include #include #include #include #include #include #include #define BUFLEN 255int 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


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 #include #include #include #include #include #include #include #include #include #include #include #define MAX_LENTH 1500static 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); }}


客户端代码


#include #include #include #include #include #include #include /*socket address struct*/#include/*host to network convertion*/#include #include #define MAX_LENTH 1500int 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 hellosignum=29,nqueue=1 recv 1499 byte(s),msg is client testsignum=29,nqueue=1 recv 1499 byte(s),msg is yessignum=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


原文链接 http://blog.csdn.net/geng823/article/details/41865507



版权声明:本文为博主原创文章,未经博主允许不得转载。
转载于:https://www.cnblogs.com/gengzj/p/4675769.html

    推荐阅读