(一) 前情
在第4篇里面,介绍了TCP编程实例,现在,我们再看看UDP编程实例。才完美嘛。
(二)上个菜:一个UDP程序分析
开胃:UDP客户服务器编程模型
与TCP面向连接,可靠的编程模型不同,UDP面向数据报,是不可靠的编程模型。因此它的编程模型相较于TCP,会简单一些。
上菜:UDP编程实例分析
服务器端程序:(可以参照上面的编程模型,理解下面这段代码)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define MYPORT "4950" //端口号
#define MAXBUFLEN 100 //buffer的最大长度
//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
if(sa->sa_family == AF_INET) {
return &(((struct sockaddr_in *)sa)->sin_addr);
}
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
int main(void)
{
int sockfd;
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr;
socklen_t addr_len;
char s[INET6_ADDRSTRLEN];
char buf[MAXBUFLEN];
int rv;
int numbytes;
memset(&hints, 0, sizeof(hints)); //一定先清0整个结构体。
hints.ai_family = AF_UNSPEC; //自动选择IPv4或者IPv6
hints.ai_socktype = SOCK_DGRAM; //采用UDP编程模型
hints.ai_flags = AI_PASSIVE; // 填充本机IP
if(0 != (rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo))) { //填充addrinfo结构体。
fprintf(stderr, "getaddrinfo:%s\n", gai_strerror(rv));
return 1;
}
//for循环,选择第一个可用的IP地址。
for(p = servinfo; p != NULL; p = p->ai_next) {
if(-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("listener: socket");
continue;
}
if(-1 == (bind(sockfd, p->ai_addr, p->ai_addrlen))) {
close(sockfd); //绑定失败,一定要close(),释放已经分配的网络描述符。
perror("listener: bind");
continue;
}
break;
}
if(NULL == p) {
fprintf(stderr, "listener: failed to bind\n");
return 2;
}
freeaddrinfo(servinfo); // 后面不会再使用servinfo,可以释放掉。
printf("listener: waiting to recvfrom...\n");
addr_len = sizeof(their_addr);
if(-1 == (numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1, 0, (struct sockaddr *)&their_addr, &addr_len))) {//绑定成功后,直接recvfrom(),等待数据。
perror("recvfrom");
exit(1);
}
printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof(s)));
printf("listener: packet is %d bytes long\n", numbytes);
buf[numbytes] = '\0';
printf("listener: packet contains \"%s\"\n", buf);//打印接收到的数据。
close(sockfd);
return 0;
}
客户端程序:(可以参照上面的编程模型,理解下面这段代码)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define SERVERPORT "4950" //定义端口号,必须与服务器端相等。
int main(int argc, char *argv[])
{
int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;
int numbytes;
if(argc != 3) {
fprintf(stderr, "usage: talker hostname message\n");
exit(1);
}
memset(&hints, 0, sizeof(hints)); //首先,清0结构体。
hints.ai_family = AF_UNSPEC; //自动选择IPv4,IPv6.
hints.ai_socktype = SOCK_DGRAM; //采用UDP protocol.
if(0 != (rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo))) { //填充addrinfo结构体
fprintf(stderr, "getaddrinfo:%s\n", gai_strerror(rv));
return 1;
}
for循环,选择第一个可用的IP地址。
for(p = servinfo; p != NULL; p = p->ai_next) {
if(-1 == (sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))) {
perror("talker: socket");
continue;
}
break;
}
if(NULL == p) {
fprintf(stderr, "talker: failed to create socket\n");
return 2;
}
if(-1 == (numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0, p->ai_addr, p->ai_addrlen))) { // 啥也不用做,直接sendto()发送数据
perror("talker: sendto");
exit(1);
}
freeaddrinfo(servinfo);
printf("talker: send %d bytes to %s\n", numbytes, argv[1]);
close(sockfd);
return 0;
}
测试步骤:
测试的环境为:ubuntu 16.04
利用gcc分别编译客户端程序和服务器端程序。编译完成后,先启动服务器端程序(假设在当前目录下),在shell下执行:
./server
然后再启动客户端程序:打开另一个终端,在shell中执行(利用loop back IP address进行测试):
./client 127.0.0.1 "Hello, World!"
测试结果:
在服务器端会看到如下信息:
listener: waiting to recvfrom...
listener: got packet from 127.0.0.1
listener: packet is 12 bytes long
listener: packet contains "Hello World!"
talker: send 12 bytes to 127.0.0.1
--THE END--