当前位置: 首页 > news >正文

Linux网络--2.2、TCP接口

目录

一、目的

二、封装服务器端

        2.1创建服务器类

        2.2创建服务器socket

        2.3绑定ip与port

        2.4启动监听

        2.5监听成功接收客户端

        2.6停止服务器

三、客户端

四、不同版本处理任务--服务端

        4.0任务        

        4.1单进程

        4.2多进程

        4.3多线程

        4.4线程池


一、目的

        1、了解服务端TCP接口的基本步骤并封装为类

        2、了解客户端TCO接口的基本步骤

        3、对比不同版本的服务端(多进程,多线程.....)

TCP:

二、封装服务器端

        2.1创建服务器类

与UDP类似,我们的服务器启动后就不再关闭,我们提供两个接口:

  1. start:启动服务器
  2. stop:停止服务器
class TcpServer
{
public:TcpServer(){}// 启动服务器void start(){}// 停止服务器void stop(){}~TcpServer(){}
};

        2.2创建服务器socket

利用socket创建套接字,与UDP相似,只是在socket接口的第二个参数使用SOCK_STREAM而不再是SOCK_DGRAM,表示面向字节流

    void InitServer(){   // 1. 创建tcp socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0); // Tcp Socketif (_listensockfd < 0){   LOG(LogLevel::FATAL) << "socket error";Die(SOCKET_ERR);}   LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;}

        2.3绑定ip与port

绑定方式与UDP基本一致,先使用原生的方式而不是直接使用封装后的sockaddr_in结构。在UDP编程接口基本使用部分已经提到过服务器不需要指定IP地址

 #include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);#define CONV(v) (struct sockaddr*) (v)      //类型转换 //填充服务端的网络信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(gport);local.sin_addr.s_addr = INADDR_ANY;//接收所有ip// 2. bindint n = ::bind(_listensockfd, CONV(&local), sizeof(local));if (n < 0){   LOG(LogLevel::FATAL) << "bind error";Die(BIND_ERR);}   LOG(LogLevel::INFO) << "bind success, sockfd is : " << _listensockfd;

        2.4启动监听

由于tcp是面向连接的所以只有监听连接的到来,并在服务器启动后交给accep才能处理

int listen(int sockfd, int backlog);
该接口的第一个参数表示当前需要作为传输的套接字,第二个参数表示等待中的客户端的最大个数。之所以会有第二个参数是因为一旦请求连接的客户端太多但是服务器又无法快速得做出响应就会导致用户一直处于等待连接状态从而造成不必要的损失。一般情况下第二个参数不建议设置比较大,而是因为应该根据实际情况决定,但是一定不能为0,本次大小定为8当监听成功,该接口会返回0,否则返回-1并设置对应的错误码// 3. cs,tcp是面向连接的,就要求tcp随时随地等待被连接                           // tcp 需要将socket设置成为监听状态                                             n = ::listen(_listensockfd, BACKLOG);                                           if (n < 0)                                                                      {LOG(LogLevel::FATAL) << "listen error";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success, sockfd is : " << _listensockfd;

      2.5监听成功接收客户端

在TCP中,启动服务器的逻辑和UDP的逻辑有一点不同,因为TCP服务器在启动之前先要进行监听,所以实际上此时服务器并没有进入IO状态,所以一旦启动服务器后,首先要做的就是一旦成功建立连接就需要进入收发消息的状态

首先判断服务器是否启动,如果服务器本身已经启动就不需要再次启动,所以还是使用一个_isRunning变量作为判断条件,基本逻辑如下:

// 启动服务器
void start()
{
    if (!_isRunning)
    {
        _isRunning = true;
        while (true)
        {
        }
    }
}

接着就是在监听成功的情况下进入IO状态,这里使用的接口就是accept,其原型如下

        int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen);

        该接口的第一个参数表示需要绑定的服务器套接字,第二个参数表示对方的套接字结构,第二个参数表示对方套接字结构的大小,其中第二个参数和第三个参数均为输出型参数

        需要注意的是该接口的返回值,当函数执行成功时,该接口会返回一个套接字,这个套接字与前面通过socket接口获取到的套接字不同。        

        在UDP中,只有一个套接字,就是socket的返回值,但是在TCP中,因为首先需要先监听,此时需要用到的实际上是监听套接字一旦监听成功,才会给定用于IO的套接字。所以实际上,在TCP中,socket接口的返回值对应的是listen用的套接字,而accept的套接字就是用于IO的套接字

    void Start(){_isrunning = true;while (_isrunning){// 不能直接读取数据// 1. 获取新连接struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意设置peerlen的大小,要不然,会有问题!LOG(LogLevel::DEBUG) << "accept ing ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error: " << strerror(errno);continue;}// 获取连接成功了LOG(LogLevel::INFO) << "accept success, sockfd is : " << sockfd;}

        2.6停止服务器

// 停止服务器
void stop()
{if (_isRunning){close(_listen_socketfd);close(_ac_socketfd);}
}

三、客户端

        与UDP一致,我们不再封装,直接显示的创建套接字,填充服务器信息后与服务器建立连接,建立连接时会自动绑定ip与port,原因与UDP一致

        因为tcp面向字节流,与文件类似,所以发送和接收文件可以用read和write,但不保险,可以使用recv和send

// 形式:./client_tcp server_ip server_port11 int main(int argc, char *argv[])12 {13     //启动程序形式warning14     if (argc != 3)15     {16         std::cout << "Usage:./client_tcp server_ip server_port" << std::endl;17         return 1;18     }19 20     //服务端IP21     std::string server_ip = argv[1]; // "192.168.1.1"22 23     //服务端端口号24     int server_port = std::stoi(argv[2]);25 26     //创建套接字,AF_INET--网络,SOCK_STREAM--字节流27     int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);28     if (sockfd < 0)29     {30         std::cout << "create socket failed" << std::endl;31         return 2;32     }                                                                                                                                                                        33 34     //服务端的套接字信息初始化,网络转主机35     struct sockaddr_in server_addr;36     memset(&server_addr, 0, sizeof(server_addr));37     server_addr.sin_family = AF_INET;38     server_addr.sin_port = htons(server_port);39     server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
// client 不需要显示的进行bind, tcp是面向连接的, connect 底层会自动进行bind42     int n = ::connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));43     if(n < 0)44     {45         std::cout << "connect failed" << std::endl;46         return 3;47     }48 49     // echo client50     std::string message;51     while(true)52     {53         char inbuffer[1024];54         std::cout << "input message: ";55         std::getline(std::cin, message);56 57         n = ::write(sockfd, message.c_str(), message.size());58         if(n > 0)59         {60             int m = ::read(sockfd, inbuffer, sizeof(inbuffer));61             if(m > 0)62             {63                 inbuffer[m] = 0;64                 std::cout << inbuffer << std::endl;65             }66             else67                 break;68         }69         else70             break;71     }::close(sockfd);74     return 0;75 }

四、不同版本处理任务--服务端

        4.0任务        

任务:利用传入的sockfd(文件fd)接收客户端的消息,成功后并回显消息给客户端

    void HandlerRequest(int sockfd) // TCP也是全双工通信的(能收能发){   LOG(LogLevel::INFO) << "HandlerRequest, sockfd is : " << sockfd;char inbuffer[4096];// 长任务while (true){   // 约定:用户发过来的是一个完整的命令string// ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1); // read读取是不完善,可能少字节,用recvssize_t n = ::recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);  if (n > 0){   LOG(LogLevel::INFO) << inbuffer;//接收并显示客户端消息//构建回显消息std::string echo_str = "server echo# ";echo_str += inbuffer;::send(sockfd,echo_str.c_str(), echo_str.size(), 0); }   else if (n == 0){   // read 如果读取返回值是0,表示client退出LOG(LogLevel::INFO) << "client quit: " << sockfd;break;}   else{// 读取失败了break;}}::close(sockfd); // fd泄漏问题!}

        4.1单进程

只能接收一条连接,用于测试,不符合生活

直接调用
// version-0
// HandlerRequest(sockfd);

        4.2多进程

利用子进程创建孙子进程执行任务后子进程直接退,孙子进程被系统领养,解放父进程继续接收连接,实现多连接

类似管道,我们可以关闭父子进程不需要的读写端,防止错误操作

            // version-1: (多进程版本)// pid_t id = fork();// if (id == 0)// {//     // child//     // 问题1: 子进程继承父进程的文件描述符表。两张,父子各一张//     // 1. 关闭不需要的fd//     ::close(_listensockfd);//     if(fork() > 0) exit(0); //子进程退出//     // 孙子进程 -> 孤儿进程 -> 1//     HandlerRequest(sockfd);//     exit(0);// }// ::close(sockfd);// // 不会阻塞// int rid = ::waitpid(id, nullptr, 0);// if(rid < 0)// {//     LOG(LogLevel::WARNING) << "waitpid error";// }

        4.3多线程

由于多线程共享同一张表我们不能随便关闭读写端

为了解决创建线程传参的参数不符合void*(void*)的问题,我们可以传结构体,进行任务调用

//结构体用于多线程版本创建线程后时参数不匹配问题,多了一个this指针参数struct ThreadData{int sockfd;TcpServer *self;//this指针调用任务处理的类内函数};//用于多线程传递描述符时的覆盖问题,【参数、返回值需要void*】,不加static会多一个this指针参数,利用结构体解决全局后访问类内函数异常的问题static void *ThreadEntry(void *args){pthread_detach(pthread_self());ThreadData *data = (ThreadData *)args;data->self->HandlerRequest(data->sockfd);return nullptr;}// version-2: 多线程版本// 主线程和新线程是如何看待:文件描述符表,共享一张文件描述符表!!// pthread_t tid;// ThreadData *data = new ThreadData;// data->sockfd = sockfd;// data->self = this;// pthread_create(&tid, nullptr, ThreadEntry, data);

        4.4线程池

利用bind或者lambda表达式进行任务执行,线程池为我们封装的单例线程池模式,可以进行任务插入

// version-3:线程池版本 比较适合处理短任务,或者是用户量少的情况// task_t f = std::bind(&TcpServer::HandlerRequest, this, sockfd); // 构建任务
ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){ this->HandlerRequest(sockfd); });

http://www.dtcms.com/a/327392.html

相关文章:

  • PMBT2907A,215 Nxp安世半导体 双极性晶体管 开关电源管理芯片
  • 蚁剑--安装、使用
  • C# 基于halcon的视觉工作流-章29-边缘提取-亚像素
  • 力扣.870优势洗牌解决方法: 下标排序​编辑力扣.942增减字符串匹配最长回文子序列牛客.背包问题(最大体积)力扣.45跳跃游戏II 另一种思考
  • 数据结构——线性表(核心操作,附代码)
  • vue项目封装axios请求,支持判断当前环境及判断token是否过期等等(详细教程,可复制粘贴代码)
  • cuda排序算法--双调排序(Bitonic_Sort)
  • 【数据库】 MySQL 表的操作详解
  • 蓝桥杯手算题和杂题简易做法
  • 《Auracast广播音频技术解析及未来路线图》 —蓝牙技术联盟 市场拓展经理 吴志豪 技术与市场经理 鲁公羽
  • 基于 DiT 大模型与字体级分割的视频字幕无痕擦除方案,助力短剧出海
  • 深度学习与遥感入门(六)|轻量化 MobileNetV2 高光谱分类
  • 4.7 GB 视频导致浏览器内存溢出(OOM)的解决方案
  • 从零部署Nacos:替代Eureka的服务注册与服务发现基础教程
  • 视频输入输出模块介绍和示例
  • Dubbo 3.x源码(33)—Dubbo Consumer接收服务调用响应
  • Python day42
  • tensorrt-llm0.20.0离线部署DeepSeek-R1-Distill-Qwen-32B
  • 第六十三章:AI模型的“跨界之旅”:不同硬件架构下的兼容性方案
  • Linux NAPI 实现机制深度解析
  • 【CDA 新一级】学习笔记第1篇:数据分析的时代背景
  • 【前端八股文面试题】【JavaScript篇7】什么是JavaScript的原型、原型链? 有什么特点
  • 【设计模式精解】Java实现责任链模式(职责链模式)优雅处理多级请求(概述,使用场景,优缺点,代码示例)
  • Rust:构造函数 new() 如何进行错误处理?
  • 信号(Signal)** 是一种进程间异步通信机制,用于通知进程发生发生了某种事件(如错误、用户中断等)
  • 疯狂星期四文案网第37天运营日记
  • Apache POI中通过WorkBook写入图片后出现导出PDF文件时在不同页重复写入该图片问题,如何在通过sheet获取绘图对象清除该图片
  • 通过限制对象的内存分配位置来实现特定的设计目标
  • 【数据结构入门】堆
  • powerbi本地报表发布到web,以得到分享链接