Linux(十八)网络基础:深入认识UDP(编写UDP服务器)

UDP协议端格式

这里写图片描述
16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度;
如果校验和出错,就会直接丢弃

UDP特点

UDP传输的过程类似于寄信
*无连接:知道对短的IP和端口号就可以直接进行传输,不需要建立连接;
*不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发送到对方,UDP协议层也不会给应用层返回任何错误信息;
*面向数据报:不能够灵活的控制读写数据的次数和数量;

面向数据报

应用层交给UDP多长的报文,UDP原样发送,既不会查分,也不会合并;

UDP的缓冲区
*UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议,进行后续的传输动作;
*UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的YDO报文就会被丢弃

DP的socket既能读也能写,这个概念叫做全双工

UDP使用注意事项
我们注意到UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长队是64K(包含UDP首部)。
所以,如果我们需要传输的数据超过64K,那么就需要我们在应用层手动的分包,多次发送,并在接收端手动拼装

基于UDP的应用层协议
*NFS:网络文件系统
*TFTP:简单文件传输协议
*DHCP:动态主机配置协议
*BOOTP:启动协议(用于无盘设备启动)
*DNS:域名解析协议
还包括自己写UDP程序时自定义的应用层协议;

编写UDP网络程序

地址转换函数
在我们上节课讲的sockaddr_in结构中的成员struct in_addr sin_addr表示32位IP地址,但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换

字符串转in_addr函数
#include <arpa/inet.h>
int inet_aton(const char*strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family,const char *strptr,void *addrptr);
in_addr转字符串函数
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr还可以转换IPv6的in6_addr,因此函数接口是void *addrptr

关于inet_ntoa
inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存IP的结果,那么是否需要手动释放呢?
man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区,这个时候不需要我们手动释放
那么问题来了,如果我们多次调用这个函数,就会把上一次的结果覆盖掉
所以要是有多个线程调用inet_ntoa就会出现线程安全问题
因此在多线程环境下,就用inet_ntop,这个函数由调用者提供一个缓冲区来保存结果,可以规避线程安全问题

下面我们来编写一个简单的UDP网络程序

服务器端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        printf("usage %s [IP] [port]\n",argv[0]);//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[2]));//将char型的字符串转换为整形的
    //in_addr用来表示一个IP地址,其实就是一个32位的数
    //inet_addr功能是将一个点分十进制的IP转换为长整数型
    //且它返回的地址已经是网络字节序列了,所以不用htonl函数
    local.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0)
    {
        printf("bind error\n");
        return 3;
    }
    char buf[1024];
    struct sockaddr_in client;
    while(1)
    {
        socklen_t len = sizeof(client);
        buf[0] = 0;//初始化
        ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,
                             (struct sockaddr *)&client,&len);
        //ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flag,
        //(struct sockaddr *)from,socket_t *fromlen)
        //recvfrom函数中参数
        //buf:接收缓冲区
        //len:缓冲区长度
        //flags:调用操作方式
        //from:指针,指向装有源地址的缓冲区,用(struct sockaddr*)强转
        //fromlen:指针,指向from缓冲区长度值
        if(s > 0)
        {
            buf[s] = 0;//相当于增加了一个\0,且这个数组本身就是char型
            //inet_ntoa(struct in_addr in)
            //将in_addr类型的参数转换为字符串
            printf("[%s|%d]:%s\n",inet_ntoa(client.sin_addr)
                   ,ntohs(client.sin_port),buf);
            sendto(sock,buf,strlen(buf),0,(struct sockaddr *)&client,len);
            //int sendto(socket s,const void *msg,int len,unsigned int flags,
            //const struct sockaddr *to,int tolen) 
        }
    }
    close(sock);
    return 0;

}

客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        printf("usage %s [IP] [port]\n",argv[0]);//argv[0]是什么
        return 1;
    }
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0)
    {
        printf("socket error\n");
        return 2;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));//将char型的字符串转换为整形的
    //in_addr用来表示一个IP地址,其实就是一个32位的数
    //inet_addr功能是将一个点分十进制的IP转换为长整数型
    //且它返回的地址已经是网络字节序列了,所以不用htonl函数
    server.sin_addr.s_addr = inet_addr(argv[1]);

    char buf[1024];
    struct sockaddr_in peer;
    while(1)
    {
        socklen_t len = sizeof(peer);
        buf[0] = 0;//初始化
        printf("please enter#");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s < 0)
        {
            printf("read error\n");
            return 3;
        }
        buf[s-1] = 0;
        sendto(sock,buf,strlen(buf),0,(struct sockaddr *)&server,sizeof(server));
        //int sendto(socket s,const void *msg,int len,unsigned int flags,
        //const struct sockaddr *to,int tolen) 
        ssize_t q = recvfrom(sock,buf,sizeof(buf)-1,0,
                             (struct sockaddr *)&peer,&len);
        if(q > 0)
        {
            buf[q] = 0;
            printf("perr return %s\n",buf);
        }
    }
    close(sock);
    return 0;

}

实现结果
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/mignatian/article/details/80507903