【Linux】TCP网络编程

目录

V1_Echo_Server

V2_Echo_Server多进程版本

V3_Echo_Server多线程版本

V3-1_多线程远程命令执行

V4_Echo_Server线程池版本


V1_Echo_Server

 TcpServer的上层调用如下,和UdpServer几乎一样:

而在InitServer中,大部分也和UDP那里一样,不同的是使用socket时第二个参数是SOCK_STREAM。

除了创建socket和bind外,还有第三步,因为tcp是面向连接的,tcp需要未来不断地能够做到获取连接,需要将server套接字设为listen状态,以便随时等待被获取连接,

其中backlog一般设为较小的数字,比如4、8等。

此时,server处于listen状态,等待别人随时来连接自己,listen就比如饭馆老板一天随时等待客人来吃饭。然后,我们可以添加一个_isrunning的成员变量,以表明服务器的运行状态,初始化为false。

在server处于listen状态后,因为tcp是需要连接的,需要使用accept函数来获取连接:

其中,第一个参数是server的套接字,后两个参数是用来得到是谁来连接server。关键在于accept的返回值:

我们看到accept的返回值竟然是一个文件描述符,这就让我们有点蒙圈了。因为在之前写udp代码时,只有一个文件描述符,那么此时我们难免有这样两个疑问:

  • return fd是什么?
  • return fd 和 _sockfd的关系

我们来将一个小故事,比如你和你的朋友去杭州西湖玩,在那里附近有很多饭馆,有一家叫西湖鱼庄,这家店雇了张三在店外面拉客,正好你在饭点碰到这家饭馆,就被拉了进去吃饭,张三带着你们进了饭店门口,然后张三喊来客人了,出来个人招呼客人,然后李四就出来招呼你们了。然后,张三又去店外面继续拉客,过了不久,张三又拉来了几个客人,到了店里喊又来客人了,出来个人招呼,此时王五出来招呼这几个客人,张三又跑出去继续拉客。在这个过程中,张三不给客人提供服务,只负责拉客。这个西湖鱼庄就是服务器,一个个客户就是一个个连接,而张三就是类成员_sockfd,李四、王五就相当于accept的返回值return fd,这个返回值来给连接提供服务,_sockfd就是用来协助accept获取新连接。把这个只负责获取连接的_sockfd叫做listensockfd(监听套接字)。

把成员变量改为_listensockfd。

如果张三拉客失败,也就是accept的返回值为0,那会怎么样呢?张三当然会继续拉客。

在提供服务时,由于udp是面向数据报,udp只能用recvfrom和sendto这样和网络强相关的接口,而tcp是面向字节流。之前我们学过C/C++的文件流以及管道的字节流,这些都是“流”,实际上它们都是一个东西,Linux下一切皆文件,所以网络、管道等都是文件,所以只要符合相同的流的特性,tcp这里的字节流的读取就相当于文件读取,也就是可以使用read/write进行读取。当使用read进行读取时,表明读取客户端结束(文件中表示读到文件结尾,这点有区别)。

在客户端这里,也是首先创建套接字,然后不需要显式bind,但是一定要有自己的IP和port,所以需要隐式bind,OS会用自己的IP和随机端口号去bind sockfd。客户端也不需要监听,没人回来连接客户端。server在等连接,所以客户端需要发起连接,使用connect调用,

那什么时候进行自动bind呢?在创建连接成功时就会bind!client的代码如下:

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << "server_ip server_port" << std::endl;
        exit(0);
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    //1.创建socket
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }
    //2.connect
    struct sockaddr_in server;
    memset(&server, 0 , sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    ::inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr.s_addr);
    int n = ::connect(sockfd, (struct sockaddr*)&server, sizeof(server));
    if(n < 0)
    {
        std::cerr << "connect socket error\n" << std::endl;
        exit(2);
    }

    while(true)
    {
        std::string message;
        std::cout << "Enter# ";
        std::getline(std::cin, message);
        write(sockfd, message.c_str(), message.size());

        char echo_buffer[1024];
        int n = ::read(sockfd, echo_buffer, sizeof(echo_buffer)-1);
        if(n > 0)
        {
            echo_buffer[n] = 0;
            std::cout << echo_buffer << std::endl;
        }
        else
        {
            break;
        }
    }
    ::close(sockfd);
    return 0;
}

我们编译运行这份代码,当启动第一个客户端时,发现可以正常echo:

然后我们再启动第二个客户端,发现服务器没有和第二个客户端建立连接,也没有echo,

只有把第一个客户端退出后,服务器才能和第二个客户端建立连接,服务器才能echo第二个客户端,

因此,我们发现这版客户端代码没有并发处理能力,一次只能处理一个客户端,这时因为主线程一直在Service内部在运行:

所以,为了解决以上服务器端不能并发处理的问题,

V2_Echo_Server多进程版本

因此,我们在处理Service时,通过创建子进程来处理:

父子进程都要有独立的文件描述符表,而子进程的文件描述符表是从父进程那里拷贝来的,注定了父子进程指向了同样的文件,所以子进程肯定能看见创建的创建的sockfd(代码是共享的,数据以写时拷贝的方式各自私有一份),也就是说,父进程打开了多少个文件,子进程可以看到并且能访问。父进程创建的listensockfd是3文件描述符,子进程创建的sockfd是4号文件描述符,子进程从父进程拷贝了文件描述符表,所以和父进程指向同一个文件。因为子进程不关心3,只关心4,这里的建议是让子进程关闭listensockfd,只保留sockfd。同时要求父进程关闭sockfd,只保留listensockfd,这里是要求,如果父进程不关sockfd,相当于4号文件描述符一直被占用,如果再有客户端来连接服务器,只能使用5号文件描述符来处理,导致父进程的文件描述符一直在被打开而从来没有被关闭,文件描述符的本质就是数组的下标,数组下标肯定是有限个,这就导致了文件描述符泄漏的问题。

所以,我们期望的是父进程把自己该做的做完,然后去回到accept,继续等待被连接。而子进程去执行if(id ==0)内部的代码,这样就能做到服务器采用多进程的方式并发处理连接,

可是,父进程在waitpid时采用的是0(阻塞式等待),所以我们刚才想的理想过程不会发生,子进程在处理任务期间,父进程会阻塞等待,这不是还是一次只能处理一个连接吗?!那怎么解决呢?我们在学习信号的时候,子进程在退出时,会向父进程发送SIGCHID信号,如果对SIGCHID进程ingore,那父进程就不需要等子进程退出了,只负责连接就行了,这种方式是可行的也是最推荐的。

此外,我们还可以这样做:

在子进程中再创建子进程,也就是孙子进程。if(fork() > 0)exit(0)让子进程直接退了,直接留下孙子进程。子进程返回了,父进程就能等待成功然后返回了。当孙子进程处理完后,就会变成孤儿进程,被系统领养,就不用再关心这个孙子进程了。但是这不是最好方案,最好方案就是上面那种。

V3_Echo_Server多线程版本

创建新线程,主线程会等待新线程,这还是串行运行,不能实现并发访问。为此,我们想到之前学过线程分离,不再让主线程等待新线程,而是让新线程分离,

那用于执行任务的文件描述符sockfd怎么交给新线程呢?我们知道,新线程和主线程是共享同一张文件描述符表的,这里绝对不能让主线程和新线程关闭自己不用的套接字fd,也不需要了。我们把Execute函数设置为了static属性,不能访问类内方法,不能访问类内的Service方法,为此,我们创建一个内部类ThreadData:

V3-1_多线程远程命令执行

由远程发过来命令行字符串,server对命令行字符串进行执行,把执行结果返回给远程。建立Command.hpp头文件,

 

我们进行网络的读取,不仅仅可以使用read/write接口,还可以使用recv/send这一对接口,这两个接口不能用来读取udp,只能读取tcp,是面向字节流的读取。

recv/send的flags默认设为0。Command类的设计如下,HandlerCommand函数用于处理客户端传来的字符串,通过Excute函数来把传入的字符串做解释,

那在Excute拿到待解释的命令行字符串后,怎么解释这个字符串呢?我们可以使用popen函数调用:

popen内部会建立一个管道文件,然后创建子进程,执行对应的command命令,内部来帮我们做命令行解析,解析后的内容放到管道文件中,返回FILE*,让我们以文件的方式读取管道。换句话说,未来只需要命令字符串传给popen就可以了,像读文件一样把结果读出来。第二个参数type是"r"/"w"/"a"。通过pclose把对应的管道文件关闭。

class Command
{
public:
    Command()
    {
        _safe_command.insert("ls");
        _safe_command.insert("touch");
        _safe_command.insert("pwd");
        _safe_command.insert("whoami");
        _safe_command.insert("which");   
    }
    ~Command(){}
    bool CheckSafe(const std::string& cmdstr)
    {
        for(auto e : _safe_command)
        {
            if(strncmp(e.c_str(), cmdstr.c_str(), e.size()) == 0)
            {
                return true;
            }
        }
        return false;
    }
    std::string Excute(const std::string& cmdstr)
    {
        if(!CheckSafe(cmdstr)) return "unsafe";
        FILE* fp = popen(cmdstr.c_str(), "r");
        std::string result;
        if(fp)
        {
            char line[1024];
            while(fgets(line, sizeof(line), fp))
            {
                result += line;
            }
            return result;
        }
        return "excute error";
    }
    void HandlerCommand(int sockfd, InetAddr addr)
    {
        while (true)
        {
            char commandbuff[1024];
            ssize_t n = ::recv(sockfd, commandbuff, sizeof(commandbuff) - 1, 0); // TODO
            if (n > 0)
            {
                commandbuff[n] = 0;
                LOG(INFO, "get command from client %s, command : %s\n", addr.AddrStr(), commandbuff); 
                std::string result = Excute(commandbuff);
                ::send(sockfd, result.c_str(), result.size(),0);
            }
            else if (n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error: %s quit\n", addr.AddrStr().c_str());
            }
        }
    }
private:
    std::set<std::string> _safe_command;
};

运行结果如下:

实际上,我们打开Xshell,实际上是打开了一个客户端,在Xshell上输入命令,其实是将命令发送到远端,去请求服务器上的一个长启动的服务,把命令行字符串交给它,由它执行并推送给客户端执行结果。所以,我们所谓的命令执行就是推送到远端。

V4_Echo_Server线程池版本

实际上,这种Service长服务不太适合用线程池,因为线程池中的线程是有上限的,每个线程一直被占用。这次的线程池版本只是一个示例,未来还是要使用V2版本的多线程。创建任务类型task_t,这是线程池中任务的类型,

using func_t = std::function<void()>; 

然后构建任务,放到线程池中去处理:

总结一下tcp,就是通过listensocket套接字去获取连接,把新连接和客户端地址交给别人去处理,可以多并发地去处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925539.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何选择适合的网站关键词?

在做谷歌SEO时&#xff0c;选对关键词是成功的一半。很多人以为找到搜索量大的词就可以了&#xff0c;但实际上&#xff0c;关键词选择并不只是看流量高低&#xff0c;更重要的是与你的用户需求是否匹配。要想精准定位用户&#xff0c;首先需要了解你的目标受众是谁&#xff0c…

AI-agent矩阵营销:让品牌传播无处不在

矩阵营销是一种通过多平台联动构建品牌影响力的策略&#xff0c;而 AI-agent 技术让这一策略变得更加智能化。AI社媒引流王凭借其矩阵管理功能&#xff0c;帮助品牌在多个平台上实现深度覆盖与精准传播。 1. 矩阵营销的优势 品牌触达更广&#xff1a;多平台联动可以覆盖不同用…

Vue教程|搭建vue项目|Vue-CLI新版脚手架

一、安装Node环境 安装Node及Npm环境 Node下载地址:Node.js — Run JavaScript EverywhereNode.js is a JavaScript runtime built on Chromes V8 JavaScript engine.https://nodejs.org/en/ 安装完成后,检查安装是否成功,并检查版本,命令如下: node -v npm -v mac@Macd…

简单好用的折线图绘制!

折线图的概念及作用&#xff1a; 折线图&#xff08;Line Chart&#xff09;是一种常见的图表类型&#xff0c;用于展示数据的变化趋势或时间序列数据。它通过一系列的数据点&#xff08;通常表示为坐标系中的点&#xff09;与这些点之间的线段相连&#xff0c;直观地展示变量…

模型 布鲁姆法则

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。分层提升思维力。 1 布鲁姆法则的应用 1.1 布鲁姆法则在产品开发流程中的应用 背景&#xff1a; 在产品开发领域&#xff0c;创新和效率是关键。布鲁姆法则可以帮助产品经理和设计师系统地提升产品开…

如何通过实验室Lims系统解决效率和数据管理难题?

您的实验室是否还在为这些问题而苦恼呢&#xff1f; 提升企业业务效率&#xff1f; 质量可追溯、数据合规性&#xff1f; 提升客户服务质量&#xff1f; 如何让管理经验和检测数据有效积累? 实验室Lims系统功能 1、业务管理 2、检验管理 3、财务管理 4、客户管理 5、…

【目标跟踪】Anti-UAV数据集详细介绍

Anti-UAV数据集是在2021年公开的专用于无人机跟踪的数据集&#xff0c;该数据集采用RGB-T图像对的形式来克服单个类型视频的缺点&#xff0c;包含了318个视频对&#xff0c;并提出了相应的评估标准&#xff08;the state accurancy, SA)。 文章链接&#xff1a;https://arxiv.…

PyG教程:MessagePassing基类

PyG教程&#xff1a;MessagePassing基类 一、引言二、如何自定义消息传递网络1.构造函数2.propagate函数3.message函数4.aggregate函数5.update函数 三、代码实战1.图数据定义2.实现GNN的消息传递过程3.完整代码4.完整代码的精简版本 四、总结1.MessagePassing各个函数的执行顺…

Win10 系统下使用研华XNavi安装板卡驱动失败

配置&#xff1a;主板 AIMB-705G2&#xff0c;CPU i5-6500&#xff0c;系统 Windows10_64bit_Pro_22H2&#xff0c; 测试&#xff1a; 1、多次安装驱动。FAIL 2、尝试在其他电脑上移植板卡驱动并且使用数字签名安装。FAIL 3、系统更新到WIN10最新版本。FAIL 4、杀毒软件卸…

用三维模型的顶点法向量计算法线贴图

法线贴图的核心概念是在不增加额外多边形数目的情况下&#xff0c;通过模拟细节来改善光照效果。具体流程包括&#xff1a; 法线的计算与存储&#xff1a;通过法线映射将三维法线向量转化为法线贴图的 RGB 值。渲染中的使用&#xff1a;在片段着色器中使用法线贴图来替代原有的…

idea编译与maven编译的问题

先说下idea编译按钮的位置 编译运行时&#xff0c;会在idea底部出现Build面板 比较&#xff1a; idea编译器编译整个项目 maven编译器根据pom.xml的配置&#xff0c;可实现灵活编译 两套编译会遇到的问题&#xff1a; maven 编译成功 &#xff0c;但idea编译失败&#xff…

deepin 安装 chrome 浏览器

deepin 安装 chrome 浏览器 最近好多小伙伴儿和我说 deepin 无法安装最新的谷歌浏览器 其实是因为最新的 谷歌浏览器 其中的一个依赖需要提前安装 提前安装依赖然后再安装谷歌浏览器就可以了 安装 fonts-liberationsudo apt -y install fonts-liberation安装 chrome 浏览器sudo…

《String类》

目录 一、定义与概述 二、创建字符串对象 2.1 直接赋值 2.2 使用构造函数 三、字符串的不可变性 四、常用方法 4.1 String对象的比较 4.1.1 比较是否引用同一个对象 4.1.2 boolean equals(Object anObject)方法&#xff1a;按照字典序比较 4.1.3 int compareTo(Strin…

OpenSSH-9.9p1 OpenSSL-3.4.0 升级步骤详细

前言 收到漏洞扫描通知 OpenSSH 安全漏洞(CVE-2023-38408) OpenSSH 安全漏洞(CVE-2023-51385) OpenSSH 安全漏洞(CVE-2023-51384) OpenSSH 安全漏洞(CVE-2023-51767) OpenSSH 安全漏洞(CVE-2023-48795) OpenSSH&#xff08;OpenBSD SecureShell&#xff09;是加拿大OpenBSD计划…

【Stable Diffusion】安装教程

目录 一、python 安装教程 二、windows cuda安装教程 三、Stable Diffusion下载 四、Stable Diffusion部署&#xff08;重点&#xff09; 一、python 安装教程 &#xff08;1&#xff09;第一步下载 打开python下载页面&#xff0c;找到python3.10.9&#xff0c;点击右边…

Scala身份证上的秘密以及Map的遍历

object test {def main(args: Array[String]): Unit {val id "42032220080903332x"//1.生日是&#xff1f;//字符串截取val birthday id.substring(10,14) //不包括终点下标println(birthday)val year id.substring(6,10) //println(year)//性别&#xff1a;倒数第…

springboot 异步 @Async 的日常使用及失效场景

文章目录 springboot 异步 Async 的日常使用引言一、Async 使用位置二、Async 使用三、注解 Async 失效的情况&#xff08;1&#xff09;调用同一个类中的异步方法&#xff08;内部调用&#xff09;&#xff08;2&#xff09;未使用 EnableAsync 注解&#xff08;3&#xff09;…

Laravel8.5+微信小程序实现京东商城秒杀方案

一、商品秒杀涉及的知识点 鉴权策略封装掊口访问频次限制小程序设计页面防抖接口调用订单创建事务使用超卖防御 二、订单库存系统方案&#xff08;3种&#xff09; 下单减库存 优点是库存和订单的强一致性&#xff0c;商品不会卖超&#xff0c;但是可能导致恶意下单&#xff…

三角网格体的光滑性问题

三角网格体的光滑性问题 在计算机图形学和计算机辅助设计中&#xff0c;C0连续性&#xff08;也称为位置连续性&#xff09;是指两个曲线或曲面在它们的公共边界上具有相同的位置。这意味着它们在边界处没有缝隙或重叠&#xff0c;但它们的切线方向可以不同。C0连续性是最低级…

独家|京东调整职级序列体系

原有的M、P、T、S主序列将正式合并为新的专业主序列P。 作者|文昌龙 编辑|杨舟 据「市象」独家获悉&#xff0c;京东已在近日在内部宣布对职级序列体系进行调整&#xff0c;将原有的M、P、T、S主序列正式合并为新的专业主序列P&#xff0c;合并后的职级体系将沿用原有专业序…