UDP协议:UDP通信


本片博客会粘贴部分代码,想要了解更多代码信息,可访问 小编的GitHub关于本篇的代码
有关 OSI七层参考模型、TCP/IP五层(四层)参考模型

为什么会有传输层?

在协议栈中,传输层位于网络层之上,传输层协议为不同主机上运行的进程提供逻辑通信,而网络层协议为不同主机提供逻辑通信。
在这里插入图片描述

TCP和UDP对比

  • 面向字节流和面向数据报
    面向数据报:数据发送的时候有最大长度限制(65535字节),必须整条发送整条接收。
    面向字节流:发送和接受数据都没有长度限制,收发数据比较灵活,数据无明显边界,容易造成tcp粘包问题。
传输协议特点应用场景
UDP协议,用户数据报协议(User Datagram Protocol)无连接,不可靠,面向数据报。数据传输速率快,实时性高,常用语音乐、视频等对数据要求不是很高,但实时性要求比较高的场景。
TCP协议,Transmission Control Protocol 传输控制协议有连接,数据可靠传输,面向字节流可以保证数据的可靠传输,因此常用语对数据安全性比较高的场景,传输效率低于UDP。

网络通信中服务端有固定服务器地址和固定的端口,客户端在有了服务端的ip和port后就可以向服务器发起请求。

UDP协议特性

UDP没有严格意义的发送缓冲区,调用sendto函数,内核直接将数据扔给网络,原封不动发送出去;UDP有接受缓冲区,但是接受缓冲区不能保证按发送顺序接受数据,UDP缓冲区满了,再来数据就会被丢弃。
在这里插入图片描述

  • 基于UDP的应用层协议
    NFS: 网络文件系统
    TFTP: 简单文件传输协议
    DHCP: 动态主机配置协议
    BOOTP: 启动协议(⽤于无盘设备启动)
    DNS: 域名解析协议
  • socket套接字编程
    socket是一套用于网络编程的接口,同时socket也是一个数据结构。UDP的socket既可以用来读,也可以用来写,称为全双工。

UDP报头解析

UDP报头应该有什么?UDP作为传输层协议报头,为了实现进程到进程的通信,必须带有源端口、目的端口;由于面向数据报的特点,UDP报头定长8字节,为了准确区分报头部分和数据部分,必须得有UDP长度,UDP提供非常有限的差错检测功能,报头16位UDP检验和来实现。
在这里插入图片描述
1、UDP长度=UDP头(8字节)+UDP数据长度
2、UDP是传输层协议,传输层负责端到端之间的数据传输,UDP头部中必须有源端口、目的端口,用来存储数据从哪个进程来到哪个进程去。
3、UDP协议首部中有⼀个16位来表示一条UDP数据的最大长度是2^16字节=65536字节=64K=8字节UDP首部+UDP数据长度,也就是说我们可以传输的有用信息不足64K,如果我们需要传输的数据达到64K, 就需要在应用层手动分包, 多次发送, 并在接收端手动拼接。
4、UDP协议的无连接:知道对端的IP的端口号就可以直接进行通信,不需要进行连接。
5、UDP协议的不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对端,, UDP协议层也不会给应用层返回任何错误信息。
6、面向数据报:应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。

UDP网络编程过程解析

在这里插入图片描述

1、创建套接字

建立与网卡的关联,协议版本的选择,传输层协议的选择

int socket(int domain, int type, int protocol);
  • domain: 地址域
    AF_INET(协议类型)一般使用ipv4协议
  • type: 套接字类型
    SOCK_STREAM 流式套接字
    SOCK_DGRAM 数据报套接字
  • protocol(协议类型):0-默认;流式套接字默认TCP协议IPPROTO_TCP,数据报套接字默认UDP协议IPPROTO_UDP
  • 返回值:套接字描述符,失败:-1

2、为套接字绑定地址信息(ip、port)

bind函数既可以绑定IPV4版本协议,也可以绑定IPV6版本,两个版本的IP头结点的大小不同,定义sockaddr为了寻求接口的统一,都要使用struct sockaddr

struct sockaddr_in		//IPV4
struct sockaddr_in6		//IPV6

为socket绑定地址信息,确定socket能够操作缓冲区中的哪些数据

int bind(int sockfd, struct sockaddr *addr,socklen_t addrlen);
  • sockfd: 套接字描述符
  • addr: 要绑定的地址信息
  • addrlen:地址信息的长度

3、 接受数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, 
int flags,struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd: socket描述符
  • buf: 用于将存储接收的数据
  • len: 想要接收的数据长度
  • flags: 0-默认是说如果缓冲区没有数据,那么我就阻塞等待
  • src_addr: 用于确定数据的发送端地址信息
  • addrlen: 地址信息的长度
  • 返回值:实际接收的数据长度 ,-1:失败

4、发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, 
int flag, struct sockaddr *dest_addr,socklen_t addrlen)    
  • sockfd: socket描述符,发送数据的时候就是通过这个socked所绑定的地址来发送
  • buf: 要发送的数据
  • len: 要发送的数据长度
  • flag: 0-默认阻塞式发送
  • dest_addr: 数据要发送到的对端地址
  • addrlen: 地址信息长度
  • 返回值:返回实际的发送数据长度,失败返回-1

5、关闭sockfd文件描述符

int close(int fd);

我们说socket函数的返回值是socket描述符,实际也是文件描述符。因为系统的文件描述符有限,我们不再使用这个文件时候,就应该关闭这个描述符,否则可能造成文件描述符泄漏问题。

基于UDP协议的应用层聊天程序

UDP服务端

创建套接字——>绑定地址——>recvfrom——>sendto——>close

//udp服务端
#include<stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
    //创建套接字
    int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(sockfd<0)
    {
        perror("socket error");
        return -1;
    }
    
    struct sockaddr_in ser_addr;
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_port=htons(8000);
    ser_addr.sin_addr.s_addr=inet_addr("192.168.117.130");
    //绑定地址
    socklen_t len=sizeof(struct sockaddr_in);
    int ret=bind(sockfd,(struct sockaddr*)&ser_addr,len);
    if(ret<0)
    {
        perror("bind error");
        close(sockfd);
        return -1;
    }
    //接受数据
    while(1)
    {
        struct sockaddr_in cli_addr;
        char buff[1024]={0};
        ssize_t recv=recvfrom(sockfd,buff,1023,0,(struct sockaddr*)&cli_addr,&len);
        if(recv<0)
        {
            perror("recvfrom error");
            close(sockfd);
            return -1;
        }
        printf("client[ip:%s port:%d] say%s\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),buff);
        memset(buff,1024,0x00);
        scanf("%s",buff);
        sendto(sockfd,buff,sizeof(buff),0,(struct sockaddr*)&cli_addr,len);
    }
    close(sockfd);
    return 0;
}

UDP客户端

客户端程序中,通常我们不推荐手动绑定地址,因为绑定有可能会因为特殊情况失败,但是客户端发送数据的时候,具体用哪个地址和端口我们都无所谓,只要数据能成功发送就可以,所以,客户端程序中我们不手动绑定地址,直到发送数据的时候,操作系统检测到socket没有绑定地址,会自动选择合适的地址和端口为socket绑定,这种绑定方式一般不会出错。

创建套接字——>sendto——>recvfrom——>close

#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>

int main()
{
    int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(sockfd<0)
    {
        perror("socket error");
        return -1;
    }
    struct sockaddr_in ser_addr;
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_port=htons(8000);
    ser_addr.sin_addr.s_addr=inet_addr("192.168.117.130");

    //客户端不推荐绑定地址
    while(1)		//一旦连接成功就一直通信。直至中断进程
    {
        socklen_t len=sizeof(struct sockaddr_in);
        struct sockaddr_in cliaddr;
        char buff[1024]={0};
        scanf("%s",buff);
        int ret=sendto(sockfd,buff,sizeof(buff),0,(struct sockaddr*)&ser_addr,len);
        if(ret<0)
        {
            perror("sendto error");
            close(sockfd);
            return -1;
        }
        memset(buff,1024,0x00);
        ssize_t recv=recvfrom(sockfd,buff,1023,0,(struct sockaddr*)&ser_addr,&len);
        if(recv<0)
        {
            perror("recvfrom error\n");
            return -1;
        }
        printf("server[ip:%s port:%d] say%s\n",inet_ntoa(ser_addr.sin_addr),ntohs(ser_addr.sin_port),buff);
    }
    close(sockfd);
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值