Linux——socket编程之tcp通信

news2024/11/16 2:32:22

前言

前面我们学习socket的udp通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。

一、tcp套接字创建

UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建套接字。

socket()    ->创建套接字

  • 参数domain:代表协议家族,输入AF_INET为IPv4协议,该参数告诉操作系统如何解释后面的type和protocol参数
  • 参数type:指定了套接字的类型,即指定了套接字的通信方式和数据传输方式,输入SOCK_STREAM面向字节流
  • 参数protocol:代表所使用的具体协议,输入0让系统自动选择合适的协议

二、填充网络信息并bind

创建好了基于tcp的IPv4套接字,我们还需要填充本地网络信息,知道了本地ip地址和端口号,和socket创建的sockfd进行绑定,后面就可以通过sockfd进行通信了

如下是 IPv4 地址的结构体sockaddr_in 。

struct sockaddr_in {
    short int sin_family;            // 地址族(Address Family),一般为 AF_INET
    unsigned short int sin_port;     // 端口号(Port),使用网络字节顺序(big-endian)
    struct in_addr sin_addr;         // IPv4 地址
    unsigned char sin_zero[8];       // 未使用的填充字段,通常设置为 0
};

填充完毕就可以开始bind了。

bind()   ->让socket信息与sockfd进行绑定

  • 参数sockfd:套接字文件描述符  传入之前创建的socket返回值
  • 参数addr:创建好的sockaddr结构体的地址
  • 参数addrlen:网络信息结构体的长度的长度

三、建立连接与监听

由于tcp是面向连接的,因此客户端和服务器双方要进行数据通信,必须要先建立连接。一般都是客户端去发起连接请求,想让服务端为他进行服务。比如你想刷抖音,那么你得打开抖音,申请去刷抖音,而不是说抖音舔着脸求你刷我,虽然他也想要日活,但是主动权还是在你手上的。

那服务端也需要去监听连接的到来,方便为客户端服务,因此有连接和监听两种状态。

connect()  建立连接

参数与bind一样

 listen()  监听连接

  • 参数sockfd:套接字描述符
  • 参数backlog:指定连接请求的最大排队数量,即在队列中等待接受连接的最大数量。他只是用于控制等待连接队列的长度,并不是服务器的最大并发连接数。
  • 返回值,成功返回0

listen返回值不为0证明listen失败。 

四、获取连接

客户端使用connect与服务端进行连接,服务端listen将自己设置为监听状态,代表能接受连接,但是你只是说自己能接受,并不会创建与客户端的连接。

正在的连接需要服务端既要listen进行监听,又要accept进行获取连接

accept()  从处于监听状态的套接字中接受一个传入的连接请求,并创建一个新的套接字来与客户端进行通信。

  • 参数sockfd:处于监听状态的套接字描述符
  • 参数addr:创建好的sockaddr结构体的地址
  • 参数addrlen:创建好的sockaddr结构体的长度的地址
  • 返回值:返回一个sockfd,使用返回的这个sockfd与客户端进行通信。

也就是说,在tcp通信中,是有两个sockfd的,之前我们socket、bind、listen、accept使用的都是同一个sockfd,但是链接已经完全建立好之后,我们要与客户端进行通信,需要使用accept返回的sockfd

这就类似于在街边拉客的美容店,张三在外面寻找客人,带到美容店去消费,进入美容店后张三就不再管你了,而是让美容店里的员工对你进行服务,张三转头又去拉客去了。

五、tcp通信的实现

有了这些预备知识,我们就可以编写代码了,具体代码如下,注释写的比较详细

Comm.hpp   (错误码)

#pragma once

//错误码
enum{
    Usage_Err = 1,
    Socket_Err,
    Bind_Err,
    Listen_Err,
    Connect_Err,
};

 InetAddr.hpp  (封装sockaddr_in结构体)

#pragma once
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class InetAddr
{
public:
    InetAddr(struct sockaddr_in& peer):_addr(peer)
    {
        _port = ntohs(peer.sin_port); // ntohs网络转主机short

        // inet_ntoa 多线程下不安全 使用静态缓冲区来存储结果,并返回指向这个静态缓冲区的指针
        // 每次调用都会覆盖这个静态缓冲区的内容
        // _ip = inet_ntoa(peer.sin_addr); // inet_ntoa sin_addr转点分十进制字符串ip
        
        // 现在我们自己维护空间ipbuff,让inet_ntop把数据写到ipbuff里  这样线程安全
        char ipbuff[64]; 
        inet_ntop(AF_INET,&peer.sin_addr,ipbuff,sizeof(ipbuff));
        _ip = ipbuff;
    }
    string GetIp()
    {
        return _ip;
    }
    uint16_t GetPort()
    {
        return _port;
    }
    struct sockaddr_in& GetAddr()
    {
        return _addr;
    }
    string PrintDebug()
    {
        string info = _ip;
        info+=":";
        info+=to_string(_port);
        return info;
    }
    ~InetAddr()
    {
    }

private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

 LockGuard.hpp  (锁的守护者)就是C++11里的lock_guard

#pragma once
#include <pthread.h>

// 不定义锁,外部会传递锁
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void UnLock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        : _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;
};

Log.hpp  (日志类) 

#pragma once

#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

enum{
    Screen = 10,
    OneFile,
    ClassFile
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    
    default:
        return "Unkonw"; 
    }
}
 
const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";

class Log  
{
public:
    Log(int style = default_style,string filename = default_filename)
        :_style(style),_filename(filename)
    {
        if(_style != Screen)
            mkdir(logdir.c_str(),0775);
    }

    //更改打印方式
    void Enable(int style)
    {
        _style = style;
        if(_style != Screen)
            mkdir(logdir.c_str(),0775);
    }

    //时间戳转化为年月日时分秒
    string GetTime()
    {
        time_t currtime = time(nullptr);
        struct tm* curr = localtime(&currtime);
        char time_buffer[128];
        snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
        curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);
        return time_buffer;
    }

    //写入到文件中
    void WriteLogToOneFile(const string& logname,const string& message)
    {
        FILE* fp = fopen(logname.c_str(),"a");
        if(fp==nullptr)
        {
            perror("fopen filed");
            exit(-1);
        }
        fprintf(fp, "%s\n", message.c_str());

        fclose(fp);
    }

    //打印日志
    void WriteLogToClassFile(const string& levelstr,const string& message)
    {
        string logname = logdir;
        logname+="/";
        logname+=_filename;
        logname+=levelstr;
        WriteLogToOneFile(logname,message);
    }

    void WriteLog(const string& levelstr,const string& message)
    {
        switch (_style) 
        {
        case Screen:
            cout<<message<<endl;//打印到屏幕中
            break;
        case OneFile:
            WriteLogToClassFile("all",message);//给定all,直接写到all里
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr,message);//写入levelstr里
            break;
        default:
            break;
        }
    }

    //打印日志
    void LogMessage(int level,const char* format,...)
    {
        char rightbuffer[1024];//处理消息
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        //现在args指向了可变参数部分
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        
        va_end(args);

        char leftbuffer[1024];//处理日志等级、pid、时间
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string loginfo = leftbuffer;
        loginfo+=rightbuffer;
        WriteLog(levelstr,loginfo);
    }

    //提供接口给运算符重载使用
    void _LogMessage(int level,char* rightbuffer)
    {
        char leftbuffer[1024];
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string messages = leftbuffer;
        messages+=rightbuffer;
        WriteLog(levelstr,messages);
    }

    //运算符重载
    void operator()(int level,const char* format,...)
    {
        char rightbuffer[1024];
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        va_end(args);
        _LogMessage(level,rightbuffer);
    }

    ~Log() 
    {}
private:
    int _style;
    string _filename;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.Enable(Screen); 
    }
    ~Conf()
    {}
};

Conf conf;

 nocopy.hpp   (让服务器类继承nocopy,达到不可拷贝的作用)

#pragma once

class nocopy
{
public:
    nocopy(){}
    
    nocopy(const nocopy& n) = delete;
    nocopy& operator=(const nocopy& n) = delete;
    ~nocopy(){}
}; 

Thread.hpp    Thread库封装

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

// 设计方的视角
//typedef std::function<void()> func_t;
namespace kky
{
    template<class T>
using func_t = std::function<void(T&)>;

template<class T>
class Thread
{
public:
    Thread(const std::string &threadname, func_t<T> func, T &data)
    :_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
    {}

    // 不加static会有this指针,无法调用pthread_creadte
    static void *ThreadRoutine(void *args) // 类内方法,
    {
        // (void)args; // 仅仅是为了防止编译器有告警
        Thread *ts = static_cast<Thread *>(args);

        ts->_func(ts->_data);

        return nullptr;
    }

    //运行线程
    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
        if(n == 0) 
        {
            _isrunning = true;
            return true;
        }
        else return false;
    }

    //等待线程
    bool Join()
    {
        if(!_isrunning) return true;
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }
    std::string ThreadName()
    {
        return _threadname;
    }
    bool IsRunning()
    {
        return _isrunning; 
    }
    ~Thread()
    {}
private:
    pthread_t _tid;
    std::string _threadname;
    bool _isrunning;
    func_t<T> _func;
    T _data;
}; 
}

 Threadpool.hpp  (线程库的封装)

#pragma once

#include <pthread.h>
#include <vector>
#include <functional>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace std;

namespace kky
{ 
static const int default_num = 5;

class ThreadData
{
public:
    ThreadData(string name)
        : thread_name(name)
    {
    }
    string thread_name;
};

template <class T>
class ThreadPool
{
private:
    ThreadPool(int thread_num = default_num)
        : _thread_num(thread_num)
    {
        pthread_mutex_init(&_mutex, nullptr); // 初始化
        pthread_cond_init(&_cond, nullptr);

        // 创建指定个数的线程
        for (int i = 0; i < _thread_num; i++)
        {
            string thread_name = "thread_";
            thread_name += to_string(i + 1);

            ThreadData td(thread_name); // ThreadData为线程数据类型

            Thread<ThreadData> t(thread_name, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td);
            _threads.emplace_back(t);

            lg(Info, "%s 被创建...", thread_name.c_str()); // 写入
        }
    }

    ThreadPool(const ThreadPool<T> &tp) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;

public:
    static ThreadPool<T> *GetInstance()
    {
        if(instance == nullptr) 
        {
            LockGuard lockguard(&sig_lock);
            if (instance == nullptr)
            {
                instance = new ThreadPool<T>();
                lg.LogMessage(Info, "创建单例成功...");
            }
        }
        return instance;
    }

    // 线程运行
    bool Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
            lg.LogMessage(Info, "%s 正在运行!", thread.ThreadName().c_str());
        }
    }

    // 线程条件变量等待
    void ThreadWait(ThreadData &td)
    {
        lg.LogMessage(Debug, "没有任务,%s休眠了", td.thread_name.c_str());
        pthread_cond_wait(&_cond, &_mutex);
    }

    // 线程条件变量唤醒
    void ThreadWakeUp()
    {
        pthread_cond_signal(&_cond);
    }

    // 执行任务
    void ThreadRun(ThreadData &td)
    {
        while (1)
        {
            T t;
            // 取出任务
            {
                LockGuard lockguard(&_mutex); // 代码块中自动加锁与解锁
                while (_q.empty())
                {
                    ThreadWait(td);
                    lg.LogMessage(Debug, "有任务了,%s去执行任务了", td.thread_name.c_str());
                }
                t = _q.front();
                _q.pop();
            }
            t();
            // 处理任务 我们通过打印消息来模拟任务
            //  cout<<t<<endl;
            // lg.LogMessage(Debug, "%s 计算结果为:%d", td.thread_name.c_str(), t);
        }
    }

    // 将任务放到队列中
    void Push(const T &in)
    {
        {
            LockGuard lockguard(&_mutex);
            _q.push(in);
        }
        lg.LogMessage(Debug, "任务push成功");
        ThreadWakeUp();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex); // 销毁
        pthread_cond_destroy(&_cond);
    }

    // 进程等待
    void Wait()
    {
        for (auto &thread : _threads)
        {
            thread.Join();
        }
    }

private:
    queue<T> _q;
    vector<Thread<ThreadData>> _threads;
    int _thread_num;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T> *instance;
    static pthread_mutex_t sig_lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

template<class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;
}

 TcpServer.hpp   (服务端的封装)

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unordered_map>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <thread>
#include "Log.hpp"
#include "Comm.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Threadpool.hpp"

class ThreadData;
static const int default_backlog = 5;
using func_t = function<void(ThreadData *)>;
using task_t = function<void()>;
using callback_t = function<void(int sockfd, InetAddr &addr)>;

class ThreadData
{
public:
    ThreadData(int sock, struct sockaddr_in &peer)
        : _sockfd(sock), _addr(peer)
    {
    }
    ~ThreadData()
    {
        close(_sockfd);
    }

public:
    int _sockfd;
    InetAddr _addr;
};

class TcpServer : public nocopy
{
public:
    TcpServer(uint16_t port)
        : _port(port), _isrunning(false)
    {
    }

    void HandlerRequest(ThreadData *td)
    {
        Service1(td->_sockfd);
        delete td;
    }

    void Init()
    {
        // 1.创建套接字  得到文件描述符
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM 代表TCP字节流
        if (_listen_sockfd < 0)
        {
            lg.LogMessage(Fatal, "create socket error, \
            errno code: %d,error string: %s",
                          errno, strerror(errno));
            exit(Socket_Err);
        }
        lg.LogMessage(Debug, "create socket success, sockfd: %d", _listen_sockfd);

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

        // 2.填充网络信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 3.bind
        int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            lg.LogMessage(Fatal, "bind socket error, \
            errno code: %d,error string: %s",
                          errno, strerror(errno));
            exit(Bind_Err);
        }
        lg.LogMessage(Debug, "bind socket success, sockfd: %d", _listen_sockfd);

        // 4.设置监听状态,TCP特有的
        if (listen(_listen_sockfd, default_backlog) != 0)
        {
            lg.LogMessage(Fatal, "listen socket error, \
            errno code: %d,error string: %s",
                          errno, strerror(errno));
            exit(Listen_Err);
        }
        lg.LogMessage(Debug, "listen socket success, sockfd: %d", _listen_sockfd);

        kky::ThreadPool<task_t>::GetInstance()->Start();
        funcs.insert(make_pair("defaultService",std::bind(&TcpServer::DefaultService
        ,this,placeholders::_1,placeholders::_2)));
    }

    // accept获取的sockfd是全双工的,因此我们read和write都可以用这个sockfd
    void Service1(int sockfd)
    {
        char buff[1024];
        while (true)
        {
            ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                cout << "client say# " << buff << endl;
                string echo_string = "server echo# ";
                echo_string += buff;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // 表示对面关闭了连接
            {
                lg.LogMessage(Info, "client quit...");
                break;
            }
            else
            {
                lg.LogMessage(Error, "read socket error, \
                errno code: %d,error string: %s",
                              errno, strerror(errno));
                break;
            }
        }
    }
    void Service2(int sockfd, InetAddr addr)
    {
        char buff[1024];
        while (true)
        {
            ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                cout << "[" << addr.PrintDebug() << "]#" << buff << endl;
                string echo_string = "server echo# ";
                echo_string += buff;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // 表示对面关闭了连接
            {
                lg.LogMessage(Info, "client quit...");
                break;
            }
            else
            {
                lg.LogMessage(Error, "read socket error, \
                errno code: %d,error string: %s",
                              errno, strerror(errno));
                break;
            }
        }
    }

    void Start()
    {
        _isrunning = true;
        // 忽略SIGCHLD信号,那么子进程退出时内核会自动回收其资源,并且不会产生僵尸进程
        // 与下面v3版本多线程一起使用,如果不是V3版本,这句代码无用
        signal(SIGCHLD, SIG_IGN);
        while (_isrunning)
        {
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // 5.获取连接
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                lg.LogMessage(Warning, "accept socket error");
                continue;
            }
            lg.LogMessage(Debug, "accept socket success,get a new sockfd: %d", sockfd);

            // 6.提供服务
            // 6.v1版本 只能一个为一个进程服务
            //  Service1(sockfd);
            //  close(sockfd);

            // v2  多进程 fork()孙子进程版本
            //  pid_t id = fork();
            //  if(id<0)
            //  {
            //      close(sockfd);
            //      continue;
            //  }
            //  else if(id ==0)
            //  {
            //      //子进程不用关心_listen_sockfd,因为父进程在listen。子进程只服务就好
            //      close(_listen_sockfd);
            //      if(fork()>0)
            //          exit(0);

            //     //孙子进程 父进程退出了,他变成孤儿进程,
            //     //被操作系统领养 死亡自动回收,不需要wait了
            //     Service1(sockfd);
            //     close(sockfd);
            //     exit(0);
            // }
            // else
            // {
            //     //父进程
            //     close(sockfd); //sockfd交给子进程去服务了

            //     //nullptr代表不需要子进程的退出信息,0代表阻塞等待
            //     pid_t rid = waitpid(id,nullptr,0);
            //     if(rid == id)
            //     {
            //         //等待成功  //去干你想干的事情
            //     }
            // }

            // v3 多进程 信号版本  //与104行signal(SIGCHLD,SIG_IGN);一起使用
            // 通过信号忽略的方式让父进程不再管子进程
            // pid_t id = fork();
            // if(id < 0)
            // {
            //     close(sockfd);
            //     continue;
            // }
            // else if(id == 0)
            // {
            //     //子进程
            //     close(_listen_sockfd);
            //     Service1(sockfd);
            //     close(sockfd);
            //     exit(0);
            // }
            // else
            // {
            //     //父进程
            //     close(sockfd);
            // }

            // v4 多线程版本

            // ThreadData *td = new ThreadData(sockfd,peer);
            // //不直接传sockfd,是担心主线程while循坏去将sockfd覆盖,因此传递对象指针
            // func_t f = bind(&TcpServer::HandlerRequest,this,placeholders::_1);
            // thread t1(f,td);
            // t1.detach();

            // v5 线程池版本
            task_t f = bind(&TcpServer::Routine, this, sockfd, peer);
            kky::ThreadPool<task_t>::GetInstance()->Push(f);
        }
    }

    //服务器去读取客户端的输入信息,依据输入提供服务
    string Read(int sockfd)
    {
        char buff[1024];
        ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
        if (n > 0)
        {
            buff[n] = 0;
        }
        else if (n == 0) // 表示对面关闭了连接
        {
            lg.LogMessage(Info, "client quit...");
        }
        else
        {
            lg.LogMessage(Error, "read socket error, \
                errno code: %d,error string: %s",
                          errno, strerror(errno));
        }
        return buff;
    }

    void Routine(int sockfd,InetAddr addr)
    {
        funcs["defaultService"](sockfd,addr);
        string type = Read(sockfd);
        lg.LogMessage(Debug,"%s select %s",addr.PrintDebug().c_str(),type.c_str());
        if(type == "ping")
            funcs[type](sockfd,addr);
        else if(type == "translate")
            funcs[type](sockfd,addr);
        else 
            funcs["defaultService"](sockfd,addr);
        close(sockfd);
    }

    void DefaultService(int sockfd,InetAddr& addr)
    {
        std::string service_list = " | ";
        for(auto& func : funcs)
        {
            service_list += func.first; 
            service_list +=" | ";
        }
        write(sockfd,service_list.c_str(),service_list.size());
    }

    // 添加string->callback_t 方法的映射
    void RegisterFunc(const string &name, callback_t func)
    {
        funcs[name] = func;
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listen_sockfd;
    bool _isrunning;

    // 输入"xxx" 就去执行 xxx 任务
    unordered_map<string, callback_t> funcs;
};

 Main.cc   (服务端的入口)

#include "TcpServer.hpp"

#include <memory>

using namespace std;

string unknown = "unknown";
class Dictionary
{
public:
    Dictionary()
    {
        dict.insert(make_pair<string,string>("banana","香蕉"));
        dict.insert(make_pair<string,string>("apple","苹果"));
        dict.insert(make_pair<string,string>("monkey","猴子"));
        dict.insert(make_pair<string,string>("love","爱"));
    }
    string Excute(const string& word)
    {
        if(dict.find(word)!=dict.end())
            return dict[word];
        return unknown;
    }
    ~Dictionary()
    {}
private:
    unordered_map<string,string> dict;
};

Dictionary ts;

void Usage(string proc)
{
    cout << "Usage \n\t" << proc << "local_port\n"
         << endl;
}

//判断服务器是否正常运行————>心跳机制  客户ping,就会获得pong 证明服务端没问题
void Ping(int sockfd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d", 
                  addr.PrintDebug().c_str(), "ping", sockfd);
    char buffer[1024];
    ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
    if (n > 0)
        buffer[n] = 0;
    string echo_string = "Pong";
    write(sockfd,echo_string.c_str(),echo_string.size());
}

void Translate(int sockfd, InetAddr addr)
{
    lg.LogMessage(Debug, "%s select %s success, fd : %d", addr.PrintDebug().c_str(), "translate", sockfd);
    char wordbuf[128];
    ssize_t n = read(sockfd, wordbuf, sizeof(wordbuf) - 1);
    if (n > 0)
        wordbuf[n] = 0;
    string chinese = ts.Excute(wordbuf);
    write(sockfd,chinese.c_str(),chinese.size());
    lg.LogMessage(Debug,"%s Translate service,%s->%s",addr.PrintDebug().c_str(),wordbuf,chinese.c_str());
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    uint16_t port = stoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(port));

    tsvr->RegisterFunc("ping", Ping);
    tsvr->RegisterFunc("translate", Translate);

    tsvr->Init();
    tsvr->Start();
}

 TcpClient.cc   (客户端的入口)

#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include "Comm.hpp"
using namespace std;

#define Retry_Count 5

void Usage(string proc)
{
    cout << "Usage \n\t" << proc << " local_ip local_port\n"
         << endl;
}

bool VisitServer(const string &ip, const uint16_t port, int &cnt)
{
    int n = 0;
    string inbuffer;
    char service_list[1024];
    ssize_t w = 0; // write 返回值,由于goto原因  需要放在前面
    ssize_t r = 0; // read  返回值,由于goto原因  需要放在前面
    bool ret = true;
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        cerr << "socket error" << endl;
        ret = false;
        goto END;
    }

    // 2.填写sockaddr_in信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    // inet_pton 类似于 inet_addr 都是让点分十进制 IP 转网络字节序的二进制序列
    inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);

    // 3.connect进行连接,他会自动绑定,不需要手动绑定
    n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        cerr << "connect error" << endl;
        ret = false;
        goto END;
    }

    //先读取操作列表
    r = read(sockfd, service_list, sizeof(service_list) - 1);
    if (r > 0)
    {
        service_list[r] = 0;
        cout << "服务器提供的服务列表是:" << service_list << endl;
    }

    cnt = 1; // 让cnt又从1-5再次链接

    cout << "Please Select Service# ";
    getline(cin, inbuffer);

    // connect并没有产生新的sockfd,只用这一个sockfd进行通信即可。
    w = write(sockfd, inbuffer.c_str(), inbuffer.size());
    if (w > 0)
    {
        char buffer[1024];
        cout << "Please Enter# ";
        getline(cin, inbuffer);
        write(sockfd, inbuffer.c_str(), inbuffer.size());
        r = read(sockfd, buffer, sizeof(buffer) - 1);
        if (r > 0)
        {
            buffer[r] = 0;
            cout << buffer << endl;
        }
        else if (r == 0) // read返回值为0,代表读端关闭,算是正常结束
        {
            goto END;
        }
        else
        {
            ret = false;
            goto END;
        }
    }
    else if (w == 0)
    {
        cout << "你并没有输入" << endl;
    }
    else
    {
        cout << "write时服务器关闭" << endl;
        ret = false;
    }
END:
    close(sockfd);
    return ret;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(Usage_Err);
    }
    signal(SIGPIPE, SIG_IGN);
    string ip = argv[1];
    uint16_t port = stoi(argv[2]);

    // 断线重连
    int cnt = 1;
    while (cnt <= Retry_Count)
    {
        int result = VisitServer(ip, port, cnt);
        if (result)
            break;
        else
        {
            sleep(1);
            cout << "server offline, retrying..., count: " << cnt++ << endl;
        }
    }
    if (cnt > Retry_Count)
    {
        cout << "server offline" << endl;
    }
}

 Makefile (一键构建代码)

.PHONY:all
all:tcp_server tcp_client
tcp_server:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:TcpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f tcp_server tcp_client

 代码链接

运行结果如下

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息技术自主可控的意义,针对国产化替换,服务器虚拟化或比公有云更具优势

我们之前在文章《博通收购VMware后&#xff0c;经销商和用户如何应对&#xff1f;新出路&#xff1a;虚拟化国产替代&#xff0c;融入信创云生态》中提到&#xff1a; 从信创整体发展和政策标准来看&#xff0c;供应商必须满足两个条件&#xff1a;一是融入国产信息技术生态&am…

DuDuTalk:4G桌面拾音设备在银行网点服务场景的应用价值

随着科技的飞速发展&#xff0c;银行业也在不断地寻求创新以提高服务质量和效率。在这个过程中&#xff0c;4G桌面拾音设备作为一种新型的智能设备&#xff0c;其在银行网点服务场景中的应用价值逐渐凸显出来。本文将从多个角度探讨4G桌面拾音设备在银行网点服务场景的应用价值…

如何更快地执行 Selenium 测试用例?

前言&#xff1a; 当我们谈论自动化时&#xff0c;首先想到的工具之一是 Selenium。我们都知道Selenium WebDriver 是一个出色的 Web 自动化工具。实施Selenium 自动化测试的主要原因是加速 selenium 测试。在大多数情况下&#xff0c;Selenium 的性能比手动的要好得多。但是&…

vue3+ts之el-tooltip换行显示内容

<el-tooltip placement"top-end"><div slot"content" class"tips"><el-button type"primary" click"exportData">导出</el-button></div><template #content><span class"cont…

出差——蓝桥杯十三届2022国赛大学B组真题

问题分析 该题属于枚举类型&#xff0c;遍历所有情况选出符合条件的即可。因为只需要派两个人&#xff0c;因此采用两层循环遍历每一种情况。 AC_Code #include <bits/stdc.h> using namespace std; string str;//选择的两人 bool ok(){if(str.find("A")!-1…

关于如何取消数据请求的操作

直接上码&#xff1a; class RequestManager {constructor() {this.requestQueue []}addRequestQueue(axios) {// 创建取消令牌const cancelToken axios.CancelToken.source()this.requestQueue.push(cancelToken.cancel)return cancelToken.token}clearRequestQueue() {thi…

【Android】Kotlin学习之数据容器(数组for循环遍历)

数组遍历 1. for ( item in arr){…} 2. for ( i in arr.indeces ) {…} (遍历下标) 3. for ((index, item) in arr.withInfex()) {…} (遍历下标和元素) 4. arr.forEach {} ( 遍历元素 ) 5. arr.forEachIndexed{index, item -> …}

刷!简单的转录组分析+Cytoscape三小时工作量,思路易复现

说在前面 两样本孟德尔随机化应该大伙都了解的不少&#xff0c;不过今天看到一篇&#xff0c;有点“料”的文章&#xff0c;一句话总结&#xff1a;Cytoscape乱拳打死老师傅&#xff0c;通篇除了WGCNA、差异分析是作为常规的转录组分析方法&#xff0c;剩下的几乎都是ClueGO的…

C盘文件清理

WinSxS里面的文件是不可删除的。WinSxS下有很多重要的组件&#xff0c;版本也很繁杂&#xff0c;为了保证Windows的正常运行&#xff0c;请确保这些文件一个都不能少。这些文件支撑着mscorwks.dll&#xff0c;没有它们&#xff0c;mscorwks也无法加载。强行删除后可能只有以安全…

大模型日报|今日必读的 8 篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.清华、智谱AI 团队推出代码评测基准 NaturalCodeBench 大型语言模型&#xff08;LLM&#xff09;在为生产活动生成代码方面表现出强大的能力。然而&#xff0c;目前的代码合成基准&#xff0c;如 HumanEval、MBPP …

Latex编辑器WinEdt修改正文字体大小方法,防止字体大小对眼睛不好

一、背景 使用Latex编辑器WinEdt写论文时&#xff0c;默认的正文字号太小&#xff0c;看起来比较累眼&#xff0c;如下图所示。但是该编辑器没有简单的设置菜单&#xff0c;不能一键修改字体大小。因此通过百度测试以下方法可以&#xff0c;特记录如下。 二、WinEdt修改正文字…

院校信息 | 伯明翰大学24Fall新增3个专业!附截止时间!

伯明翰大学针对2024年秋季入学&#xff0c;推出3个新的授课型硕士项目&#xff1a; MSc Financial Data Science 金融数据科学理学硕士 MSc Statistical Data Science 统计学数据科学理学硕士 MSc Statistics 统计学理学硕士 以上所有课程24fall申请截止时间为6月1日&#xf…

2024年,UTONMOS也许能带领元宇宙走向下一个征程

“元宇宙元年”开启时&#xff0c;科技的触角企图在0与1构成的世界里、安放可以数字化的一切&#xff0c;绘制出时间与空间的虚拟延长线。 尼尔斯蒂芬森笔下的虚拟城市沿着一条100米宽的道路发展&#xff0c;楼宇上的电子标志在昏暗的街区蔓延&#xff0c;人们可以通过虚拟现实…

一站式HMI软件开发套件eStation,让开发更简单高效

4月份举办的北京国际车展上全球首发车117辆&#xff0c;新能源车型278个&#xff0c;越来越多的车厂通过差异化和改善UI/UE体验&#xff0c;来获取更多用户的青睐。为快速响应差异化竞争需求&#xff0c;智能座舱HMI市场遇到以下挑战&#xff1a; 如何兼容不同项目开发人员编程…

MySQL利用变量进行查询操作

新建连接&#xff0c;自带world数据库&#xff0c;里面自带city表格。 # MySQL利用变量进行查询操作 set cityNameHaarlemmermeer; select * from city where NamecityName;# 多个结果查询 set cityName1Haarlemmermeer; set cityName2Breda; set cityName3Willemstad; selec…

个人直播/流媒体服务解决方案实践

目录 1. 说明 1.1 拓扑结构图 2. 准备工作 2.1 软硬件清单 3. 步骤 3.1 按上面的软硬件清单准备好材料 3.2 内网检查测试 3.3 透传到公网服务器 3.5 机顶盒配置 4. 总结 5. 参考 6. 后语 1. 说明 - 在本地局域网建立流媒体服务&#xff0c;并发布到公网服务器供终…

读天才与算法:人脑与AI的数学思维笔记22_中文房间

1. 华生的工作模式 1.1. 请你想象一个巨大的场景&#xff0c;其中有单词、名字和其他可能的答案&#xff0c;它们散布在各处 1.1.1. IBM所做的第一步是以某种连贯的方式排列单词 1.1.2. 第二步是理解每个问题&#xff0c;并为该问题生成候选位置标记 1.1.2.1. 爱因斯坦会演…

ChatGPT Web Midjourney一键集成最新版

准备工具 服务器一台 推荐使用浪浪云服务器 稳定 安全 有保障 chatgpt api 推荐好用白嫖的api 项目演示 项目部署 浏览器访问casaos 添加软件原添加 https://gitee.com/langlangy_1/CasaOS-AppStore-LangLangy/raw/master/chatmjd.zip 安装此软件 等待安装 安装后再桌面设置…

开发Web3 ETF的技术难点

开发Web3 ETF&#xff08;Exchange-Traded Fund&#xff0c;交易所交易基金&#xff09;软件时&#xff0c;需要注意以下几个关键问题。开发Web3 ETF软件是一个复杂的过程&#xff0c;涉及到金融、法律和技术多个领域的专业知识。开发团队需要综合考虑上述问题&#xff0c;以确…

和comate一起,用JavaScript实现一个简易版五子棋小游戏

前言 五子棋起源于中国&#xff0c;是全国智力运动会竞技项目之一&#xff0c;是一种两人对弈的纯策略型棋类游戏。双方分别使用黑白两色的棋子&#xff0c;下在棋盘直线与横线的交叉点上&#xff0c;先形成五子连珠者获胜。 这次和Baidu Comate智能代码助手共同完成这个小游戏…