1 UDP协议报头
源端口号、目的端口号:表示是谁发来的报文,要将该报文传送给上层(应用层)的谁
16位UDP长度:该长度包括UDP 报头在内的整个报文的长度
长度共16位,最大长度为65536,即64K,若超过该长度,需要在应用层对报文手动分包,并在接收方手动拼装
16校验和:对报头信息的简单校验,并不保证UDP协议的可靠性,如果校验和出错,直接将报文丢弃
2 UDP协议特点
(1)不可靠:没有重传机制和确认机制,在传输过程中出现问题,不进行任何处理
(2)无连接:知道对方的IP地址和端口号可以直接进行传输,不需要建立连接
(3)面向数据报:在传输过程中,以一个数据报为单位进行传输,“整发整取”,即每次只能发送或接收一个报文,不能发送半个报文
(4)全双工:既可以收数据,也可以发数据(同时收发数据),称为“全双工”
(5)成本小,效率高,有特定的应用场景
【注】:由于UDP是面向数据报的,所以在发送时需要知道UDP报文的大小,即报头中的16位UDP长度
3 如何分离报文和有效载荷
UDP采用定长报头的方法,已知UDP长度A和报头长度B,有效载荷即为:A-B
4 UDP的缓冲区
(1)UDP没有发送缓冲区:UDP在发送数据时使用sendto接口,sendto直接将数据发送给内核,再由内核进行之后的操作;
同时由于UDP不保证可靠性,UDP在发送数据后不必考虑数据是否到达对方,若未到达的重传问题
(2)UDP具有接收缓冲区:若接收缓冲区满了,之后达到的数据会直接丢弃,而此时发送方并不知道
UDP的接受缓冲区并不保证数据的按序到达,所以也证明UDP不能够保证数据的可靠性
5 编写UDP——socket套接字
UDP服务器,实现功能:接收客户端数据,并将数据回显给客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// ./usp_server 8080
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage:%s [port]\n",argv[0]);
return 1;
}
//创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
printf("socket error!\n");
return 2;
}
//填充本地套接字
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口号
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
printf("bind error!\n");
return 3;
}
char buf[1024];
struct sockaddr_in client;
socklen_t len = sizeof(client);
while(1)
{
//接收客户端发来的信息
ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
if(s < 0)
{
printf("recvfrom error!\n");
break;
}
else
{
buf[s] = 0;
//显示接受数据的IP地址和端口号
printf("client[%s][%d],say:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
//将接收到的数据再回显给客户端
ssize_t r = sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,len);
}
}
return 0;
}
UDP客户端,实现功能:发送数据,显示服务器回显数据,实现简单的客户端——服务器通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage:%s [port]\n",argv[0]);
return 1;
}
//创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
printf("sock error!\n");
return 2;
}
//填充服务器套接字
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[1]));
server.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in client;
socklen_t len = sizeof(client);
char buf[1024];
while(1)
{
printf("Please Enter:");
fflush(stdout);
ssize_t r = read(0,buf,sizeof(buf)-1);//输入发送的数据
buf[r-1] = 0;
//将数据发送至sock
ssize_t s = sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
if(s > 0)
{
//从sock中接受数据
r = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
buf[r] = 0;
//将服务器接收数据回显
printf("server echo# %s\n",buf);
}
}
return 0;
}
运行结果:
【注】:为什么服务器绑定端口号,但是客户端没有绑定端口号?
客户端也可以绑定端口号,但是绑定端口号之后,客户端只能使用绑定端口号,其复用性不强,所以一般客户端不绑定端口号。
由于服务器被很多客户端使用,服务器端口号需要做到众所周知,也因此服务器必须绑定端口号。