传输层——UDP协议

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;
}
运行结果:

【注】:为什么服务器绑定端口号,但是客户端没有绑定端口号?
             客户端也可以绑定端口号,但是绑定端口号之后,客户端只能使用绑定端口号,其复用性不强,所以一般客户端不绑定端口号。
             由于服务器被很多客户端使用,服务器端口号需要做到众所周知,也因此服务器必须绑定端口号。




猜你喜欢

转载自blog.csdn.net/weixin_39294633/article/details/80928552