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

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 这是一个基于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;
        }
    }


}

服务端

服务端创建主线程,接收客户端的请求,创建新的子线程,
子线程完成后续交互,子线程中创建新的套接字,返回给客户端,后续交互将在新的套接字中完成.
将子线程分离,线程运行结束将由系统回收资源

// 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>
#include <pthread.h>
#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0




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

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

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

//定义结构体为子线程传递参数
struct thread_arg {
   
    char *ip;
    unsigned char login_status;
    struct sockaddr_in thread_addr;//客户端的地址
}thread_arg;

int main(int argc, char *argv[]){
   


    //验证
    int new_sockfd = TheLogin("172.17.140.183", "8083");
    //接收数据
    //由子线程完成

    //关闭套接字
    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);
    return;
}


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);
    //线程创建
    pthread_t recv_thread;


    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){
             //使用子线程完成后续交互
             struct thread_arg pack;
             pack.ip = ip;
             pack.login_status = login_status;
             pack.thread_addr = client_addr;
             //创建子线程
             pthread_create(&recv_thread, NULL, pthread_todo, &pack);
             printf("子线程创建成功\n");
        } else{
            //回传失败消息
            sendto(sockfd, &login_status, sizeof(login_status), 0, (struct sockaddr *) &client_addr, client_addr_len);
        }
        //将新建的线程设置为分离状态
        pthread_detach(recv_thread);
        printf("子线程分离成功\n");

    }

}

void *pthread_todo(void *arg){
    //子线程函数
     struct thread_arg *pack = (struct thread_arg*)arg;



    //创建新的套接字文件描述符
    int new_sockfd = init_socket(pack->ip, "0");
    printf("子线程创建新的套接字文件描述符:%d\n", new_sockfd);
    sendto(new_sockfd, &pack->login_status, sizeof(pack->login_status), 0,\
    (struct sockaddr *) &pack->thread_addr, sizeof(pack->thread_addr));

    //接收数据
    recv_data(new_sockfd);

    pthread_exit(NULL);

}
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
相关文章
|
3月前
|
负载均衡 算法 安全
基于Reactor模式的高性能网络库之线程池组件设计篇
EventLoopThreadPool 是 Reactor 模式中实现“一个主线程 + 多个工作线程”的关键组件,用于高效管理多个 EventLoop 并在多核 CPU 上分担高并发 I/O 压力。通过封装 Thread 类和 EventLoopThread,实现线程创建、管理和事件循环的调度,形成线程池结构。每个 EventLoopThread 管理一个子线程与对应的 EventLoop(subloop),主线程(base loop)通过负载均衡算法将任务派发至各 subloop,从而提升系统性能与并发处理能力。
190 3
|
17天前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
71 1
|
17天前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
65 1
|
5月前
|
调度 Python
探索Python高级并发与网络编程技术。
可以看出,Python的高级并发和网络编程极具挑战,却也饱含乐趣。探索这些技术,你将会发现:它们好比是Python世界的海洋,有穿越风暴的波涛,也有寂静深海的奇妙。开始旅途,探索无尽可能吧!
136 15
|
7月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
278 23
|
8月前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
226 20
|
8月前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
200 0
|
Java 应用服务中间件
面对海量网络请求,Tomcat线程池如何进行扩展?
【10月更文挑战第4天】本文详细探讨了Tomcat线程池相较于标准Java实用工具包(JUC)线程池的关键改进。首先,Tomcat线程池在启动时即预先创建全部核心线程,以应对启动初期的高并发请求。其次,通过重写阻塞队列的入队逻辑,Tomcat能够在任务数超过当前线程数但未达最大线程数时,及时创建非核心线程,而非等到队列满才行动。此外,Tomcat还引入了在拒绝策略触发后重新尝试入队的机制,以提高吞吐量。这些优化使得Tomcat线程池更适应IO密集型任务,有效提升了性能。
面对海量网络请求,Tomcat线程池如何进行扩展?
|
11月前
|
存储 网络协议 物联网
C 语言物联网开发之网络通信与数据传输难题
本文探讨了C语言在物联网开发中遇到的网络通信与数据传输挑战,分析了常见问题并提出了优化策略,旨在提高数据传输效率和系统稳定性。
|
11月前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
255 2

热门文章

最新文章