来源:《基于UNIX/Linux的C系统编程》
UDP协议采用C/S模式,套接字的全部工作流程如下图:
步骤1:服务器端和客户端进程均调用socket函数创建一个基于UDP协议的套接字描述符;
步骤2:服务器端进程调用bind函数命名套接字,将套接字与协议、本地地址和本地端口绑定;
步骤3:客户端与服务器端进行数据通信,调用sendto函数可向对方发送数据,调用recvfrom函数可接收数据;
步骤4:当不需要数据传输时,可以调用close或shutdown函数关闭套接字。
1、创建UDP套接字
socket函数可以用于创建UDP套接字,其调用形式与TCP类似,但须在函数的第二个参数type取值为SOCK_DGRAM。
与TCP协议服务器端套接字的创建过程相比,这里减少了listen和accept过程。
2、UDP数据的发送和接收
在UDP套接字中,使用recvfrom和sendto函数来收发数据,其函数说明如下表:
所需头文件 | #include <sys/types.h> #include <sys/socket.h> |
---|---|
函数原型 | int sendto(int sockfd, const void *buff, int nbytes, int flags, struct sockaddr *to, socklen_t *addrlen); |
int recvfrom(int sockfd, void *buff, int nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); | |
参数 | sockfd:套接字描述符 |
buff:一个指向接收数据缓冲区的指针 | |
nbytes:指定缓冲区最大可以容纳数据的大小(字节) | |
flags:在UDP连接中一般设为0 | |
to:一个含有数据将发往的协议地址(包含IP地址和端口号)的套接字地址结构,其大小由addrlen来指定 | |
from:所指向的套接字地址结构装填数据包发送者的协议地址,它的大小也由addrlen所指的整数返回给调用者 | |
addrlen:地址长度 | |
函数返回值 | sendto():若函数调用成功,返回读入的字节数,否则返回-1并将错误码写入errno |
recvfrom():若函数调用成功,返回写出的字节数,否则返回-1并将错误码写入errno |
与TCP数据的发送和接收相比,UDP的发送和接收函数中都增加了用于存储对方地址的结构,这主要与UDP无连接的特性相关。
例1:结合UDP编程模型,编写一程序验证上述函数的可行性。需要实现以下功能:
- UDP服务器程序:守候在特定的套接字地址上,循环接收客户发来的信息,并显示客户IP地址及相应信息。如果服务收到客户的信息为”quit”,则退出循环,并关闭套接字;
- UDP客户机程序:客户向服务器发信息,然后等待服务器回应。一旦接收到服务器发来的数据,则显示该信息,并关闭套接字。
客户端程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #define PORT 1234 #define MAXDATASIZE 100 int main(int argc, char *argv[]) { int fd, numbytes; // files descriptors char buf[MAXDATASIZE]; // buf will store received text struct hostent *he; // structure that will get infotmation about remote host struct sockaddr_in server, reply; // server's address information if (argc != 3) { printf("Usage: %s <IP Address> <message>\n", argv[0]); exit(1); } if ((he = gethostbyname(argv[1])) == NULL) { perror("gethostbyname() error!"); exit(1); } if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { perror("socket() error"); exit(1); } memset(&server, 0, sizeof(server)); server.sin_family = PF_INET; server.sin_port = htons(PORT); server.sin_addr = *((struct in_addr *)he->h_addr); sendto(fd, argv[2], strlen(argv[2]), 0, (struct sockaddr *)&server, sizeof(struct sockaddr)); while (1) { int len; if ((numbytes = recvfrom(fd, buf, MAXDATASIZE, 0, (struct sockaddr *)&reply, &len)) == -1) { perror("ercvfrom() error!"); exit(1); } buf[numbytes] = '\0'; printf("Server message: %s\n", buf); break; } close(fd); }
服务器端程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define PORT 1234 #define MAXDATASIZE 100 int main() { int sockfd; struct sockaddr_in server, client; int sin_size, num; char msg[MAXDATASIZE]; // buffer for message /* Creating UDP socket */ if ((sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { perror("Creating socket error!"); exit(1); } memset(&server, 0, sizeof(server)); server.sin_family = PF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { perror("bind() error!"); exit(1); } sin_size = sizeof(struct sockaddr_in); while (1) { num = recvfrom(sockfd, msg, MAXDATASIZE, 0, (struct sockaddr *)&client, &sin_size); if (num < 0) { perror("recvfrom() error!"); exit(1); } msg[num] = '\0'; printf("You got a message %s from %s\n", msg, inet_ntoa(client.sin_addr)); // Print client's IP sendto(sockfd, "Welcome to my server.\n", 22, 0, (struct sockaddr *)&client, sin_size); if (strcmp(msg, "quit") == 0) break; } close(sockfd); }
客户端运行结果如下:
此处留截图。
服务器端运行结果如下:
此处留截图。