UDP 是无连接不可靠的数据包协议,不同于 TCP 提供的面向连接的可靠字节流。在介绍UDP编程模型前,先介绍UDP协议中两个重要的函数:sendto 和 recvfrom。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
//两者均返回:读写字节数——成功,-1——出错
前面三个参数:sockfd、buff 和 nbytes 等同于 read 和 write 函数的三个参数:套接口描述字、指向读入或写出缓冲区的指针和读写字节数。
sendto :其中 to 参数指向一个含有数据包接收者的协议地址(含 IP 地址和端口号)的套接口地址结构,其大小由 addrlen 参数指定。
recvfrom:from 参数指向一个将由该函数在返回时填写数据包发送者的协议地址(含数据发送端的 IP地址和端口号)的套接口地址结构,而在该套接口地址结构中填写的字节数则放在 addrlen 参数所指的整数中返回给调用者。简单地说就是 from 参数指向的结构体在 recvfrom 函数返回时将被对端(即:数据发送端)地址(含 IP 地址和端口号)所填充,后面的 addrlen 是个整型指针,调用前应填入 from 指向的结构体的大小,调用后将被填入对端地址的实际大小。
上面的 recvfrom 函数最后两个参数类似于 accept 的最后两个参数:函数返回时其中套接口地址结构的内容告诉我们是谁发送了数据包(UDP,通常用于UDP情况下)。
我们看看 UDP 编程模型:
相比TCP,对于服务器而言,UDP
- 没有调用 accept ,因为不需要建立连接,一个套接口可以接收不同 IP 发来的数据,对端 socket 地址由 recvfrom 获得。
对于客户端而言
- 没有调用 connect,因为不需要建立连接,首次发送数据时,由操作系统临时选择本地 IP 地址和端口,由 sendto 指定目标 IP 地址和端口,因此一个套接口可以向不同 IP 发送数据。
程序如下(编程环境:Linux deepin 2014)
服务器端:
服务器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#define PORT 8888
#define MAX_SIZE 1024
void udp_respon(int sockfd)
{
struct sockaddr_in cliaddr;
int n,i;
unsigned int addrlen;
char msg[MAX_SIZE];
while(1)
{
n = recvfrom(sockfd, msg, MAX_SIZE, 0, (struct sockaddr *)&cliaddr, &addrlen);
msg[n] = '\0';
fprintf(stdout, "I have recevied %s", msg);
n = strlen(msg);
for(i = 0; i < n; ++i)
{
msg[i] = msg[i] + 'A' - 'a';
}
sendto(sockfd, msg, n, 0, (struct sockaddr *)&cliaddr, addrlen);
}
}
int main(void)
{
int sockfd;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_DGRAM,0);
bzero(&seraddr, sizeof(seraddr));
//服务器端套接口结构体初始化
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(PORT);
//绑定
bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
udp_respon(sockfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#define PORT 8888
#define MAX_SIZE 1024
void udp_request(int sockfd, const struct sockaddr_in *addr, int len)
{
char buffer[MAX_SIZE];
int n;
while(1)
{
fgets(buffer, MAX_SIZE, stdin);
//向目标端发送数据
sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)addr, len);
printf("I have sent to server %s", buffer);
printf("Waiting respond from server\n");
bzero(buffer, MAX_SIZE);
//这里不需要了解数据发送端的信息,所以设置NULL
n = recvfrom(sockfd, buffer, MAX_SIZE, 0, NULL, NULL);
buffer[n] = 0;
printf("I have received from server ");
fputs(buffer, stdout);
printf("\n");
}
}
int main()
{
int sockfd;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
//目标端套接口结构体设置
bzero(&seraddr, sizeof(struct sockaddr_in));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(PORT);
udp_request(sockfd, &seraddr, sizeof(struct sockaddr_in));
close(sockfd);
return 0;
}
对于服务器程序而言,编程步骤:
调用 socket,创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;调用 bind,将刚刚创建的 socket 与本机的 IP地址和端口号绑定,这样一来 socket 就变成了一个 3元组;调用 recvfrom,从套接口接收客户端发送过来的数据(同时能获得客户端的 IP地址和端口号),当然如果此时客户端并未发送数据,那么 recvfrom 将阻塞到有数据到达为止;根据客户端的要求进行数据处理;调用 sendto (将客户端 IP和端口号作为参数传入),向套接口写入处理后的数据,该数据将通过网络到达客户端,当所有数据都已处理完毕,将调用 close 关闭套接口,自此通信结束。
对于客户机程序而言,编程步骤:
调用 socket,创建一个不与任何网络连接相对应的套接口(socket),调用 sendto(将服务器 IP和端口号作为参数传入)向 socket 中写入数据,此时系统将选择一个本机尚未使用的端口号作为本地端口号,并选择本机的 IP地址作为本地 IP,这样,该 socket 就变成了一个 3元组,该数据将到达服务器,被 recvfrom 所读取(服务器端),然后调用
recvfrom(客户端),等待从 socket 获取服务器通过 sendto 回送的数据,当所有数据都接收完毕,调用 close 关闭套接口,自此通信结束。