【Linux】socket编程3

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【Linux】socket套接字

在这里插入图片描述


前言
下面的编程代码中,一些socket接口需要参考【Linux】socket套接字


目录

  • 👉🏻新的网络接口函数(待会编程会用到)
    • inet_pton
    • inet_ntop
    • read和write函数
  • 👉🏻实现TCP通信
    • TcpServer.hpp
    • TcpClient.cc
  • 👉🏻TCP实现并发通信(服务端一对多服务)
    • 1.多进程
    • 2.多进程:父进程不等待子进程
    • 3.多线程
    • 4.线程池(引入多模式服务)
      • 补充翻译服务

👉🏻新的网络接口函数(待会编程会用到)

inet_pton

inet_pton 是一个用于将字符串形式的 IP 地址转换为网络地址结构的函数,其中 “pton” 指的是 “presentation to network”。这个函数通常用于将 IPv4 或 IPv6 地址的字符串表示形式转换为适当的网络地址结构,以便网络编程中的套接字操作。

函数原型如下:

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

其中:

  • af 参数指定了地址族(address family),可以是 AF_INET 表示 IPv4 地址族,或者 AF_INET6 表示 IPv6 地址族。
  • src 参数是一个指向以 NULL 结尾的字符串的指针,表示待转换的 IP 地址字符串。
  • dst 参数是一个指向用于存储结果的缓冲区的指针,该缓冲区应该足够大以容纳对应的网络地址结构。

函数的返回值是一个整数,如果转换成功,返回值为 1(表示成功转换了一个有效的 IP 地址字符串),如果转换失败,返回值为 0(表示输入的字符串不是有效的 IP 地址),并且 errno 被设置为相应的错误码。

例如,下面是一个使用 inet_pton 的简单示例:

#include <stdio.h>
#include <arpa/inet.h>

int main() {
    const char *ip_str = "192.0.2.1";
    struct in_addr addr;

    if (inet_pton(AF_INET, ip_str, &addr) > 0) {
        printf("IPv4 address: %s\n", inet_ntoa(addr));
    } else {
        perror("inet_pton");
        return 1;
    }

    return 0;
}

这个示例将字符串形式的 IPv4 地址 “192.0.2.1” 转换为网络地址结构,并打印出转换后的地址。

inet_ntop

inet_ntop 是一个用于将网络地址结构表示的 IP 地址转换为字符串形式的函数,其中 “ntop” 指的是 “network to presentation”。这个函数通常用于将 IPv4 或 IPv6 地址的网络地址结构转换为人类可读的字符串表示形式,以便网络编程中的套接字操作。

函数原型如下:

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

其中:

  • af 参数指定了地址族(address family),可以是 AF_INET 表示 IPv4 地址族,或者 AF_INET6 表示 IPv6 地址族。
  • src 参数是一个指向包含网络地址结构的内存区域的指针。
  • dst 参数是一个指向用于存储结果的缓冲区的指针,这个缓冲区应该足够大以容纳转换后的字符串表示形式。
  • size 参数指定了 dst 缓冲区的大小,以防止发生缓冲区溢出。

函数的返回值是一个指向转换后的字符串的指针,如果转换成功,返回值指向 dst 缓冲区中的字符串,如果转换失败,返回值为 NULL,并且 errno 被设置为相应的错误码。

例如,下面是一个使用 inet_ntop 的简单示例:

#include <stdio.h>
#include <arpa/inet.h>

int main() {
    struct in_addr addr;
    const char *ip_str = "192.0.2.1";
    char buffer[INET_ADDRSTRLEN];

    if (inet_pton(AF_INET, ip_str, &addr) > 0) {
        const char *ip_str_converted = inet_ntop(AF_INET, &addr, buffer, INET_ADDRSTRLEN);
        if (ip_str_converted != NULL) {
            printf("IP address: %s\n", ip_str_converted);
        } else {
            perror("inet_ntop");
            return 1;
        }
    } else {
        perror("inet_pton");
        return 1;
    }

    return 0;
}

这个示例将字符串形式的 IPv4 地址 “192.0.2.1” 转换为网络地址结构,然后再将该结构转换为字符串形式,并打印出转换后的地址。

read和write函数

当你在网络编程中处理数据时,readwrite 函数是你的好帮手。它们用于从文件描述符(包括套接字)读取数据和向文件描述符写入数据。让我来详细解释一下它们的作用。

  • read 函数read 函数用于从文件描述符中读取数据。在网络编程中,通常用于从套接字读取数据。其函数原型如下:
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
- `fd` 是文件描述符,指向要读取的数据流。
- `buf` 是一个指针,指向用于存储读取数据的缓冲区。
- `count` 是要读取的字节数。

read 函数返回实际读取的字节数,如果读取成功,则返回非负整数;如果到达文件末尾(或连接关闭),则返回0;如果出现错误,则返回-1,并设置 errno 变量来指示错误的类型。

  • write 函数write 函数用于向文件描述符中写入数据。在网络编程中,通常用于向套接字写入数据。其函数原型如下:
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
- `fd` 是文件描述符,指向要写入数据的目标。
- `buf` 是一个指针,指向要写入的数据。
- `count` 是要写入的字节数。

write 函数返回实际写入的字节数,如果写入成功,则返回非负整数;如果出现错误,则返回-1,并设置 errno 变量来指示错误的类型。

这两个函数是网络编程中最常用的函数之一。当你需要从套接字读取数据或向套接字写入数据时,就会使用它们。想象一下,当你在网络上发送消息时,你使用 write 函数将消息写入套接字,而接收消息时,你使用 read 函数从套接字中读取数据。这就是它们的基本用法。


read,write和recvfrom,sendto有什么不同

read 和 write 函数主要用于简单的文件 I/O 操作和部分套接字数据传输,而 recvfrom 和 sendto 函数主要用于 UDP 协议中的套接字数据传输,特别是在需要指定数据来源和目标地址的情况下。

👉🏻实现TCP通信

TcpServer.hpp

#pragma once

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<cerrno>
#include"nocopy.hpp"
#include"Log.hpp"
#include"CommErr.hpp"

const int default_backlog = 5;
class TcpServer:public nocopy
{
public:
    TcpServer(uint16_t port)
    :_port(port),_isrunning(false)
    {}
    void Init()
    {
        //创建套接字sockfd
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock == -1)
        {
            lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
            exit(Socket_Err);
        }
        lg.LogMessage(Debug,"create success,sockfd:%d\n",_listensock);

        //初始化本地网络信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        //将本地网络信息Bind到内核当中
        if(bind(_listensock,CONV(&local),sizeof(local))==-1)
        {
            lg.LogMessage(Fatal,"Bind fail,the error code :%d,error string: %s\n",errno,strerror(errno));
            exit(Bind_Err);
        }
        lg.LogMessage(Debug,"Bind success,sockfdL%d\n",_listensock);

        //往事俱备,接下来服务端进行监听就可以了
        if(listen(_listensock,default_backlog)==-1)
        {
            lg.LogMessage(Fatal,"Listen fail,the error code :%d,error string: %s\n",errno,strerror(errno));
            exit(Listen_Err);
        }
        lg.LogMessage(Debug,"Listen success,sockfdL%d\n",_listensock);

    }
    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            //接收客户端信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensock,CONV(&peer),&len);
            if(sockfd==-1)
            {
                lg.LogMessage(Fatal,"accept fail,the error code :%d,error string: %s\n",errno,strerror(errno));
                continue;//失败了也不退出,不放弃继续等待下一位有缘人
            }
            lg.LogMessage(Debug,"accept success,sockfdL%d\n",sockfd);

            //与客户端建立连接后,可以为其提供服务了
            Serveice(sockfd);

            close(sockfd);//关闭文件描述符
        }
    }
    void Serveice(int sockfd)
    {
        //先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
        char buffer[1024];
        
       while(true)
       {
        ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
         if(n>0)
        {
            buffer[n] = 0;
            cout<<"client say:"<<buffer<<endl;
            string echo_string = "server say:";
            echo_string+=buffer;
            write(sockfd,echo_string.c_str(),echo_string.size());

        }
        else if(n==0)
        {
            lg.LogMessage(Info,"client quit...\n");
            break;

        }
        else
        {
            lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
            break;
        }
       }
    }
    ~TcpServer()
    {

    }
private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;
};

TcpClient.cc

#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include<sys/types.h>
//包含网络头文件
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//包含外部头文件
#include"Log.hpp"
#include"CommErr.hpp"

void Usage(const string & process)
{
    cout<<"Usage:"<<process<<" server_ip server_port"<<endl;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //将命令行的ip和port记录
    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);

    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
       lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
        exit(Socket_Err);
    }
    lg.LogMessage(Debug,"create success,sockfd:%d\n",sockfd);


    //初始化本地网络信息
    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(server_port);
    // p:process(进程), n(网络) -- 不太准确,但是好记忆
    inet_pton(AF_INET,server_ip.c_str(),&local.sin_addr);

    //发起连接的时候,client会被OS自动进行本地绑定。所以我们不用显示bind!
    
    //接下来开始主动去连接服务端
    int n = connect(sockfd,CONV(&local),sizeof(local));
    if(n==-1)
    {
        lg.LogMessage(Error,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
        exit(Connect_Err);
    }

    //连接成功后,去发消息给服务端,并接收服务端发来的消息
    while(true)
    {
        string inbuffer;
        cout<<"Please Enter:";
        getline(cin,inbuffer);

        ssize_t ret = write(sockfd,inbuffer.c_str(),inbuffer.size());
        if(ret>0)
        {
            //发送成功,此时我们可以顺便读取服务端发送来的消息
            char buffer[1024];
            int n = read(sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                //读到了
                buffer[n] = 0;
                cout<<buffer<<endl;
            }
            else
            break;
        }
        else
        break;

        
    }
    close(sockfd);//关闭文件描述符
    return 0;
}

实现效果:
在这里插入图片描述

我们目前实现的通信代码因为是单进程的,所以客户端和服务端只能进行串行执行,也就是说服务端一次只能应付对待一个客户端,如果再来一个客户端,服务端并不会马上响应,得等另一个客户端退出了,服务端才会过来执行这个新来的客户端。

接下来,我们就会去写多个版本的代码去实现客户端和服务端的并发执行。

👉🏻TCP实现并发通信(服务端一对多服务)

代码主要在服务端代码中进行优化

1.多进程

 //v2:支持多进程,实现并发
            pid_t id = fork();
            if(id<0)
            {
                close(sockfd);
                continue;
            }
            else if(id==0)
            {
                //child
                close(_listensock);//关闭子进程不会再使用的文件描述符,使得文件描述符表的空间得到有效释放
                if(fork()>0)
                {
                    //此时再创建进程,如果还是子进程本程则退出
                    exit(0);//子进程退出后进入下一个循环进行accept,让孙子进程对accept的新的sockfd进行处理,就能实现并发
                }
                //孙子进程,孤儿进程(此时子进程已经退了),孙子进程退出不用担心僵尸状态,因为被系统领养(资源系统自动回收),正常处理
                Serveice(sockfd);//孙子进程来处理
                close(sockfd);
                exit(0);
            }
            else
            {
                close(sockfd);//关闭父进程不会再使用的文件描述符
                pid_t rid = waitpid(id,nullptr,0);//父进程等待子进程的终止进行资源回收,避免子进程僵尸状态
                if(rid == id)
                {
                    //do nothing
                }
            }

2.多进程:父进程不等待子进程

#include<signal.h>
signal(SIGCHLD,SIG_IGN);//在Linux环境中,如果对SIG_IGN进行忽略,子进程退出的时候,会自动释放资源
		 pid_t id = fork();
            if(id<0)
            {
                close(sockfd);
                continue;
            }
            else if(id==0)
            {
                //child
                close(_listensock);//关闭子进程不会再使用的文件描述符,使得文件描述符表的空间得到有效释放
               
                Serveice(sockfd);//子进程来处理
                close(sockfd);
                exit(0);
            }
            else
            {
                close(sockfd);//关闭父进程不会再使用的文件描述符
                //do nothing
            }


3.多线程

class TcpServer;//先声明TcpServer
class ThreadData
{
public:
    ThreadData(int sock, TcpServer *ptr, struct sockaddr_in &peer)
    :sockfd(sock),  svr_ptr(ptr), addr(peer)
    {}
    int SockFd() {return sockfd;}
    TcpServer *GetServer() { return svr_ptr;};
    ~ThreadData()
    {
        close(sockfd);
    }
private:
    int sockfd;
    TcpServer* svr_ptr;
public:
    InetAddr addr;
};
//v5:多线程,相比于进程池,多线程可以直接共享当前进程下的文件描述符
            ThreadData *td = new ThreadData(sockfd, this, peer);
            pthread_t tid;
            pthread_create(&tid, nullptr, HandlerRequest, td);
            //主线程和新线程因为共享同一张文件描述符表,所以没有多余不用的文件描述符
            //主线程和新线程如何join回收?
            //如果直接pthread_join,这会直接包括主线程一起被回收,而我主线程还要一直去accept新的sockfd呢
            //所以为了只让新线程进行join,主线程不join,可以进行线程detach分离
            //线程在结束执行时能够自动释放其所占用的资源,而无需其他线程调用pthread_join函数等待其结束。
 //之所以要static是因为pthread_create的函数方法参数只能有一个void*,而成员函数默认还有一个this参数
   static void *HandlerRequest(void *args) //static的话就没有默认的this参数,所以要在ThreadData中自己传入
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->GetServer()->Service(td->SockFd(), td->addr);
        delete td;
        return nullptr;
    }
 void Serveice(int sockfd,InetAddrtoLocal addr)
    {
        //先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
        char buffer[1024];
        
       while(true)
       {
        ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
         if(n>0)
        {
            buffer[n] = 0;
            cout<<addr.Info()<<"say:"<<buffer<<endl;
            string echo_string = "server say:";
            echo_string+=buffer;
            write(sockfd,echo_string.c_str(),echo_string.size());

        }
        else if(n==0)
        {
            lg.LogMessage(Info,"client quit...\n");
            break;

        }
        else
        {
            lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
            break;
        }
       }
    }

4.线程池(引入多模式服务)

🍎TcpServer.hpp

#pragma once

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<cerrno>
#include<sys/wait.h>
#include<sys/types.h>
#include<signal.h>
#include<pthread.h>
#include <functional>
#include <unordered_map>

//包含外部头文件
#include"nocopy.hpp"
#include"Log.hpp"
#include"CommErr.hpp"
#include"InetAddrtoLocal.hpp"
#include"ThreadPool.hpp"
#include"Task.hpp"

using task_t = std::function<void()>;
using callback_t = std::function<void(int, InetAddrtoLocal &)>; // 可能要调整

const int default_backlog = 5;


class TcpServer;
class ThreadData
{
public:
    ThreadData(int sock, TcpServer *ptr, struct sockaddr_in &peer)
    :sockfd(sock),  svr_ptr(ptr), addr(peer)
    {}
    int SockFd() {return sockfd;}
    TcpServer * GetServer() { return svr_ptr;};
    ~ThreadData()
    {
        close(sockfd);
    }
private:
    int sockfd;
    TcpServer* svr_ptr;
public:
    InetAddrtoLocal addr;
};


class TcpServer:public nocopy
{
public:
    TcpServer(uint16_t port)
    :_port(port),_isrunning(false)
    {}
    void Init()
    {
        //创建套接字sockfd
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock == -1)
        {
            lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
            exit(Socket_Err);
        }
        lg.LogMessage(Debug,"create success,sockfd:%d\n",_listensock);


        //解决一些少量的bind失败的问题  TODO
        int opt = 1;
        setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));

        //初始化本地网络信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        //将本地网络信息Bind到内核当中
        if(bind(_listensock,CONV(&local),sizeof(local))==-1)
        {
            lg.LogMessage(Fatal,"Bind fail,the error code :%d,error string: %s\n",errno,strerror(errno));
            exit(Bind_Err);
        }
        lg.LogMessage(Debug,"Bind success,sockfd%d\n",_listensock);

        //往事俱备,接下来服务端进行监听就可以了
        if(listen(_listensock,default_backlog)==-1)
        {
            lg.LogMessage(Fatal,"Listen fail,the error code :%d,error string: %s\n",errno,strerror(errno));
            exit(Listen_Err);
        }
        lg.LogMessage(Debug,"Listen success,sockfd%d\n",_listensock);
        //启动线程池
        ThreadNs::ThreadPool<task_t>::GetInstance()->Start();
        //_funcs.insert(make_pair("defaultService",std::bind()))
    }
    void Serveice(int sockfd,InetAddrtoLocal addr)
    {
        //先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
        char buffer[1024];
        
       while(true)
       {
        ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
         if(n>0)
        {
            buffer[n] = 0;
            cout<<addr.Info()<<"say:"<<buffer<<endl;
            string echo_string = "server say:";
            echo_string+=buffer;
            write(sockfd,echo_string.c_str(),echo_string.size());

        }
        else if(n==0)
        {
            lg.LogMessage(Info,"client quit...\n");
            break;

        }
        else
        {
            lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
            break;
        }
       }
    }
    void Start()
    {
        _isrunning = true;
        signal(SIGCHLD,SIG_IGN);//在Linux环境中,如果对SIG_IGN进行忽略,子进程退出的时候,会自动释放资源
        while(_isrunning)
        {
            //接收客户端信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensock,CONV(&peer),&len);
            if(sockfd==-1)
            {
                lg.LogMessage(Fatal,"accept fail,the error code :%d,error string: %s\n",errno,strerror(errno));
                continue;//失败了也不退出,不放弃继续等待下一位有缘人
            }
            lg.LogMessage(Debug,"accept success,sockfdL%d\n",sockfd);

            //v6:线程池,解决来了才创建线程的效率低下的问题
            task_t t = std::bind(&TcpServer::Route,this,sockfd,InetAddrtoLocal(peer));
            ThreadNs::ThreadPool<task_t>::GetInstance()->Push(t);//将任务推送到线程池中执行
        }

    }
    string Read(int sockfd)
    {
        char type[1024];
        ssize_t n = read(sockfd,type,sizeof(type)-1);
        if(n>0)
        {
            //读到了
            type[n] = 0;
        }
        else if(n==0)// read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
        {
            lg.LogMessage(Info,"client quiy...\n");
        }
        else
        {
            lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
        }
        return type;
    }
    void Route(int sockfd,InetAddrtoLocal addr)
    {
        //根据客户端发来的信息路由与之对应的服务
        //_funcs["defaultService"](sockfd,addr);
        string type = Read(sockfd);//读取客户端发来的消息
        lg.LogMessage(Debug,"%s select %s\n",addr.Info().c_str(),type.c_str());
        if(type=="ping")
            _funcs[type](sockfd,addr);//执行回调函数
        else if(type=="translate")
            _funcs[type](sockfd,addr);
        else if(type=="transform")
            _funcs[type](sockfd,addr);
        else
        {}

        close(sockfd);
    }
     void RegisterFunc(const std::string &name, callback_t func)
    {
        //注册服务
        _funcs[name] = func;
    }
    void DefaultService(int sockfd, InetAddrtoLocal &addr)
    {
        //发送默认的服务有哪些
        (void)addr;
        std::string service_list = " |";
        for(auto func: _funcs)
        {
            service_list += func.first;
            service_list += "|";
        }

        write(sockfd, service_list.c_str(), service_list.size());
    }
   
    //之所以要static是因为pthread_create的函数方法参数只能有一个void*,而成员函数默认还有一个this参数
   static void *HandlerRequest(void *args) //static的话就没有默认的this参数,所以要在ThreadData中自己传入
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->GetServer()->Serveice(td->SockFd(), td->addr);
        delete td;
        return nullptr;
    }

    
    ~TcpServer()
    {

    }
private:
    uint16_t _port;
    int _listensock;
    bool _isrunning;

    //构建业务逻辑
    unordered_map<string,callback_t> _funcs;
};

🍎TcpClient.cc

#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include<sys/types.h>
//包含网络头文件
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//包含外部头文件
#include"Log.hpp"
#include"CommErr.hpp"



#define Retry_Count 5
void Usage(const string & process)
{
    cout<<"Usage:"<<process<<" server_ip server_port"<<endl;
}

bool visitServer(string& server_ip,uint16_t& server_port,int* cnt)
{
    
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
       lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
        return false;
    }
    lg.LogMessage(Debug,"create success,sockfd:%d\n",sockfd);


    //初始化本地网络信息
    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(server_port);
    // p:process(进程), n(网络) -- 不太准确,但是好记忆
    inet_pton(AF_INET,server_ip.c_str(),&local.sin_addr);

    //发起连接的时候,client会被OS自动进行本地绑定。所以我们不用显示bind!
    
    //接下来开始主动去连接服务端
    int n = connect(sockfd,CONV(&local),sizeof(local));
    if(n==-1)
    {
        lg.LogMessage(Error,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
        return false;
    }
    *cnt = 0;
    //连接成功后,去发消息给服务端,并接收服务端发来的消息
    string inbuffer;
    cout<<"Please select service or quit:";//挑选服务模式
    getline(cin,inbuffer);
    if(inbuffer == "quit") return true;

    ssize_t ret = write(sockfd,inbuffer.c_str(),inbuffer.size());
  
        
        if(ret>0)
        {
            //服务端收到我们发送的挑选服务信息了
           while(true)
           {
             //发送成功,此时我们可以顺便读取服务端发送来的消息
            char buffer[1024];
            int n = read(sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                //读到了
                buffer[n] = 0;
                cout<<"server say:"<<buffer<<endl;

                //接下来继续与服务端对话
                string inbuffer;
                cout<<"Please enter:";
                getline(cin,inbuffer);
                write(sockfd,inbuffer.c_str(),inbuffer.size());
            }
            else if(n==0)
            {
                break;
            }
            else
            return false;//读取失败
           }
        }
        else
        {
            return false;
        }

        
  

    close(sockfd);//关闭文件描述符

    return true;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return 1;
    }
    //将命令行的ip和port记录
    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);

    int cnt = 1;
    while(cnt <= Retry_Count)
    {
        bool  result = visitServer(server_ip,server_port,&cnt);
        if(result)
        {
            break;//这是正常退出情况
        }
        else
        {
            sleep(1);
            cout<<"server offline,retrying connect...count = "<<cnt<<endl;
            cnt++;
        }
    }
    if(cnt >= Retry_Count)
    {
        cout<<"server offline"<<endl;
    }
    return 0;
}

🍎Main.cc

#include "TcpServer.hpp"
#include "CommErr.hpp"
#include <memory>



void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_port\n" << std::endl;
}
void Interact(int sockfd)
{
  while(true)
  {
      char buffer[1024];
    ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);//读取客户端信息
    if (n > 0)
    {
        buffer[n] = 0;
        cout<<"clinet say:"<<buffer<<endl;
        string message = buffer;
        if(write(sockfd, message.c_str(), message.size())<0)//发送回客户端
            cout<<"send fail"<<endl;
    }
    else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
    {
        lg.LogMessage(Info, "client quit...\n");
        break;
    }
    else
    {
        lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
        break;
    }
  }
}
void Ping(int sockfd, InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "ping", sockfd);
    // 一直进行IO
    std::string message = "Ping Service Start...";
     write(sockfd, message.c_str(), message.size());//先回应来自客户端的消息
    Interact(sockfd);
   
   
}

void Translate(int sockfd,InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Translate", sockfd);
   
}

// 改成大写,字符串改成大写
void Transform(int sockfd, InetAddrtoLocal addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Transform", sockfd);
    
}
// ./tcp_server 8888
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    uint16_t port = stoi(argv[1]);

    std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);
   
    //在内核中注册服务类型
    tsvr->RegisterFunc("ping", Ping);
    tsvr->RegisterFunc("translate", Translate);
    tsvr->RegisterFunc("transform", Transform);
    
    //初始化和启动服务端
    tsvr->Init();
    tsvr->Start();

    return 0;
}

实现效果如下: 👇🏻👇🏻👇🏻
在这里插入图片描述

至于还有更多的服务模式可以根据自己的需求去增设,这里只写了ping对话服务

补充翻译服务

🍎Translate.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"

const std::string unknown = "unknown";
const std::string mydict = "./resouce/dict.txt";
const std::string sep = " ";

class Wold
{
    // 单词, 音节, 中文解释,类型, 相近
};

class Translater
{
public:
    Translater(std::string dict_path = mydict) : _dict_path(dict_path)
    {
        LoadDict();
        Parse();
    }
    void LoadDict()
    {
        std::ifstream in(_dict_path);
        std::string line;
        while(std::getline(in, line))//边读取一行
        {
            lines.push_back(line);//边写入一行
        }
        in.close();
        lg.LogMessage(Debug, "Load dict txt success, path: %s\n", _dict_path.c_str());
    }
    void Parse()
    {
        for(auto &line : lines)
        {
            auto pos = line.find(sep); // XXXX YYY
            if(pos == std::string::npos) continue;
            else
            {
                std::string word = line.substr(0, pos);//空格前的英文
                std::string chinese = line.substr(pos+sep.size());//空格后的中文
                _dict.insert(std::make_pair(word, chinese));
            }
        }
        lg.LogMessage(Debug, "Parse dict txt success, path: %s\n", _dict_path.c_str());
    }
    void debug()//打印字典
    {
        // for(auto &line : lines)std::cout << line << std::endl;

        for(auto &elem : _dict)
        {
            std::cout << elem.first << " : " << elem.second << std::endl;
        }
    }
    std::string Excute(const std::string &word)//翻译转换
    {
        auto iter = _dict.find(word);
        if (iter == _dict.end())
            return unknown;
        else
            return _dict[word];
    }
    ~Translater()
    {
    }

private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
    std::vector<std::string> lines;
};

效果如下: 👇🏻👇🏻👇🏻
在这里插入图片描述


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

【C语言】冒泡排序算法详解

目录 一、算法原理二、算法分析时间复杂度空间复杂度稳定性 三、C语言实现四、Python实现 冒泡排序&#xff08;Bubble Sort&#xff09;是一种基础的排序算法。它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。遍历数列…

IDEA 使用备忘录(不断更新)

IDEA 项目结构&#xff08;注意层级结构&#xff0c;新建相应结构时&#xff0c;按照以下顺序新建&#xff09;&#xff1a; project&#xff08;项目&#xff09; module&#xff08;模块&#xff09; package&#xff08;包&#xff09; class&#xff08;类&#xff09; 项…

Matlab|【免费】【sci】考虑不同充电需求的电动汽车有序充电调度方法

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现sci文献《A coordinated charging scheduling method for electric vehicles considering different charging demands》&#xff0c;主要实现电动汽车协调充电调度方法&#xff0c;该方法主要有以…

【JAVA进阶篇教学】第三篇:JDK8中Stream API使用

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第三篇&#xff1a;JDK8中Stream API使用。 Java 8 中的 Stream API 提供了一种便捷、高效的方式来处理集合数据&#xff0c;它支持函数式编程风格的操作&#xff0c;包括过滤、映射、归约等。Stream API 可以大大简化集…

Ubuntu 22最新dockers部署redis哨兵模式,并整合spring boot的详细记录(含spring boot项目包)

dockers部署redis哨兵模式&#xff0c;并整合spring boot 环境说明相关学习博客一、在docker中安装redis1、下载dockers镜像包和redis配置文件&#xff08;主从一样&#xff09;2、编辑配置文件&#xff08;主从一样&#xff09;3、启动redis&#xff08;主从一样&#xff09;4…

4-Java方法详解

目录 Java方法详解 1、什么是方法 2、方法的定义及调用 3、方法重载 4、命令行传参 5、可变参数 6、递归 例题&#xff1a;代码实现一个计算机 Java方法详解 1、什么是方法 2、方法的定义及调用 形参&#xff1a;用来定义作用的 实参&#xff1a;实际调用传递给他的参数…

【Qt 学习笔记】Qt常用控件 | 显示类控件Progress Bar的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 显示类控件Progress Bar的使用及说明 文章编号&#xff…

【创建型模式】抽象工厂模式

一、抽象工厂模式概述 抽象工厂模式定义&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 模式动机&#xff1a; 1.当系统提供的工厂生产的具体产品并不是一个简单的对象&#xff0c;而是多个位于不同产品等级结构、属于不同类型的…

Simba:Mamba 增强了 U-ShiftGCN,用于视频中的骨骼动作识别

Simba&#xff1a;Mamba 增强了 U-ShiftGCN&#xff0c;用于视频中的骨骼动作识别 摘要IntroductionRelated WorksMethodologyDown-sampling ShiftGCN Encoder Experiments & ResultsDatasets Simba: Mamba augmented U-ShiftGCN for Skeletal Action Recognition in Video…

通过vue完成表格数据的渲染展示和vue的生命周期及小结

案例 通过vue完成表格数据的渲染展示 把视图区展示的数据 死数据替换掉 从vue的数据模型中读取 展示在视图区 vue中的数据 模型是js中的自定义类型 形成的数组 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&l…

NL2SQL进阶系列(1):DB-GPT-Hub、SQLcoder、Text2SQL开源应用实践详解

NL2SQL进阶系列(1)&#xff1a;DB-GPT-Hub、SQLcoder、Text2SQL开源应用实践详解 NL2SQL基础系列(1)&#xff1a;业界顶尖排行榜、权威测评数据集及LLM大模型&#xff08;Spider vs BIRD&#xff09;全面对比优劣分析[Text2SQL、Text2DSL] NL2SQL基础系列(2)&#xff1a;主流…

2024华中杯C题平面曲线重建思路

华中杯数学建模思路 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技 术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号&#xff0c;其基本原理是当外界环境参数发生变化时&#xff0c;会引起光纤传感器中光波参量&#xff08;如波长、相位、…

IP-guard WebServer 权限绕过漏洞复现(QVD-2024-14103)

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

全球排名前十的搜索引擎,你猜百度排名在第几位?bing稳居二位!

通常情况下&#xff0c;营销人员在争夺其在线业务的流量时会非常关注Google&#xff0c;无论是通过他们的网站&#xff0c;博客文章还是其他形式的内容。考虑到谷歌无疑是最受欢迎的搜索引擎&#xff0c;拥有超过85%的搜索市场份额&#xff0c;这是有道理的。 但这种受欢迎程度…

【Qt】Qt Hello World 程序

文章目录 1、Qt Hello World 程序1.1 使用按钮实现1.1.1 使用可视化方式实现 1.1.2 纯代码方式实现 label创建堆&#xff08;内存泄漏&#xff09;或者栈问题Qt基础类&#xff08;Qstring、Qvector、Qlist&#xff09;乱码问题零散知识 1、Qt Hello World 程序 1.1 使用按钮实…

【代码随想录】【动态规划】day48:打家劫舍

打家劫舍1 def rob(self, nums):""":type nums: List[int]:rtype: int"""# 分为两个情况&#xff0c;偷还是不偷&#xff0c;# dp[i]为考虑到第i个房间时的最大值if len(nums) 0: # 如果没有房屋&#xff0c;返回0return 0if len(nums) 1: #…

QoS流量整形

流量整形是一种带宽技术形式&#xff0c;它延迟某些类型的网络数据包的流动&#xff0c;以确保更高优先级应用程序的网络性能&#xff0c;它主要涉及调整数据传输速率&#xff0c;以确保网络资源以最佳容量得到利用。流量整形的目的是防止网络拥塞并提高网络的整体性能&#xf…

穿越物联网的迷雾:深入理解MQTT协议

目录标题 1、MQTT简介核心特性 2、MQTT的工作原理通信过程 3、MQTT的消息质量&#xff08;QoS&#xff09;4、安全机制5、实践应用环境准备示例项目发布者客户端订阅者客户端 6、最佳实践7、结论8、参考资料 在物联网&#xff08;IoT&#xff09;的海洋中&#xff0c;数据像水流…

【深度学习】Attention、Self-Attention、Multi-Head Attention

一、Attention 在CV领域&#xff0c;注意力机制通常分为通道注意力和空间注意力或者两者结合。 一张图像经backbone得到的特征通常包括多个通道&#xff0c;每个通道是一个像素矩阵&#xff0c;每个通道对任务的贡献不尽相同&#xff0c;单个通道的特征图中每个像素对任务的贡…

Ansible在macOS上的安装部署

一、安装 Ansible&#xff08;使用 Homebrew&#xff09; 安装 Homebrew&#xff08;如果尚未安装&#xff09;&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"使用 Homebrew 安装 Ansible&#x…