linux实现网络程序

1️⃣ 在linux下,通过套接字实现服务器和客户端的通信。
2️⃣ 实现单线程、多线程通信。或者实现线程池来通信。
3️⃣ 优化通信,增加守护进程。

有情提醒,类里面默认的函数是内联。内联函数在调用的地方展开,没有函数地址,无法保持和传递。

一 、

1.1实现TCP服务器
创建socket文件描述符(TCP/UDP,客户端+服务端)
int socket(int domain, int type, int protocol);
绑定端口号(TCP/UDP,服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端
int connect(int sockfd, const struct sockaddr*addr,socklen_t addrlen);
 void init()
    {
        // 1.创建套接字
        // 1.1 协议族 1.2 SOCK_STREAM是一个有序、可靠、面向连接 的双向字节流 1.3建立一个流式套接字
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s,%d", strerror(errno), listenSock_);

        // 2.bind绑定
        // 2.1填充服务器信息
        // sockaddr_in是系统封装的一个结构体,
        // 具体包含了成员变量:sin_family、sin_addr、sin_zero
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        // 绑定INADDR_ANY,管理一个套接字不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。
        // 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)。
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0) // 说明绑定错误
        {
            logMessage(FATAL, "bind :%s", strerror(errno));
            exit(BIND_ERR);
        }

        logMessage(DEBUG, "bind :%s,%d", strerror(errno), listenSock_);

        // 3.监听socket
        if (listen(listenSock_, 5) < 0)
        {
            logMessage(FATAL, "listen :%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen :%s,%d", strerror(errno), listenSock_);

        // 4.启动线程池
        tp_ = ThreadPool<Task>::getInstance();
    }
	void loop()
    {
        // 启动线程池
        tp_->start();
        logMessage(DEBUG, "thread pool start success ,thread number : %d", tp_->ThreadNum());

        // signal(SIGCHLD,SIG_IGN);//基类忽略子类信号
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket

            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                logMessage(WARINING, "accept :%s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port); // 将一个16位数由网络字节顺序转换为主机字节顺序
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept :%s | %s[%d],socket fd: %d", strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.3 版本 --线程池版本
            // 5.3.1 构建任务
            //  Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            //  tp_->push(t);//将获取的任务给线程池,目前线程池只有5个线程

            // 5.3.2
            // 直接使用回调函数
            Task t(serviceSock, peerIp, peerPort, execCommand);
            // Task::callback_t call = execCommand;
            tp_->push(t);
            // popen传入命令字符串,让它能以文件的方式读到命令接口
        }
    }
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
sockfd,利用系统调用socket()建立的套接字描述符,通过bind()绑定到一个本地地址(一般为服务器的套接字),并且通过listen()一直在监听连接;
addr,指向struct sockaddr的指针,该结构用通讯层服务器对等套接字的地址(一般为客户端地址)填写,返回地址addr的确切格式由套接字的地址类别(比如TCP或UDP)决定;若addr为NULL,没有有效地址填写,这种情况下,addrlen也不使用,应该置为NULL;
addrlen,一个值结果参数,调用函数必须初始化为包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值;

上边绑定的时候需要将16位主机字节序转16位网络字节序。还有设置=cockaddr_in的结构体里面有域、协议家族该结构体是系统封装的。就是处理网络字节和主机字节的相互转换。获取连接的时候需要重新将网络字节序转化为主机字节序。accept返回的是一个新的socket

1.2实现UDP客户端
创建socket套接字
connect向服务器发起链接请求

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    //  ./clientTcp serverIp serverPort
    //默认argc为1,argv[0]为程序名称。如果输入一个参数,则argc为2
    std::string serverIp=argv[1];
    uint16_t serverPort=atoi(argv[2]);
    //1.创建soket
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        std::cerr<<"socket:"<<strerror(errno)<<std::endl;
        exit(SOCKET_ERR);
    }
    //2.CONNECT向服务器发起链接请求
    //2.1先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverPort);//将一个16位主机字节顺序转换成网络字节顺序
    inet_aton(serverIp.c_str(),&server.sin_addr);//将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)。
    //2.2发起请求connect自动绑定bind
    if(connect(sock,(const struct sockaddr *)&server,sizeof(server))!=0)
    {
        std::cerr<<"connect:"<<strerror(errno)<<std::endl;
        exit(CONN_ERR);
    }
    std::cout<<"inof : connect success: "<<sock<<std::endl;
    
    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout<<"请输入你的消息>>>";
        std::getline(std::cin,message);
        if(strcasecmp(message.c_str(),"quit")==0)
        {
            quit=true;
        }
       size_t s=write(sock,message.c_str(),message.size());
        if(s>0)
        {
            message.resize(1024);
            ssize_t s=read(sock,(char *)(message.c_str()),1024);
            if(s>0)
            message[s]=0;
            std::cout<<"Server Echo>>>"<<message<<std::endl;
        }
        else if(s<=0)
        {
            break;
        }

    }
    
    close(sock);
    return 0;
}

以上代码主要是创建套接字,获取远端信息,端口和ip将主机字节顺序转换为网络字节顺序。使用connect请求连接。

#include <sys/types.h> 					
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:
第一个参数:int sockdf:
		    socket文件描述符
第二个参数: const struct sockaddr *addr:
			传入参数,指定服务器端地址信息,含IP地址和端口号
第三个参数:socklen_t addrlen:
			传入参数,传入sizeof(addr)大小
返回值:
	成功: 0
	失败:-1,设置errno

connect()函数返回后并不进行数据交换。而是要等服务器端 accept 之后才能进行数据交换(read、write)。客户端端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

二、

我们这边要注意既然是实现服务器与客户端互通。我们提供服务就需要考虑让多人连接单人连接等问题。
以下我们实现单线程和多线程版本。
提供服务:

void transService(int sock,const std::string &clientIp,uint16_t clientPort)
{
assert(sock>=0);
assert(!clientIp.empty());
assert(clientPort>=1024);

    char inbuffer[BUFFER_SIZE];
    while (true)
    {
        size_t s=read(sock,inbuffer,sizeof(inbuffer)-1);
        if(s>0)
        {
            inbuffer[s]='\0';
            if(strcasecmp(inbuffer,"quit")==0)
            {
                logMessage(DEBUG,"client quit -- %s[%d]",clientIp.c_str(),clientPort);
                break;
            }
            logMessage(DEBUG,"trans before: %s[%d]>>>%s",clientIp.c_str(),clientPort,inbuffer);

           //大小写转换
           for(int i = 0;i<s;i++)
           {
                if(isalpha(inbuffer[i])&&islower(inbuffer[i]))
                {
                    inbuffer[i]=toupper(inbuffer[i]);
                }
           }
            logMessage(DEBUG,"trans after : %s[%d]>>> %s",clientIp.c_str(),clientPort,inbuffer);
            write(sock,inbuffer,strlen(inbuffer));
        }
        else if(s==0)
        {
                logMessage(DEBUG,"client quit -- %s[%d]",clientIp.c_str(),clientPort);
                break;
        }
        else
        {
                logMessage(DEBUG,"%s[%d] - read:%s",clientIp.c_str(),clientPort,strerror(errno));
                break;
        }

    }
    close(sock);
    logMessage(DEBUG,"server close %d done",sock);

}

单线程版本:

直接传套接字、ip、端口号


```cpp
 transService(serviceSock,peerIp,peerPort);

多线程版本:

  //5.1 多线程版本

		pid_t id=fork();
        if(id==0)
        {   //基类进程
            close(listenSock_);
            //又进行了一次fork,让
            if(fork()>0) exit(0);
            //子类进程 基类退出--子类还在被系统拿到--回收问题就交给系统回收
            transService(serviceSock,peerIp,peerPort);
            exit(0);//子进程退出后会进入僵尸状态
        }
        //基类退出
        close(serviceSock);
        //获取基类退出后的退出码
        pid_t ret=waitpid(id,nullptr,0);
        assert(ret>0);
        (void)ret;

//多线程会共享文件描述符表
ThreadData td=new ThreadData(peerPort,peerIp,serviceSock,this);
pthread_t tid;
pthread_create(&tid,nullptr,threadRoutine,(void
)td);

pthread_detach的使用让每个线程结束后自动释放自己所用的资源。
int serviceSock=accept(listenSock_,(struct sockaddr*)&peer,&len);每个用户连接后需要执行5步。

  • ThreadData *td=new ThreadData(peerPort,peerIp,serviceSock,this);这步是为了将主线程把链接信息封装好,然后创建子线程,让子线程去处理这份数据,然后等待下一个链接。
  • pthread_create就是完成第一行的创建子进程是执行子进程对应的任务。

线程池版本:

pthread_mutex_init的使用是为了解决在多线程访问共享资源时,多个线程同时对共享资源操作产生的冲突而提出的一种解决方法,在执行时,哪个线程持有互斥锁,并对共享资源进行加锁后,才能对共享资源进行操作,此时其它线程不能对共享资源进行操作。
我们先启动线程池
在这里插入图片描述

    tp_ = ThreadPool<Task>::getInstance();为每个线程池进行加锁

然后启动线程池
tp_->start();
来了任务就将任务放至任务队列里面,如果没有任务就等待。如果有任务就拿任务并且把任务队列的首部弹出该任务。返回任务。

Task t(serviceSock, peerIp, peerPort, execCommand);
 tp_->push(t);

调用程序执和被调用函数同时在执行。当被调函数执行完毕后,被调函数会反过来调用某个事先指定函数,以通知调用程序:函数调用结束。这个过程称为回调(Callback)。

void execCommand(int sock, const std::string& clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char command[BUFFER_SIZE];
    while (true)
    {
        size_t s = read(sock, command, sizeof(command) - 1);
        if (s > 0)
        {
            command[s] = '\0';
            // 这个用户执行command命令
            logMessage(DEBUG, "[%s:%d] exec [%s] ..done", clientIp.c_str(), clientPort, command);

            // 考虑安全
            std::string safe = command;
            if ((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink")))
            {
                break;
            }

            FILE *fp = popen(command, "r");
            if (fp == nullptr)
            {
                logMessage(WARINING, "exec %s failed,beacuse: %s", command, strerror(errno));
                break;
            }
            // 遍历目录
            char line[1024];
            while (fgets(line, sizeof(line) - 1, fp) != nullptr)
            {
                write(sock, line, strlen(line));
            }
            // //本来要写入dile里但现在写入网络。
            // dup2(sock,fp->_fileno);
            // fflush(fp);
            pclose(fp);
            logMessage(DEBUG, "[%s:%d] exec [%s]...done", clientIp.c_str(), clientPort, command);
        }
        else if (s == 0)
        {
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read:%s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }
    close(sock);
    logMessage(DEBUG, "server close %d done", sock);
}

对于线程池我的理解还是不行。希望能多多与大家一块交流。

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

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

相关文章

Mac使用命令行工具解压和压缩rar文件

目前在Mac电脑里支持解压缩的格式主要有&#xff1a;zip、gz等&#xff0c;但是还不支持rar格式的文件&#xff0c;接下来带着大家学习一下如何解压缩rar格式文件。 1.下载rar工具 打开&#xff1a;https://www.rarlab.com/download.htm 根据自己电脑的芯片要求选择自己的安装…

【计算机基本原理-数据结构】数据结构中树的详解

【计算机基本原理-数据结构】数据结构中树的详解 1&#xff09;总览2&#xff09;树的相关概念3&#xff09;二叉树、满二叉树、完全二叉树4&#xff09;二叉查找树 - BST5&#xff09;平衡二叉树 - AVL6&#xff09;红黑树7&#xff09;哈弗曼树8&#xff09;B 树9&#xff09…

TCP流量控制与拥塞控制

什么是流量控制 一条TCP连接的每一侧主机都为该连接设置了接收缓存。当该TCP连接接收到正确的、有序的报文段&#xff0c;就会将数据放入接收缓存。相关联的应用会从缓存中读取数据。 如果发送者发送数据过快、过多&#xff0c;而接收方的应用程序从缓冲区读取的速度较慢&…

机器学习实战教程(十):逻辑回归

概述 逻辑回归&#xff08;Logistic Regression&#xff09;是一种用于解决二分类或多分类问题的统计学习方法。它以自变量线性组合的形式进行建模&#xff0c;并使用Sigmoid函数将结果映射到[0, 1]的值域内&#xff0c;表示样本属于某个类别的概率。 Logistic Regression是最…

Stable Diffusion-生式AI的新范式

! 扩散模型&#xff08;Stable Diffusion)现在是生成图像的首选模型。由于扩散模型允许我们以提示( prompts)为条件生成图像&#xff0c;我们可以生成我们所选择的图像。在这些文本条件的扩散模型中&#xff0c;稳定扩散模型由于其开源性而最为著名。 在这篇文章中&#xff0…

STM32平衡小车 TB6612电机驱动学习

TB6612FNG简介 单片机引脚的电流一般只有几十个毫安&#xff0c;无法驱动电机&#xff0c;因此一般是通过单片机控制电机驱动芯片进而控制电机。TB6612是比较常用的电机驱动芯片之一。 TB6612FNG可以同时控制两个电机&#xff0c;工作电流1.2A&#xff0c;最大电流3.2A。 VM电…

力劲塑机:用CRM“塑造”数字化能力

你知道吗&#xff1f;从手机到电脑&#xff0c;从暖气到扶梯&#xff0c;从家用电器到汽车、摩托车&#xff0c;从眼镜、手表到拉链、纽扣&#xff0c;这些物品的生产过程都离不开压铸和注塑工艺。如果说压铸和注塑这个几百亿的产业带动了几万亿的市场&#xff0c;一点也不夸张…

fc坦克大战游戏完美复刻

文章目录 一、 介绍二、 制作基本物体三、 控制玩家坦克移动、转向四、 子弹脚本、爆炸脚本五、 敌人AI寻路算法六、 坦克生成点脚本七、 用链表实例化地图八、 玩家游戏控制器脚本九、 添加音效十、 资源包 一、 介绍 儿时经典游戏《坦克大战》完整复刻 发射子弹、生成敌人、…

巧用千寻位置GNSS软件|一文教会横断面测量

测横断面主要用于线路工程和水利工程的前期设计中&#xff0c;在线路平曲线设计好之后&#xff0c;千寻位置GNSS软件可用于在中桩处测定垂直于线路中线方向原地貌的地面起伏的数据&#xff0c;本期就为大家介绍具体的操作技巧。 点击【测量】->【测横断面】&#xff0c;选择…

java——最小的K个数

题目链接 牛客在线oj题——最小的K个数 题目描述 给定一个长度为 n 的可能有重复值的数组&#xff0c;找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字&#xff0c;则最小的4个数字是1,2,3,4(任意顺序皆可)。 数据范围&#xff1a;0≤k,n≤10000&…

Flink之TaskManager内存解析

一、CK失败 Flink任务的checkpoint操作失败大致分为两种情况&#xff0c;ck decline和ck expire: &#xff08;1&#xff09;ck decline 发生ck decline情况时&#xff0c;我们可以通过查看JobManager.log或TaskManager.log查明具体原因。其中有一种特殊情况为ck cancel&…

idea使用 ( 二 ) 创建java项目

3.创建java项目 3.1.创建普通java项目 3.1.1.打开创建向导 接 2.3.1.创建新的项目 也可以 从菜单选择建立项目 会打开下面的选择界面 3.1.2.不使用模板 3.1.3.设置项目名 Project name : 项目名 Project location : 项目存放的位置 确认创建 3.1.4.关闭tips 将 Dont s…

增强型PID-自适应-前馈-神经网络控制研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

解决docker启动mysql无法输入中文以及中文不显示或乱码问题

前言 我在使用MySQL时&#xff0c;遇到了两个问题。一是在插入中文数据时&#xff0c;无法输入中文。二是在select的时候&#xff0c;查出来的中文数据是空的&#xff08;因为插入时为空&#xff09;&#xff0c;然后我就使用Navicat连接数据库添加了中文数据&#xff0c;再到…

搭建家庭影音媒体中心 --公网远程连接Jellyfin流媒体服务器

文章目录 前言1. 安装Home Assistant2. 配置Home Assistant3. 安装cpolar内网穿透3.1 windows系统3.2 Linux系统3.3 macOS系统 4. 映射Home Assistant端口5. 公网访问Home Assistant6. 固定公网地址6.1 保留一个固定二级子域名6.2 配置固定二级子域名 转载自远程穿透的文章&…

人工智能时代来临,殊不知低代码早已出手

科普一下人工智能的等级划分&#xff0c;按照实力&#xff0c;人工智能可以分为弱人工智能(Artificial Narrow Intelligence&#xff0c;简称ANI)、强人工智能(Artificial General Intelligence简称AGI)、超人工智能(Artificial Superintelligence简称ASI)三个等级。 弱人工智能…

JavaWeb学习------Servlet

目录 JavaWeb学习------Servlet Servlet 生命周期 Servlet 生命周期 Servlet 方法介绍 •Servlet 体系结构 Servlet 体系结构 •Servlet urlPattern配置 Servlet urlPattern配置 •XML 配置方式编写 Servlet XML 配置方式编写 Servlet JavaWeb学习------Servlet •快速…

【汽车品牌案例02-设置右侧索引 Objective-C语言】

一、刚才我们说了一下,如何把那个汽车品牌加载起来,我们使用了一个模型的嵌套,以及我们在创建单元格的时候,是不是指定了一个,单元格的可重用ID吧, 1.根据重用ID来创建单元格,那么我们运行的时候,已经能把这个大致的效果做出来了, 大致就是这么一个效果, 接下来,还…

【剧前爆米花--爪哇岛寻宝】网络互连,网络通信和网络分层

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaEE初阶》 文章分布&#xff1a;这是一篇关于网络初识的文章&#xff0c;在这篇文章中讲解了局域网广域网&#xff0c;IP地址&#xff0c;端口以及网络分层等相关内容&#xff0c;希望对你有所帮助&#xff01; 目录 网络互连…

PyTorch中的交叉熵函数 CrossEntropyLoss的计算过程

CrossEntropyLoss() 函数联合调用了 nn.LogSoftmax() 和 nn.NLLLoss()。 关于交叉熵函数的公式详见&#xff1a; 交叉熵损失函数原理详解 CrossEntropyLoss() 函数的计算过程可以拆解为如下四个步骤&#xff1a; 1、对输出的结果进行softmax操作,因为softmax操作可以将所有输入…
最新文章