嵌入式学习记录5.20(TCP并发服务器)

目录

[一. TCP并发服务器](#一. TCP并发服务器)

[二 .多进程实现TCP并发服务器](#二 .多进程实现TCP并发服务器)

2.1流程框架

2.2具体实现代码

[三. 多线程实现并发服务器](#三. 多线程实现并发服务器)

3.1流程框架

3.2具体实现

一. TCP并发服务器

1> 由于循环服务器使用时,只能等到上一个客户端处理结束后,才能处理下一个客户端

2> 原因是:accept函数是阻塞函数,而数据收发也是阻塞任务,这两个任务目前是顺序执行,当一个任务阻塞时,另一个任务只能等待

3> 为了解决上述顺序执行的多个阻塞任务,让多个阻塞任务可以并发执行,我们可以引入多进程或多线程实现多任务并发执行

4> 多进行实现原理:父进程可以用于接受客户端的连接请求,并创建出一个子进程用于通信

子进程只负责完成跟客户端的通信

5> 多线程实现原理:主线程可以用于接受客户端的连接请求,并创建出一个分支线程用于通信

分支线程只负责完成跟客户端的通信

二 .多进程实现TCP并发服务器

2.1流程框架

cpp 复制代码
//定义信号处理函数
void handler(int signo)
{
    if(signo == SIGCHLD)
    {
        while(waitpid(-1, NULL, WNOHANG) != 0);    
    }
}

//将SIGCHLD信号绑定到信号处理函数中
signal(SIGCHLD, handler);

sfd = socket();           //创建用于通信的套接字文件描述符
bind();                      //绑定ip地址和端口号
listen();                 //将套接字设置成被动监听状态

while(1)
{
    newfd = accept();             //阻塞等待客户端连接请求,并为其创建一个新的用于通信的套接字问津描述符
    pid = fork();             //创建子进程用于处理客户端
    if(pid > 0)
    {
        //父进程
        close(newfd);    
    }elseif(pid == 0)
    {
    //跟当前客户端进行通信
    close(sfd);           //关闭sfd
    recv();              //阻塞读取消息
    send();              //发送消息
    close(newfd);                //关闭套接字
    exit();                 //退出进程
    }
}
close(sfd);                      //关闭监听

2.2具体实现代码

cpp 复制代码
#include<myhead.h>
#define SER_PORT 8888              //服务器端口号
#define SER_IP "192.168.125.113"   //服务器ip地址

//定义信号处理函数
void handler(int signo)
{
    //判断要处理的信号
    if(signo == SIGCHLD)
    {
        while(waitpid(-1, NULL, WNOHANG) != 0);
    }
}




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

    //将子进程的SIGCHLD(17)信号
    //当子进程退出时,会向其父进程发送该信号
    if(signal(SIGCHLD, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }


    //1、为通信创建一个端点
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    //参数1:说明使用的是ipv4通信域
    //参数2:说明使用的是TCP面向连接的通信方式
    //参数3:由于参数2中已经指定通信方式,填0即可
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("socket success sfd = %d\n", sfd);   //3

    //调用端口号快速重用函数
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }

    //2、绑定ip和端口号
    //2.1 准备地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;    //通信域
    sin.sin_port = htons(SER_PORT);    //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);  //ip地址

    //2.2 绑定工作
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    //3、将套接字设置成被动监听状态
    if(listen(sfd, 128)==-1)
    {
        perror("listen error");
        return -1;
    }
    printf("listen success\n");

    //4、阻塞等待客户端的连接
    //4.1 定义用于接受客户端信息的容器
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    int newfd = -1;             //客户端套接字变量

    while(1)
    {   
        //父进程
        newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd == -1)
        {
            perror("accept error");
            return -1;
        }
        printf("[%s:%d]:发来连接请求\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));

        pid_t pid = fork();          //创建子进程
        if(pid > 0)
        {
            //父进程体
            //关闭newfd
            close(newfd);
        
        }else if(pid == 0)
        {
            //关闭sfd
            close(sfd);

            //5、与客户端进行相互通信
            char rbuf[128] = "";            //读取消息内容的容器

            while(1)
            {
                //清空容器
                bzero(rbuf, sizeof(rbuf));

                //从套接字中读取数据
                //int res = read(newfd, rbuf, sizeof(rbuf));
                int res = recv(newfd, rbuf, sizeof(rbuf), 0);
                if(res == 0)
                {
                    printf("客户端已经下线\n");
                    break;
                }

                //将读取的消息展示出来
                printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);

                //将收到的消息处理一下,回复给客户端
                strcat(rbuf, "*_*");

                //讲消息发送给客户端
                //write(newfd, rbuf, strlen(rbuf));
                send(newfd, rbuf, strlen(rbuf), 0);
                printf("发送成功\n");
            }

            //6、关闭套接字
            close(newfd);

            //退出子进程
            exit(EXIT_SUCCESS);

        }else
        {
            perror("fork error");
            return -1;
        }
    }
    close(sfd);


    return 0;
}

三.多线程实现并发服务器

3.1流程框架

cpp 复制代码
//定义线程体函数
void *deal_cli_msg(void *arg)
{
        //跟当前客户端进行通信
    recv();              //阻塞读取消息
    send();              //发送消息
    close(newfd);                //关闭套接字
    pthread_exit(NULL);            //退出线程
}


sfd = socket();           //创建用于通信的套接字文件描述符
bind();                      //绑定ip地址和端口号
listen();                 //将套接字设置成被动监听状态

while(1)
{
    newfd = accept();             //阻塞等待客户端连接请求,并为其创建一个新的用于通信的套接字问津描述符
    pthread_create(&tid, NULL, deal_cli_msg, &info);         //创建分支线程用于跟客户端进行通信
    pthread_detach(tid);                      //将线程设置成分离态
}
close(sfd);                      //关闭监听

3.2具体实现代码

cpp 复制代码
#include<myhead.h>
#define SER_PORT 8888              //服务器端口号
#define SER_IP "192.168.125.113"   //服务器ip地址

//定义一个结构体类型,用于向线程体函数传递参数
struct Info
{
    int newfd; 
    struct sockaddr_in cin;
};


//定义线程体函数
void * deal_cli_msg(void *arg)
{
    //解析传递进来的数据
    int newfd = ((struct Info*)arg)->newfd;
    struct sockaddr_in cin = ((struct Info*)arg)->cin;

    //5、与客户端进行相互通信
    char rbuf[128] = "";            //读取消息内容的容器

    while(1)
    {
        //清空容器
        bzero(rbuf, sizeof(rbuf));

        //从套接字中读取数据
        //int res = read(newfd, rbuf, sizeof(rbuf));
        int res = recv(newfd, rbuf, sizeof(rbuf), 0);
        if(res == 0)
        {
            printf("客户端已经下线\n");
            break;
        }

        //将读取的消息展示出来
        printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);

        //将收到的消息处理一下,回复给客户端
        strcat(rbuf, "*_*");

        //讲消息发送给客户端
        //write(newfd, rbuf, strlen(rbuf));
        send(newfd, rbuf, strlen(rbuf), 0);
        printf("发送成功\n");
    }

    //6、关闭套接字
    close(newfd);

    //退出线程
    pthread_exit(NULL);

}





int main(int argc, const char *argv[])
{
    //1、为通信创建一个端点
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    //参数1:说明使用的是ipv4通信域
    //参数2:说明使用的是TCP面向连接的通信方式
    //参数3:由于参数2中已经指定通信方式,填0即可
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("socket success sfd = %d\n", sfd);   //3

    //2、绑定ip和端口号
    //2.1 准备地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;    //通信域
    sin.sin_port = htons(SER_PORT);    //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP);  //ip地址

    //2.2 绑定工作
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    //3、将套接字设置成被动监听状态
    if(listen(sfd, 128)==-1)
    {
        perror("listen error");
        return -1;
    }
    printf("listen success\n");

    //4、阻塞等待客户端的连接
    //4.1 定义用于接受客户端信息的容器
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);

    while(1)
    {
        //accept函数会预选一个当前未分配的最小的文件描述符
        //即使在阻塞过程中,有更小的文件描述符产生,本次操作的文件描述符也不会更改了
        int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd == -1)
        {
            perror("accept error");
            return -1;
        }

        printf("[%s:%d]:发来连接请求, newfd = %d\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);


        //定义要传递数据的结构体变量
        struct Info buf = {newfd, cin};

        //创建分支线程,用于通信
        pthread_t tid = -1;
        if(pthread_create(&tid, NULL, deal_cli_msg, &buf) == -1)
        {
            printf("pthread_create error\n");
            return -1;
        }


        //将线程设置成分离态
        pthread_detach(tid);

    }
    close(sfd);


    return 0;
}
相关推荐
城南vision12 分钟前
计算机网络——TCP篇
网络·tcp/ip·计算机网络
@小博的博客21 分钟前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
lihuhelihu1 小时前
第3章 CentOS系统管理
linux·运维·服务器·计算机网络·ubuntu·centos·云计算
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
山东布谷科技官方1 小时前
布谷直播源码部署服务器关于数据库配置的详细说明
运维·服务器·数据库·直播系统源码·直播源码·直播系统搭建·直播软件开发
爱吃喵的鲤鱼1 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
Tony聊跨境2 小时前
独立站SEO类型及优化:来检查这些方面你有没有落下
网络·人工智能·tcp/ip·ip
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
love_and_hope2 小时前
Pytorch学习--神经网络--搭建小实战(手撕CIFAR 10 model structure)和 Sequential 的使用
人工智能·pytorch·python·深度学习·学习
Chef_Chen2 小时前
从0开始学习机器学习--Day14--如何优化神经网络的代价函数
神经网络·学习·机器学习