C语言 网络编程(八)并发的UDP服务端 以进程完成功能

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。

网络(八)并发的UDP服务端 以进程完成功能

多进程处理 UDP客户端

// todo UDP发送端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0
//?发送数据
//?@param fd 套接字描述符
//?@param addr 目标地址
//?@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen);

void login(int fd, struct sockaddr_in * addr ,struct sockaddr_in * new_addr , socklen_t new_addrlen);

//命令行参数 ip port
int main(int argc, char *argv[] ){
   
//    if(argc!= 3){
   
//        printf("Usage: %s ip port\n", argv[0]);
//        exit(EXIT_FAILURE);
//    }

    //!通过socket函数创建套接字
    //!@param domain 协议族,AF_INET表示IPv4协议族
    //!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
    //!@param protocol 协议,一般为0 让系统⾃动识别
    //!@return 成功返回套接字描述符,失败返回-1
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1){
       //创建套接字失败
        perror("socket err");
        exit(EXIT_FAILURE);
    }

    //准备接收消息的地址
    /*      struct sockaddr_in {
                short int sin_family; // 地址族 AF_INET
                unsigned short int sin_port; // 端口号
                struct in_addr sin_addr;// IP地址
                unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
            };
    */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8083);//htons函数将主机字节序转换为网络字节序

    //addr.sin_addr.s_addr=inet_addr("192.168.74.1");//inet_addr()将点分十进制IP地址转换为网络字节序IP地址

    //inet_aton()将点分十进制IP地址转换为网络字节序IP地址
    //@param ip 字符串形式的IP地址
    //@param in_addr 结构体变量,用于存储IP地址
    int ret=inet_aton("172.17.140.183", &addr.sin_addr); // 成功返回⾮0,失败返回0
    if(ret == 0){
   
        perror("inet_aton err");
        exit(EXIT_FAILURE);
    }
    printf("ip == %d\n",addr.sin_addr.s_addr);



    //inet_ntoa()将网络字节序IP地址转换为点分十进制IP地址
    //char *ip=inet_ntoa(addr.sin_addr); // 成功返回⾮0,失败返回0
    //printf("ip == %s\n",ip);

    //获取  和服务端的新建的子进程通信
    struct sockaddr_in new_addr;
    login(fd, &addr, &new_addr, sizeof(new_addr));

    //与新的子进程通信
    send_data(fd, &new_addr, sizeof(new_addr));

    return 0;
}

//!发送数据
//!@param fd 套接字描述符
//!@param addr 目标地址
//!@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen){
   

    while (1){
   
        int n = 0;//返回发送的字节数
        char buf[1024] = {
   0};
        printf("请输入要发送的消息:");
        fgets(buf, 1024, stdin);


        //!发送数据
        //!@param fd 套接字描述符
        //!@param buf 发送缓冲区
        //!@param len 发送缓冲区长度
        //!@param flags 发送标志  0 表示默认操作
        //!@param addr 目标地址
        //!@param addrlen 地址长度
        //!@return 成功返回发送的字节数,失败返回-1
        n= sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)addr, addrlen);
        if(n == -1){
   
            perror("sendto err");
            exit(EXIT_FAILURE);
        }

        if(strncmp(buf, "exit",4) == 0){
   
            break;
        }
    }

}


void login(int fd, struct sockaddr_in * addr ,struct sockaddr_in * new_addr , socklen_t addrlen){
   
    char login_status=LITTLE_ENDIAN;

    while (1){
   
        int n = 0;//返回发送的字节数
        char buf[1024] = {
   0};
        printf("请输入要发送的消息:");
        fgets(buf, 1024, stdin);



        n= sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)addr, addrlen);
        if(n == -1){
   
            perror("sendto err");
            exit(EXIT_FAILURE);
        }

        //接收消息服务器的响应
        n= recvfrom(fd, &login_status, sizeof(login_status), 0, (struct sockaddr *)new_addr, &addrlen);
        if(n == -1){
   
            perror("recvfrom err");
            exit(EXIT_FAILURE);
        }
        if(login_status == LOGIN_SUCCESS){
   
            printf("登录成功\n");
            printf("新的子进程的地址为:%s:%d\n",inet_ntoa(new_addr->sin_addr),ntohs(new_addr->sin_port));
            break;
        }else if(login_status == LOGIN_FAIL){
   
            printf("登录失败\n");
            continue;
        }


        if(strncmp(buf, "exit",4) == 0){
   
            break;
        }
    }


}

多进程 处理UDP服务端

父进程负责创建套接字,绑定到指定端口,等待客户端的连接请求,创建子进程处理客户端的请求。
子进程中返回一个新建的套接字,后续与客户端通信将使用新的套接字,在子进程中完成。


// todo UDP服务器端程序
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0


void todo(int signum){
   
    printf("收到信号%d\n",signum);

    waitpid(-1, NULL, WNOHANG);
}

//接收数据
void recv_data(int sockfd);

//初始化套接字
int  init_socket(char *ip, char* port);

int TheLogin(char *ip, char * port);

int main(int argc, char *argv[]){
   
    //处理僵死的子进程
    signal(SIGCHLD, todo);

    //验证
    int new_sockfd = TheLogin("172.17.140.183", "8083");
    //接收数据
    recv_data(new_sockfd);

    //关闭套接字
    close(new_sockfd);
    return 0;
}

//接收数据
void recv_data(int sockfd) {
   
    struct sockaddr_in client_addr;//客户端的地址
    int client_addr_len = sizeof(client_addr);

    while(1) {
   
        char recv_buf[1024]={
   0};

        //接收数据
        //*@param sockfd 套接字描述符
        //*@param buf 接收缓冲区
        //*@param len 接收缓冲区长度
        //*@param flags 接收标志
        //*@param src_addr 发送方地址
        //*@param addrlen 发送方地址长度
        //*@return 成功返回接收到的字节数,失败返回-1
        int ret = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &client_addr, &client_addr_len);
        if (ret == -1) {
            perror("recvfrom err");
            exit(EXIT_FAILURE);
        }
        //打印接收到的信息
        char *ip_str = inet_ntoa(client_addr.sin_addr);//将网络字节序IP地址转换为点分十进制IP地址
        int port = ntohs(client_addr.sin_port); //将网络字节序端口号转换为主机字节序端口号
        printf("接收到来自%s:%d的数据:%s\n", ip_str, port, recv_buf);

        if(strncmp(recv_buf, "exit", 4) == 0){
            //退出程序
            break;
        }
    }
    close(sockfd);
    exit(EXIT_SUCCESS);
}


int  init_socket(char *ip,char *port){
    //!通过socket函数创建套接字
    //!@param domain 协议族,AF_INET表示IPv4协议族
    //!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
    //!@param protocol 协议,一般为0 让系统⾃动识别
    //!@return 成功返回套接字描述符,失败返回-1
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1){    //创建套接字失败
        perror("socket err");
        exit(EXIT_FAILURE);
    }

    //准备服务器地址
    /*      struct sockaddr_in {
                short int sin_family; // 地址族 AF_INET
                unsigned short int sin_port; // 端口号
                struct in_addr sin_addr;// IP地址
                unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
            };
    */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));

    addr.sin_family=AF_INET;
    addr.sin_port=htons(atoi(port));//htons函数将主机字节序转换为网络字节序

    //inet_aton()将点分十进制IP地址转换为网络字节序IP地址
    //*@param ip 字符串形式的IP地址
    //*@param in_addr 结构体变量,用于存储IP地址
    int ret=inet_aton(ip, &addr.sin_addr); // 成功返回⾮0,失败返回0
    if(ret == 0){
        perror("inet_aton err");
        exit(EXIT_FAILURE);
    }


    //!绑定套接字到服务器地址
    //!@param sockfd 套接字描述符
    //!@param addr 服务器地址
    //!@param addrlen 服务器地址长度
    //!@return 成功返回0,失败返回-1
    int ret2 = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret2 == -1){
        perror("bind err");
        exit(EXIT_FAILURE);
    }


    return fd;
}


int TheLogin(char *ip, char *port){
    unsigned char login_status;
    int new_sockfd;
    //初始化套接字
    int sockfd = init_socket(ip, port);



    struct sockaddr_in client_addr;//客户端的地址
    int client_addr_len = sizeof(client_addr);

    while(1) {
        char recv_buf[1024]={0};

        int ret = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &client_addr, &client_addr_len);
        if (ret == -1) {
            perror("recvfrom err");
            exit(EXIT_FAILURE);
        }
        //打印接收到的信息
        char *ip_str = inet_ntoa(client_addr.sin_addr);//将网络字节序IP地址转换为点分十进制IP地址
        int port = ntohs(client_addr.sin_port); //将网络字节序端口号转换为主机字节序端口号
        printf("接收到来自%s:%d的数据:%s\n", ip_str, port, recv_buf);

        //登录验证
        //判断是否为登录请求
        login_status = ( strncmp(recv_buf, "login",5)==0 ? LOGIN_SUCCESS: LOGIN_FAIL ) ;
        if(login_status == LOGIN_SUCCESS){
            //使用子进程完成后续的交互
            int pid = fork();
            if(pid == -1){
                perror("fork err");
                exit(EXIT_FAILURE);
            }else if(pid == 0){
                printf("开始了新的进程\n");
                //子进程将关闭父进程的套接字
                close(sockfd);
                //子进程使用新的套接字完成后续的交互
                //使用0号,系统将自动分配一个可用端口号
                new_sockfd = init_socket(ip, "0");
                printf("新的套接字文件描述符:%d\n", new_sockfd);
                //子进程通过新的端口 发送成功消息
                sendto(new_sockfd, &login_status, sizeof(login_status), 0, (struct sockaddr *) &client_addr, client_addr_len);
                break;
            }

        } else{
            //回传失败消息
            sendto(sockfd, &login_status, sizeof(login_status), 0, (struct sockaddr *) &client_addr, client_addr_len);
        }


    }
    //返回新的套接字
    return new_sockfd;
}
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
6天前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
29 10
|
24天前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
48 2
|
24天前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
55 1
|
2月前
|
网络协议 Unix C语言
C语言 网络编程(十六)广播和组播
广播和组播是网络通信的重要方式。广播允许一台主机向子网内所有主机发送数据包,常用于局域网内的消息传播;组播则将数据包发送给特定的一组主机,适用于视频会议等应用场景。广播地址如 `192.168.1.255` 用于同一子网的所有主机。组播地址如 `224.0.0.0` 至 `239.255.255.255` 标识特定主机群。C语言示例展示了如何通过 UDP 实现广播和组播通信。此外,UNIX域套接字用于同一机器上进程间的高效通信。
108 14
|
2月前
|
网络协议 算法 网络性能优化
C语言 网络编程(十五)套接字选项设置
`setsockopt()`函数用于设置套接字选项,如重复使用地址(`SO_REUSEADDR`)、端口(`SO_REUSEPORT`)及超时时间(`SO_RCVTIMEO`)。其参数包括套接字描述符、协议级别、选项名称、选项值及其长度。成功返回0,失败返回-1并设置`errno`。示例展示了如何创建TCP服务器并设置相关选项。配套的`getsockopt()`函数用于获取这些选项的值。
|
2月前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
2月前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
2月前
|
缓存 运维 NoSQL
使用 psutil 获取硬件、网络以及进程信息
使用 psutil 获取硬件、网络以及进程信息
42 0
|
2月前
|
网络协议
网络协议概览:HTTP、UDP、TCP与IP
理解这些基本的网络协议对于任何网络专业人员都是至关重要的,它们不仅是网络通信的基础,也是构建更复杂网络服务和应用的基石。网络技术的不断发展可能会带来新的协议和标准,但这些基本协议的核心概念和原理将继续是理解和创新网络技术的关键。
104 0
|
2天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【10月更文挑战第23天】在数字时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将探讨网络安全漏洞、加密技术和安全意识等方面的内容,以帮助读者更好地了解如何保护自己的网络安全。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,我们将为读者提供一些实用的建议和技巧,以增强他们的网络安全防护能力。