参考:盛延敏:网络编程实战
一、UDP和TCP的不同
TCP 的发送和接收每次都是在一个上下文中,类似这样的过程:
A 连接上: 接收→发送→接收→发送→…
B 连接上: 接收→发送→接收→发送→…
而 UDP 的每次接收和发送都是一个独立的上下文,类似这样:
接收 A→发送 A→接收 B→发送 B →接收 C→发送 C→ …
二、UDP网络编程
服务器端创建 UDP 套接字之后,绑定到本地端口,调用 recvfrom 函数等待客户端的报文发送;客户端创建套接字之后,调用 sendto 函数往目标地址和端口发送 UDP 报文,然后客户端和服务器端进入互相应答过程。
recvfrom 和 sendto 是 UDP 用来接收和发送报文的两个主要函数:
#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
三、编程实践
udp服务端
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <signal.h> #define SERV_PORT 43211 #define MAXLINE 4096 static int count; static void recvfrom_int(int signo) { printf("\nrecevied %d datagrams\n",count); exit(0); } int main(int argc,char*argv[]) { int socket_fd; struct sockaddr_in server_addr; socket_fd = socket(AF_INET,SOCK_DGRAM,0); bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SERV_PORT); bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)); socklen_t client_len; char message[MAXLINE]; count = 0; signal(SIGINT,recvfrom_int); struct sockaddr_in client_addr; client_len = sizeof(client_addr); for(;;) { int n = recvfrom(socket_fd,message,MAXLINE,0,(struct sockaddr *) &client_addr, &client_len); message[n] = 0; printf("received %d bytes: %s\n",n,message); char send_line[MAXLINE]; sprintf(send_line,"Hi,%s\n",message); sendto(socket_fd,send_line,strlen(send_line),0, (struct sockaddr *) &client_addr, client_len); count++; } }
udp客户端
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <signal.h> #define SERV_PORT 43211 #define MAXLINE 4096 int main(int argc,char* argv[]) { if(argc != 2) { perror("Usage:udpclient <IPaddress>"); return -1; } int socket_fd; socket_fd = socket(AF_INET,SOCK_DGRAM,0); struct sockaddr_in server_addr; bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERV_PORT); inet_pton(AF_INET,argv[1],&server_addr.sin_addr); socklen_t server_len = sizeof(server_addr); struct sockaddr *reply_addr; reply_addr = malloc(server_len); char send_line[MAXLINE],recv_line[MAXLINE+1]; socklen_t len; int n; while(fgets(send_line,MAXLINE,stdin) != NULL) { int i = strlen(send_line); if(send_line[i-1] == '\n') { send_line[i-1] = 0; } printf("now sending %s \n",send_line); size_t rt = sendto(socket_fd,send_line,strlen(send_line),0,(struct sockaddr *)&server_addr,server_len); if(rt < 0) { perror("send faild"); } printf("send bytes %zu \n",rt); len = 0; n = recvfrom(socket_fd,recv_line,MAXLINE,0,reply_addr,&len); if(n < 0) { perror("recvfrom failed"); } recv_line[n] = 0; fputs(recv_line,stdout); fputs("\n",stdout); } exit(0); }
效果:
1)若只运行客户端,程序一直会阻塞在recvfrom上
2)先开服务端,再开客户端
3) 开服务端,开两个客户端
服务端:
客户端1:
客户端2:
两个客户端发送的报文,依次都被服务端收到,并且客户端也可以收到服务端处理之后的报文。如果我们此时把服务器端进程杀死,就可以看到信号函数在进程退出之前,打印出服务器端接收到的报文个数。
再重启服务器端进程,并使用客户端 1 和客户端 2 继续发送新的报文,这也体现了UDP 报文的“无连接”的特点,可以在 UDP 服务器重启之后,继续进行报文的发送,这就是 UDP 报文“无上下文”的最好说明。(但在服务端进程被杀死后,此时就是1)状态了,若此时在任意客户端发送数据,程序便会一直阻塞在recvfrom上)
注:
详解inet_pton()和inet_ntop()函数