云计算百科
云计算领域专业知识百科平台

(c语言)网络编程之UDP认识与并发服务器实现

一、概述

UDP(User Datagram Protocol) : 用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可用于进行高效率的传输。 但不保证数据的可靠性。

特点 UDP 是无连接的协议 UDP 不保证数据可靠性 UDP 是面向报文的 UDP通信的实时性较高

缺点:没有流控制,没有应答确认机制,不能解决丢包、重发、错序问题。

使用场景 适合于广播/组播式通信中。 MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议 流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输

在这里插入图片描述

二、UDP协议报文

用户数据报由两个部分组成:首部 + 数据部分。首部部分很简单,只有 8 个字节,由四个字段组成,每个字段的长度都是两个字节。

在这里插入图片描述 源端口号: 源端口号,需要对方回信时选用,不需要时全部置0. 目的端口号:目的端口号,在终点交付报文的时候需要用到。 长度:UDP的数据报的长度(包括首部和数据)其最小值为8(只有首部) 校验和:检测UDP数据报在传输中是否有错,有错则丢弃

UDP和TCP协议对比 特点: tcp协议是面向连接、可靠、基于字节流 udp协议是无连接、不可靠、基于数据报文

性能: tcp协议传输效率慢,所需要资源多 udp协议传输效率快,所需要资源少

应用常用: tcp协议常用于文件,邮件传输 udp协议常用于语音,视频,直播等实时性要求较高的场所

三、C语言中UDP实现

常见服务器类型: 处理多客户端的通讯处理方式不同,就导致服务器分为以下几种类型

  • 迭代服务器 大多数UDP都是迭代运行,服务器等待客户端的数据,收到数据后处理该数据,送回其应答,在等待下一个客户端请求。
  • 在这里插入图片描述

  • 并发服务器 并发服务器是指在同一个时刻可以响应多个客户端的请求 本质是创建 多进程/多线程 ,对多数用户的信息进行处理 UDP协议一般默认是不支持多线程并发的 ,因为默认UDP服务器只有一个sockfd,所有的客户端都是通过同一个sockfd进行通信的。udp使用一个socket,如何做到做并发呢?(子进程/子线程中重新创建)
  • (一)、UDP并发服务器之多进程并发

  • 场景设计 多个udp客户端需要先验证密钥是否正确后,才允许进行数据交互。假设密钥为"root"。(类似于登录功能) 服务器接收到客户端信息,需要考虑两种情况 <1>A用户的密钥验证请求消息 <2>B用户的数据交互接收消息

  • 框架图 在这里插入图片描述

  • 使用场景 当UDP服务器与客户端交互多个数据报。问题在于每个客户都是往服务器端的同一个的端口发送数据,并用的同一个sockfd。并发服务器的每一个子进程如何正确区分每一个客户的数据报(涉及到进程的调度问题,如何避免一个子进程读取到不该它服务的客户发送来的数据报)。 解决的方法是服务器(知名端口)等待客户的到来,当一个客户到来后,记下其IP和port,然后服务器fork一个子进程,建立一个socket再bind一个随机端口,然后建立与客户端的连接,并处理该客户的请求。父进程继续循环,等待下一个客户的到来。 在tftpd中就是使用这种技术的 。

  • 代码实现

    UDP服务器代码实现

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <signal.h>
    #define LOGIN_KEY "root"
    #define LOGIN_SUCCESS 1
    #define LOGIN_FAILURE 0
    int init_socket(const char* ip,const char* port)
    {
    // 创建socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == 1)
    {
    perror("socket");
    return 1;
    }
    // 绑定IP + 端口
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(struct sockaddr_in));
    addr.sin_family =AF_INET;
    addr.sin_port = htons(atoi(port));
    inet_aton(ip,&(addr.sin_addr));
    int addrlen = sizeof(struct sockaddr_in);
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen)==1)
    {
    fprintf(stderr,"bind failed\\n");
    return 1;
    }
    return sockfd;
    }
    int authentication_key(const char* ip,const char* port)
    {
    int sockfd = init_socket(ip,port);
    if(sockfd == 1)
    {
    return 1;
    }
    char buf[512]={0};
    int len = sizeof(buf);
    struct sockaddr_in addr;
    int addrlen = sizeof(addr);
    int new_sockfd;
    // 循环验证用户密钥
    while(1)
    {
    memset(buf,0,len);
    ssize_t recvbytes = recvfrom(sockfd,buf,len,0,(struct
    sockaddr*)&addr,&addrlen);
    if(recvbytes == 1)
    {
    perror("redvfrom");
    return 1;
    }
    unsigned char loginstatus = (strncmp(buf,LOGIN_KEY,4)==0)?
    LOGIN_SUCCESS:LOGIN_FAILURE;
    if(loginstatus == LOGIN_SUCCESS)
    {
    pid_t pid = fork();
    if(pid == 1)
    {
    perror("fork");
    return 1;
    }
    else if(pid == 0)
    {
    // 执行的是子进程
    close(sockfd);//密钥验证成功,不需要sockfd文件描述符
    new_sockfd = init_socket(ip,"0");// 绑定0端口,系统会随机分配一个可用的端口号
    sendto(new_sockfd,&loginstatus,sizeof(loginstatus),0,(struct
    sockaddr*)&addr,addrlen);
    break;
    }
    }
    else
    {
    // 登录失败,使用原端口回复信息
    ssize_t ret =
    sendto(sockfd,&loginstatus,sizeof(loginstatus),0,(struct sockaddr*)&addr,addrlen);
    }
    }
    return new_sockfd;
    }
    // 接收数据
    void recv_data(int sockfd)
    {
    char buf[512]={0};
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    while(1)
    {
    memset(buf,0,sizeof(buf));
    ssize_t recvbytes = recvfrom(sockfd,buf,sizeof(buf),0,(struct
    sockaddr*)&client_addr,&addrlen);
    if(recvbytes== 1)
    {
    perror("recvfrom");
    exit(EXIT_FAILURE);
    }
    printf("client ip:%s\\n",inet_ntoa(client_addr.sin_addr));
    printf("client port:%d\\n",ntohs(client_addr.sin_port));
    printf("client content:%s\\n",buf);
    if(strncmp(buf,"quit",4)==0)
    {
    break;
    }
    }
    close(sockfd);
    exit(EXIT_SUCCESS);
    }
    void signal_handler(int signum)
    {
    // 回收子进程的资源
    waitpid(1,NULL,WNOHANG);
    printf("%s\\n",strsignal(signum));
    }
    int main(int argc,char* argv[])
    {
    if(argc !=3)
    {
    fprintf(stderr,"%s ip port.\\n",argv[0]);
    exit(EXIT_FAILURE);
    }
    // 回收僵尸态的子进程[子进程结束后,会发SIGCHLD信号]
    if(signal(SIGCHLD,signal_handler)==SIG_ERR)
    {
    perror("signal error.");
    exit(EXIT_FAILURE);
    }
    // 验证秘钥
    int sockfd = authentication_key(argv[1],argv[2]);
    // recv data
    recv_data(sockfd);
    return 0;
    }

    UDP客户端代码实现

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #define LOGIN_SUCCESS 1
    #define LOGIN_FAILURE 0
    // 发送交互消息
    void send_message(int sockfd,struct sockaddr_in* addr,int addrlen)
    {
    char buf[512]={0};
    while(1)
    {
    memset(buf,0,sizeof(buf));
    printf("client:");
    fgets(buf,sizeof(buf),stdin);
    buf[strlen(buf)1]='\\0';
    int ret = sendto(sockfd,buf,strlen(buf),0,(struct
    sockaddr*)addr,addrlen);
    if(ret == 1)
    {
    perror("client sendto");
    exit(EXIT_FAILURE);
    }
    if(strncmp(buf,"quit",4)==0)
    {
    break;
    }
    }
    }
    void send_authentication_key(int sockfd,struct sockaddr_in* addr,struct
    sockaddr_in* server_data_addr,int addrlen)
    {
    char buf[512]={0};
    unsigned char flag = LOGIN_FAILURE;
    while(1)
    {
    putchar('>');
    memset(buf,0,sizeof(buf));
    fgets(buf,sizeof(buf),stdin); //接收用户的输入
    buf[strlen(buf)1]='\\0';
    int ret = sendto(sockfd,buf,strlen(buf),0,(struct
    sockaddr*)addr,addrlen);
    if(ret == 1)
    {
    perror("client sendto");
    exit(EXIT_FAILURE);
    }
    // 接收服务器返回的消息
    ssize_t recvbytes = recvfrom(sockfd,&flag,sizeof(flag),0,(struct
    sockaddr*)server_data_addr,&addrlen);
    if(recvbytes == 1)
    {
    perror("client recvfrom");
    exit(EXIT_FAILURE);
    }
    if(flag == LOGIN_SUCCESS)
    {
    break;
    }
    }
    }
    int main(int argc,char* argv[])
    {
    if(argc !=3)
    {
    fprintf(stderr,"%s ip port.\\n",argv[0]);
    exit(EXIT_FAILURE);
    }
    // 创建socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == 1)
    {
    perror("socket");
    return 1;
    }
    // 服务器端的地址
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(struct sockaddr_in));
    addr.sin_family =AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],&(addr.sin_addr));
    int addrlen = sizeof(struct sockaddr_in);
    // 处理数据的服务端信息
    struct sockaddr_in server_data_addr;
    memset(&server_data_addr,0,sizeof(struct sockaddr_in));
    server_data_addr.sin_family =AF_INET;
    // 发送密钥信息
    send_authentication_key(sockfd,&addr,&server_data_addr,addrlen);
    // 发送交互消息
    send_message(sockfd,&server_data_addr,addrlen);
    //关闭文件描述符
    close(sockfd);
    return 0;
    }

    在这里插入图片描述

    (二)、UDP并发服务器之多线程并发

    多线程并发服务器和多进程并发服务器的思路大同小异,都是服务器端等待客户端,当一个客户端到来后,记录其IP和port,然后同理,创建子线程,建立一个socket再bind一个随机端口,然后建立与客户端的连接,并处理该客户端的请求。父线程继续循环,等待下一个客户端的到来。 一个进程是可以绑定多个端口。

  • 框架图 在这里插入图片描述
  • 代码实现 服务端代码:
  • #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <pthread.h>
    #define LOGIN_KEY "root"
    #define LOGIN_SUCCESS 1
    #define LOGIN_FAILURE 0
    typedef struct{
    char ip[256];// ip
    unsigned char flag; // 登录标识
    struct sockaddr_in addr;// 数据交互的地址
    }packet_t;
    int init_socket(const char* ip,const char* port)
    {
    // 创建socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == 1)
    {
    perror("socket");
    return 1;
    }
    // 绑定IP + 端口
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(struct sockaddr_in));
    addr.sin_family =AF_INET;
    addr.sin_port = htons(atoi(port));
    inet_aton(ip,&(addr.sin_addr));
    int addrlen = sizeof(struct sockaddr_in);
    if(bind(sockfd,(struct sockaddr*)&addr,addrlen)==1)
    {
    fprintf(stderr,"bind failed\\n");
    return 1;
    }
    return sockfd;
    }
    void* message_handle(void* argv)
    {
    void recv_data(int sockfd);
    // 接收参数
    packet_t* packet = (packet_t*)argv;
    //创建新的socket
    int new_sockfd = init_socket(packet->ip,"0");
    if(new_sockfd == 1)
    {
    perror("init_socket");
    return NULL;
    }
    // 给客户端响应登录成功
    sendto(new_sockfd,&(packet->flag),sizeof(packet->flag),0,(struct
    sockaddr*)&(packet->addr),sizeof(struct sockaddr));
    // 数据交互
    recv_data(new_sockfd);
    pthread_exit(NULL);
    }
    void authentication_key(const char* ip,const char* port)
    {
    int sockfd = init_socket(ip,port);
    if(sockfd == 1)
    {
    return 1;
    }
    char buf[512]={0};
    int len = sizeof(buf);
    struct sockaddr_in addr;
    int addrlen = sizeof(addr);
    int new_sockfd;
    pthread_t pid;
    // 循环验证用户密钥
    while(1)
    {
    memset(buf,0,len);
    ssize_t recvbytes = recvfrom(sockfd,buf,len,0,(struct
    sockaddr*)&addr,&addrlen);
    if(recvbytes == 1)
    {
    perror("redvfrom");
    return 1;
    }
    unsigned char loginstatus = (strncmp(buf,LOGIN_KEY,4)==0)?
    LOGIN_SUCCESS:LOGIN_FAILURE;
    if(loginstatus == LOGIN_SUCCESS)
    {
    // 创建子线程
    packet_t packet;
    strcpy(packet.ip,ip);
    packet.flag = loginstatus;
    packet.addr = addr;
    pthread_create(&pid,NULL,message_handle,&packet);
    // 线程分离
    pthread_detach(pid);
    }
    else
    {
    // 登录失败,使用原端口回复信息
    ssize_t ret =
    sendto(sockfd,&loginstatus,sizeof(loginstatus),0,(struct sockaddr*)&addr,addrlen);
    }
    }
    }
    // 接收数据
    void recv_data(int sockfd)
    {
    char buf[512]={0};
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    while(1)
    {
    memset(buf,0,sizeof(buf));
    ssize_t recvbytes = recvfrom(sockfd,buf,sizeof(buf),0,(struct
    sockaddr*)&client_addr,&addrlen);
    if(recvbytes== 1)
    {
    perror("recvfrom");
    exit(EXIT_FAILURE);
    }
    printf("client ip:%s\\n",inet_ntoa(client_addr.sin_addr));
    printf("client port:%d\\n",ntohs(client_addr.sin_port));
    printf("client content:%s\\n",buf);
    if(strncmp(buf,"quit",4)==0)
    {
    break;
    }
    }
    close(sockfd);
    }
    int main(int argc,char* argv[])
    {
    if(argc !=3)
    {
    fprintf(stderr,"%s ip port.\\n",argv[0]);
    exit(EXIT_FAILURE);
    }
    // 验证秘钥
    authentication_key(argv[1],argv[2]);
    return 0;
    }

    客户端代码与进程那块一样

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » (c语言)网络编程之UDP认识与并发服务器实现
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!