UDP提供的是无连接、不可靠的数据报服务。
在传输过程中数据可能会丢失。我们只有通过在应用层进行正确的控制才能修复在传输层上存在的缺陷。因此,需要我们编写可靠的UDP应用程序。
UDP客户端与服务器交互的步骤如图:
使用UDP协议进行通信时,客户端并不需要与服务器建立连接,只需要通过sendto系统调用给指定的服务器发送数据报。相同地,服务器也不需要监听或接收客户端的连接,只需要通过recvfrom系统调用等待客户端发送数据报。
sendto函数与recvfrom函数都是阻塞的!
基本步骤如下:
使用系统调用socket创建一个套接字并返回这个套接字的文件描述符sockfd
#include
int socket(int domain, int type, int protocol);
一个套接字为一条通信线路的一个端点。
domain
domain参数指定哪种协议族,常见的协议族有 AF_INET、AF_INET6 和 AF_UNIX 。AF_UNIX 用于通过文件系统实现的本地套接字,AF_INET 用于网络套接字。
type type
参数指定服务类型,即 SOCK_STREAM 和 SOCK_DGRAM。
SOCK_STREAM 为流套接字,基于 TCP,提供可靠,有序的服务。
SOCK_DGRAM 为数据报套接字,基于 UDP,提供不可靠,无序的服务。
protocol
prorocol参数指定具体的协议,通常前两个参数就可以确定具体的协议,所以我们把这个参数默认设置为0。
要想让创建的套接字可以被其他进程使用,那必须给该套接字命名。对套接字命名的意思是指将该套接字关联一个IP地址和端口号,使用系统调用bind
来实现。
#include
int bind(int socket, const struct sockaddr *address, size_t address_len);
bind
系统调用把参数address
中的地址分配给与文件描述符socket
关联的套接字,address_len为address
地址结构的长度。
每种套接字都有自己的地址格式,对于AF_INET地址族,套接字地址由socket_in来指定。
struct sockaddr_in
{
sa_family_t sin_family; //协议族
u_int16_t sin_port; //端口号
struct in_addr sin_addr; //IP地址
};
struct in_addr
{
u_int32_t s_addr;
};
对于客户套接字,我们一般不需要指定套接字的端口号,而对于服务器套接字,我们需要指定套接字的端口号以便让客户正确向服务器发送数据。
UDP 是无连接不可靠的数据报协议。服务器不接收来自客户的连接,而只管调用recvfrom
系统调用,等待客户的数据到达。recvfrom
的声明如下:
#include
int recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len);
recvfrom的参数说明:
- socket:创建的套接字描述符
- buffer:指向输入缓冲区的指针
- length:缓冲区大小
- flags:在本文中,可以将 flags 置为0即可
- src_addr:指向客户套接字地址的指针
- src_len:地址长度recvfrom的返回值为读入数据的长度。
UDP是无连接的,客户端可以直接向服务器发送消息而不需要建立连接。使用sendto
系统调用向服务器发送消息。函数定义如下:
#include
int sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
sendto
函数的参数说明:
-socket:创建的套接字描述符
-buffer:输出缓冲区的指针
-length:缓冲区大小
-flags:正常应用中,flags一般设置为0
-dest_addr:指向服务器套接字地址的指针
-dest_len:地址长度
操作系统为每个套接字分配了一个文件描述符,使用完后需要关闭使操作系统回收该文件描述符,使用 close
系统调用。
#include
int close(int sockfd);
服务器端:
int main()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd != 0);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6500);
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); //回环地址
int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
while(1)
{
char recvbuf[128] = {0};
int len = sizeof(cli);
int n = recvfrom(sockfd, recvbuf, 127, 0, (struct sockaddr*)&cli, &len);
if(n <= 0)
{
printf("recvfrom error\n");
continue;
}
printf("recvbuff: %s\n", recvbuf);
sendto(sockfd, "OK", 2, 0, (struct sockaddr*)&cli, sizeof(cli));
}
close(sockfd);
}
客户端:
int main(int argc, char *argv[])
{
if(argc <3)
{
printf("please choose server's IP && port\n");
exit(0);
}
int port = 0;
sscanf(argv[2], "%d", &port);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_addr.s_addr = inet_addr(argv[1]);
ser.sin_port = htons(port);
while(1)
{
printf("please input data: ");
fflush(stdout);
char buff[128] = {0};
fgets(buff, 128, stdin);
buff[strlen(buff) - 1] = 0;
if(strncmp(buff, "end", 3) == 0)
{
close(sockfd);
break;
}
sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&ser, sizeof(ser));
memset(buff, 0, 128);
recvfrom(sockfd, buff, 127, 0, NULL, NULL);
printf("%s\n", buff);
}
close(sockfd);
}