Linux --TCP协议实现简单的网络通信(中英翻译)

一、什么是TCP协议

1.1 、TCP是传输层的协议,TCP需要连接,TCP是一种可靠性传输协议,TCP是面向字节流的传输协议;

二、TCPserver端的搭建

2.1、我们最终好实现的效果是

客户端在任何时候都能连接到服务端,然后向服务端发送请求,服务端回应客户端的请求;且当客户端向服务端发送请求连接失败时(断网或者服务器故障),会有一个重新连接的状态,在某个时间段内可以不停尝试重新连接;在这里我们把请求和回应请求设计为一个英文翻译,即客户端发送英文,服务端翻译英文并把结果返回给客户端;

2.2 TCPserver第一步创建套接字
2.3、 准备数据
2.4、绑定
2.5、监听

以上初始化工作完成,接下来服务器运行:

2.6、获取连接
2.7、让线程池取获取并处理任务

自己实现的简单版的线程池:

cpp 复制代码
#pragma once
// 线程池类实现
#include <iostream>
#include <vector>
#include <queue>
#include <unistd.h>
const static int defaultNum = 5;
struct ThreadInfo
{
    pthread_t tid_;
    std::string threadname_;
};
template <class T>
class threadpool
{
public:
    void lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void wakeup() // 唤醒
    {
        pthread_cond_signal(&cond_);
    }
    void sleep_t() // 休眠(到条件变量的等待队列里面等)
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool isempty()
    {
        return task_queue.empty();
    }
    std::string getThreadname(pthread_t tid)
    {
        for (auto &e : threads_)
        {
            if (e.tid_ == tid)
                return e.threadname_;
        }
        return "None";
    }

public:
    void push(const T &task)
    {
        lock();
        task_queue.push(task);
        wakeup();
        unlock();
    }
    static void *HandlerTask(void *args) // 类内成员函数有this指针,会参数不匹配,加static修饰就没有了
    {
        threadpool<T> *tp = static_cast<threadpool<T> *>(args);
        std::string name=tp->getThreadname(pthread_self());
        while (true)
        {
            tp->lock();
            // 1.获取任务
            while (tp->isempty()) // 防止伪唤醒
            {
                tp->sleep_t();
            }
            T t =tp->pop();
            tp->unlock();
            // 2.消费任务
            t();
        }
    }
    void start()
    {
        int threadcout = threads_.size();
        for (int i = 0; i < threadcout; i++)
        {

            // 创建的同时把线程数组里的数据初始化好
            threads_[i].threadname_ = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid_), nullptr, HandlerTask, this); // static成员函数不能访问成员变量,只能通过类对象访问
        }
    }

    T pop()
    {
        T out = task_queue.front();
        task_queue.pop();
        return out;
    }
    static threadpool<T> *GetInstacne() // 获取实例
    {                                   // 如果有多个线程同时进来获取实例呢?如果不上锁会导致对象被实例化多份出来
        if (tp_ == nullptr)             // 后面进来的大部分线程都会判断失败,不需要继续往获取锁
        {
            // 走到这里的只有一小部分线程
            pthread_mutex_lock(&lock_); // 上锁
            if (tp_ == nullptr)         // 只有第一次获取单例的线程需要进行条件判断,后续线程进来判断都失败
            {
                tp_ = new threadpool<T>();
            }
            pthread_mutex_unlock(&lock_); // 解锁
        }
        return tp_;
    }

private:
    std::vector<ThreadInfo> threads_; // 存放线程信息(通过里面的tid找到线程)
    std::queue<T> task_queue;              // 存放任务的队列
    pthread_mutex_t mutex_;           // 访问队列的时候需要上锁
    pthread_cond_t cond_;             // 如果没有任务就去里面等
    threadpool(const T &) = delete;   // 把一切可构造出第二个对象的成员函数禁掉
    const threadpool<T> &operator=(const threadpool<T> &) = delete;
    threadpool(int threadnum = defaultNum)
        : threads_(threadnum)
    {
        // 初始化锁跟条件变量
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~threadpool()
    {
        // 销毁锁和条件变量
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    static threadpool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T> // 静态类成员类外定义
threadpool<T> *threadpool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t threadpool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER; // 如果用全局锁需要对锁进行初始化跟销毁
2.8 把Task任务编写一下,即收发任务
cpp 复制代码
#pragma once
#include <string>
#include"Init.hpp"
Init init;
const int buffersize = 1024;
class Task
{
private:
  int sock_fd;
  std::string clientip_;
  uint16_t clientport_;

public:
  Task(const int &fd, const std::string &ip, const uint16_t &port)
      : sock_fd(fd), clientip_(ip), clientport_(port)
  {
  }
  void operator()()
  {
    Run();
  }
  void Run()
  {

    char buffer[buffersize];
    ssize_t n = read(sock_fd, buffer, sizeof(buffer));
    if (n > 0)
    {
      buffer[n] = '\0';
      std::string message = buffer; // 获取client的信息
      std::string echo_message = init.translation(message);
      // 2.写
    // 把获取到的信息进行转换,输出client想要的信息
      ssize_t w = write(sock_fd, echo_message.c_str(), echo_message.size());
      if (w < 0)
      {
        lg(WARNING, "Write fail!!");
      }
      else if (w == 0)
      {
        lg(WARNING, "Read close!!");
      }
    }
    else if (n < 0)
    {
      lg(INFO, "Read fail!! client ip is:%s, clien port is:%d",clientip_.c_str(),clientport_);
    }
    else
    {
      lg(WARNING, "Client close!! fd:%d, client ip: %s, client port: %d",sock_fd,clientip_.c_str(),clientport_);
    }
    close(sock_fd);
  }
  ~Task()
  {
  }
};
2.9、server的main入口函数
cpp 复制代码
#include "TcpServer.hpp"
#include <memory>
void Usage(std::string proc)
{
    std::cout<<"\n\rUsage:"<<proc<<" serverport[1024+]"<<std::endl;
}
//server serverport
int main(int argc,char * argv[])
{
  
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port=std::stoi(argv[1]);
     lg.Enable(CLASSFILE);

    std::unique_ptr<TcpServer> ptr(new TcpServer(port));
    ptr->Init();
    ptr->Start();
    return 0;
}

三、客户端的编写

3.1
cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#define SIZE 1024
void Usage(std::string proc)
{
    std::cout << "\n\rUsage:" << proc << " serverip serverport" << std::endl;
}
// client serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    // 2.准备数据
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server)); // 初始化结构体
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);
    // 3.OS自动绑定在第一次连接成功时
    // 4.发起连接
    while (true)
    {
        // 1.创建套接字
        int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_fd < 0)
        {
            return -1;
        }
        int cnt = 5;
        bool isconnect = false;
        do
        {
            ssize_t c = connect(sock_fd, (sockaddr *)&server, sizeof(server));
            if (c<0)
            {
                cnt--;
                isconnect = true;
                std::cout << "Is connect!! time: " << cnt << std::endl;
                sleep(2);
            }
            else
            {
                break;
            }
        } while (cnt && isconnect);
        if (cnt == 0)
        {
            std::cout << "user offline!!" << std::endl;
            break;
        }
        // 走到这里连接成功
        // 1.写
        std::cout << "Please enter@ " << std::endl;
        std::string line;
        std::getline(std::cin, line);
        ssize_t w = write(sock_fd, line.c_str(), line.size());
        if(w<0)
        {
            std::cout<<"Write fail!!"<<std::endl;
        }
    
        // 2.读
        char buffer[SIZE];
        ssize_t r = read(sock_fd, buffer, sizeof(buffer));
        if (r > 0)
        {
            buffer[r] = '\0';
            std::cout << buffer << std::endl;
        }
        close(sock_fd);
    }
    return 0;
}
3.2、在server中加入守护进程

守护进程的实现:

cpp 复制代码
#pragma once
#include <unistd.h>
#include<cstdlib>
#include<signal.h>
#include<string>
   #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

std:: string nullfile="/dev/null";
void Deamon(const std::string& cwd="")
{
       //1.忽略其他异常信号
    signal(SIGSTOP,SIG_IGN);
    signal(SIGPIPE,SIG_IGN);
    signal(SIGCLD,SIG_IGN);
 
 //2.自成会话
    if(fork()>0)exit(0);
    setsid();

    //3.更改调用进程的工作目录
    if(cwd.c_str()!="")
    {
        chdir(cwd.c_str());
    }

    //4.关闭标准输入输出错误流
    //打开垃圾桶
    int fd=open(nullfile.c_str(),O_RDONLY);//只读方式打开
    if(fd>0)
    {
    //重定向到垃圾桶
    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    }

}
3.3、翻译服务的实现
cpp 复制代码
#pragma oce
#include<unordered_map>
#include<fstream>
#include"log.hpp"
extern Log lg;
std::string filename="dict.txt";
std::string separator=":";
static bool split(const std::string&line,std::string*ptr1,std::string*ptr2)
{
    auto pos=line.find(separator);
    if(pos!=std::string::npos)
    {
        *ptr1=line.substr(0,pos);
        *ptr2=line.substr(pos+1);
        return true;
    }
    return false;
}
class Init
{
    private:
    std::unordered_map<std::string,std::string> dict_;
    public:
    Init()
    {
        std::ifstream in(filename);
        if(!in.is_open())
        {
            lg(FATAL,"Ifstream open fail!!");
            exit(1);}
        std::string line;
        while(getline(in,line))
        {
            std::string ptr1,ptr2;
            split(line,&ptr1,&ptr2);
            dict_.insert({ptr1,ptr2});
        }
        in.close();
    }
    std::string translation(std::string&word)
    {
        auto iter=dict_.find(word);
        if(iter!=dict_.end())
        {
            return iter->second;
        }
        return "Unknow";
    }
    ~Init()
    {

    }
};

其中这个字典集随便找的一些内容:

3.4makefile 编译运行
cpp 复制代码
.PHONY:all
all: tcpclient tcpserver
tcpclient:TcpClient.cpp
	g++ -o $@ $^ -std=c++11
tcpserver:Main.cpp
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f tcpserver tcpclient

服务运行只要运行一次起来后有就会7*24小时在后台跑着:

相关推荐
律宏阔4 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔4 小时前
Windows 禁用联想笔记本所有 Lenovo 后台服务
windows
律宏阔4 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴4 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千4 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei375 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei375 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei375 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
codigger5 小时前
ObjectSense:一门千行内核、把可靠性写进骨子里的面向对象脚本语言
开发语言·编程·编程语言
GeW5 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux