【原创】《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
版权声明:本文为博主原创文章,未经博主允许不得转载。