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

简介: 这段代码展示了如何使用多进程处理 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应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
10月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
441 1
|
10月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
412 1
|
调度 Python
探索Python高级并发与网络编程技术。
可以看出,Python的高级并发和网络编程极具挑战,却也饱含乐趣。探索这些技术,你将会发现:它们好比是Python世界的海洋,有穿越风暴的波涛,也有寂静深海的奇妙。开始旅途,探索无尽可能吧!
350 15
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
SQL 监控 安全
网络安全与信息安全:漏洞、加密与安全意识
随着互联网的迅猛发展,网络安全和信息安全问题日益受到关注。本文深入探讨了网络安全漏洞、加密技术以及提高个人和组织的安全意识的重要性。通过分析常见的网络攻击手段如缓冲区溢出、SQL注入等,揭示了计算机系统中存在的缺陷及其潜在威胁。同时,详细介绍了对称加密和非对称加密算法的原理及应用场景,强调了数字签名和数字证书在验证信息完整性中的关键作用。此外,还讨论了培养良好上网习惯、定期备份数据等提升安全意识的方法,旨在帮助读者更好地理解和应对复杂的网络安全挑战。
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
541 17
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
404 10
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。