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

基于tcp协议的网络通信(基础echo版.多进程版,多线程版,线程池版),telnet命令

目录

基础版

思路

辅助函数 

服务端 

代码

运行情况 -- telnet +ip +端口号

传输的数据为什么没有转换格式

客户端

思路

代码

多进程版

引入

问题 

解决

注意点 

服务端

代码

运行情况

进程池版(简单介绍)

多线程版

引入

问题+解决

注意点

服务端

代码

运行情况 

线程池版

引入

过程介绍

服务端

代码 

task.hpp

thread_pool.hpp

helper.hpp

运行情况


基础版

思路

和udp不同的是,tcp是面向字节流,面向连接的协议

  • 所以要注意socket建立时的传入的数据类型 -- AF_STREAM

它需要客户端主动先和服务端建立连接,而不是直接发送数据

  • 那么,客户端就需要调用connect函数
  • 相应的,服务端需要一直处于监听(等待连接到来)的状态 -- listen函数,也需要一个接收连接的函数 -- accept(服务端会卡在accept中,直到有连接请求到来)

tcp协议当然也需要创建套接字并与自己的地址信息绑定 -- socket()+bind()

但是,tcp里会有两个不同的套接字文件,这两个的用处不一样

  • 在tcp协议中,服务端里被socket创建,被bind绑定,被accept使用的套接字a,只是用来获取连接的
  • 之后的io操作,由accept创建的新套接字b完成(也就是accept返回的fd)
  • 就像在饭店,有人负责拉客(门口站着的那种),这就是a的工作,所以可以命名为listen_socket(用于和b区分,a一般只有一个,当然也可以有多个)

  • 有人负责提供服务(服务员),这就是b的工作(可以有多个)

注意,每来一个新连接,就会有一个新的fd被返回

  • 即使连接获取失败,也不能说明什么,也许是对方切断了连接
  • 它不像socket那样,获取失败就说明哪里有问题;连接失败是可以被接受的
  • 所以,accept失败后不需要退出程序
  • 难道拉客的时候失败了你就辞职了吗? 不会的,你只会继续下一次的拉客

当客户端与服务端建立好连接后,就可以开始通信了

辅助函数 

获取时间,为客户端封装标识符

#pragma once#include <string>
#include <cstring>enum
{SOCK_ERROR = 1,BIND_ERROR,LISTEN_ERROR,CONNECT_ERROR
};std::string get_time()
{time_t t = time(nullptr);struct tm *ctime = localtime(&t);char time_stamp[1024];snprintf(time_stamp, sizeof(time_stamp), "[%d-%d-%d %d:%d:%d]:",ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);return time_stamp;
}std::string generate_id(const std::string ip, const uint16_t port)
{return "[" + ip + ":" + std::to_string(port) + "]";
}

打印日志

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define INFO 0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4 // 致命的错误#define SIZE 1024class Log
{
public:Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);printf("%s\n", logtxt);}~Log(){}private:std::string levelToString(int level){switch (level){case INFO:return "INFO";case DEBUG:return "DEBUG";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "NONE";}}
};Log lg;

服务端 

代码

#include <iostream>
#include <string>#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>#include "Log.hpp"
#include "helper.hpp"const int backlog = 5;
const int buff_size = 1024;class tcp_server
{
public:tcp_server(const uint16_t port = 8080, const std::string ip = "0.0.0.0"): ip_(ip), port_(port), listen_sockfd_(-1){}void run(){init();sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);memset(&client_addr, 0, client_len);lg(INFO, "init success");while (true){int sockfd = accept(listen_sockfd_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);if (sockfd < 0){continue;}char client_ip[32];inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));int client_port = ntohs(client_addr.sin_port);lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip, client_port);echo(sockfd, client_ip, client_port);close(sockfd);}}~tcp_server() {}private:void init(){listen_sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (listen_sockfd_ < 0){lg(FATAL, "socket create error, sockfd : %d,%s", listen_sockfd_, strerror(errno));exit(SOCK_ERROR);}lg(INFO, "socket create success, sockfd : %d", listen_sockfd_);struct sockaddr_in *addr = new sockaddr_in;memset(addr, 0, sizeof(*addr));addr->sin_family = AF_INET;inet_pton(AF_INET, ip_.c_str(), &(addr->sin_addr));addr->sin_port = htons(port_);int t = bind(listen_sockfd_, reinterpret_cast<struct sockaddr *>(addr), sizeof(*addr));if (t < 0){lg(FATAL, "bind error, sockfd : %d,%s", listen_sockfd_, strerror(errno));exit(BIND_ERROR);}lg(INFO, "bind success, sockfd : %d", listen_sockfd_);if (listen(listen_sockfd_, backlog) < 0){lg(FATAL, "listen error, sockfd : %d,%s", listen_sockfd_, strerror(errno));exit(LISTEN_ERROR);}lg(INFO, "listen success, sockfd : %d", listen_sockfd_);delete addr;}void echo(int fd, const char* ip, const uint16_t port){char buffer[buff_size];memset(buffer, 0, sizeof(buffer));while (true){int n = read(fd, buffer, sizeof(buffer) - 1);if (n < 0){lg(ERROR, "%s:%d read error, %s", ip, port, strerror(errno));break;}else if (n == 0) //如果返回0,说明对端关闭了连接{lg(INFO, "%s:%d quit", ip, port);break;}else{buffer[n] = 0;std::string res = process_info(buffer, ip, port);write(fd, res.c_str(), res.size());}}}std::string process_info(const std::string &info, const std::string ip, const uint16_t port){std::string time_stamp = get_time();std::string id = generate_id(ip, port);std::string res = id + time_stamp + info;return res;}private:int listen_sockfd_;uint16_t port_;std::string ip_;
};

运行情况 -- telnet +ip +端口号

当我们只有服务端,且想要查看服务端是否处于监听状态,就可以用这个命令远程连接指定服务

这样我们就可以将其作为客户端,与服务端通信了:

当我们想要退出时,输入ctrl+],再输入quit命令即可:

传输的数据为什么没有转换格式

我们一直都对ip地址和端口号进行转换,那传输的数据呢?

无论是之前的udp协议,还是今天写的tcp协议,都是直接将字符串传进去了,为什么能这样呢?

  • 因为收发数据的函数会自动帮我们进行转换
  • 而ip地址和端口号是被存到系统级的结构体里的,它规定的数据类型就是那样
  • 所以我们在初始化时必须转成相应类型 ; 当我们要读取时,也要转换成适合显示的类型

客户端

思路

和使用udp协议一样,客户端也需要套接字(因为服务端创建了套接字,他们之间通信的基础就是套接字)

依然也不需要手动绑定,由os为我们随机分配端口号并绑定(因为客户端的端口号不重要,只需要保证客户端的唯一性即可)

那什么时候os为我们绑定呢?

  • udp是在客户端第一次发送消息时绑定,但tcp必须要先连接成功,才能发送消息
  • 而服务端有等待连接的函数,那么客户端肯定也有建立连接的函数 -- connect()
  • 也就是在客户端主动向服务端建立连接时,os调用bind,将客户端的套接字创建好 -- 这是建立连接的前提
  • 既然要主动建立连接,客户端就得提前知道服务端的ip和端口号(和udp里,主动向服务端发送消息一样)
  • 所以,这些信息我们要么在代码里写死,要么以命令行的形式传进去

连接成功后,客户端就可以开始发送数据了

发送完,等待服务端的响应数据

代码

#include <iostream>
#include <string>#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>#include "Log.hpp"
#include "helper.hpp"class tcp_client
{
public:tcp_client(const uint16_t port = 8080, const std::string ip = "47.108.135.233"): sockfd_(-1), port_(port), ip_(ip){}~tcp_client() {}void run(){// struct sockaddr_in *server_addr = init();sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(FATAL, "socket create error, sockfd : %d,%s", sockfd_, strerror(errno));exit(SOCK_ERROR);}lg(INFO, "socket create success, sockfd : %d", sockfd_);struct sockaddr_in *server_addr = new sockaddr_in;memset(server_addr, 0, sizeof(*server_addr));server_addr->sin_family = AF_INET;inet_pton(AF_INET, ip_.c_str(), &(server_addr->sin_addr));server_addr->sin_port = htons(port_);int ret = connect(sockfd_, reinterpret_cast<struct sockaddr *>(server_addr), sizeof(*server_addr));if (ret < 0){std::cout << "connect fail" << std::endl;exit(CONNECT_ERROR);}while (true){std::cout << "please enter:" << std::endl;std::string buffer;std::getline(std::cin, buffer);write(sockfd_, buffer.c_str(), buffer.size());char info[1024];memset(info, 0, sizeof(info));int n = read(sockfd_, info, sizeof(info) - 1);if (n > 0){info[n] = 0;std::cout << info << std::endl;}else{break;}}}private:struct sockaddr_in *init(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(FATAL, "socket create error, sockfd : %d,%s", sockfd_, strerror(errno));exit(SOCK_ERROR);}lg(INFO, "socket create success, sockfd : %d", sockfd_);struct sockaddr_in *addr = new sockaddr_in;memset(addr, 0, sizeof(*addr));addr->sin_family = AF_INET;inet_pton(AF_INET, ip_.c_str(), &(addr->sin_addr));addr->sin_port = htons(port_);return addr;}private:int sockfd_;uint16_t port_;std::string ip_;
};

因为tcp是在建立好连接的基础上通信的,如果通信过程中,连接断掉了该怎么办?

就和游戏中有时候会提示:断线重连中(一般是我们自己的网络出现波动/断掉了),我们需要重新调用客户端中的connect函数

当我们在游戏里重新连接上时,有些游戏会将已经进行的游戏内容快速给你播放一遍

这就说明该游戏会将游戏数据一直维护着,重连后将数据全部推送给你,然后让你继续游玩

多进程版

引入

如果有多个客户端运行的话,我们的代码无法支持并发运行

  • 因为服务端是单进程,所以只能一直循环为一个客户端服务
  • 直到这个客户端退出后,才会退出echo函数(里面是while循环),才会重新获取连接(也就是回到while循环的一开始):
  • 让后启动的客户端只能干等着,这显然是不合理的
  • 所以我们需要将服务端改为多进程版本的 -- 当有新客户端连接时,就创建出新的子进程,让子进程去服务,主进程去监听是否有新的连接

问题 

父进程等待子进程是必要的

  • 不然就会形成僵尸进程

又因为父进程不会退出(他负责监听是否有进程连接,连接了就派进程去服务)

  • 所以让子进程变成孤儿进程也是不行的

并且,它也不可以阻塞在等待函数里(他有自己的任务)

  • 不然和之前的代码有什么区别呢

所以,该怎么办呢?

解决

选择非阻塞式等待(也就是轮询)是可以的

但我们还有其他方法:

  • 先明确我们的前提 -- 不能让阻塞式等待的父进程卡在waitpid,也不能托孤->子进程最好立即退出->让其他进程去帮子进程执行
  • 也就是在子进程内部再次fork,让孙子进程实际提供服务->因为子进程的退出,孙子进程成为了孤儿进程,由os释放其资源
  • 这样父进程就可以立即等待到子进程,也就会进入下一次的循环去进行连接了
  • 子进程和孙子进程都不会变成僵尸进程
  • 皆大欢喜~

也可以手动忽略子进程发出的sigchld信号

  • 这样父进程也不需要等待了,由os接手释放资源

注意点 

注意,子进程是去执行io操作的,所以listen_sockfd就没有用了(它只管连接)

  • 那么子进程最好关闭它,防止误操作
  • 子进程关闭了它,并不会使指向的文件真正关闭 -- 还有父进程使用它(os在管理它时,会有一个引用计数字段嘟,只有计数=0时,才会关闭文件)

同理,父进程将io操作交给了子进程去处理,那么用于io的sockfd就没用了

  • 需要关闭它

这样,这两个套接字分别都只有一个进程去使用了

服务端

代码

    void run(){init();sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);memset(&client_addr, 0, client_len);lg(INFO, "init success");while (true){int sockfd = accept(listen_sockfd_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);if (sockfd < 0){continue;}char client_ip[32];inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));int client_port = ntohs(client_addr.sin_port);lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip, client_port);// 单进程版// echo(sockfd, client_ip, client_port);// close(sockfd);// 多进程版 -- 孙子进程版int ret = fork();if (ret == 0){close(listen_sockfd_);int t = fork();if (t == 0){echo(sockfd, client_ip, client_port);}exit(0);}close(sockfd);waitpid(ret, nullptr, 0);// 多进程版 -- 忽略信号版int ret = fork();if (ret == 0){close(listen_sockfd_);echo(sockfd, client_ip, client_port);exit(0);}close(sockfd);signal(SIGCHLD, SIG_IGN);}}

其他的都没有变

运行情况

可以看到,当我们运行了两个客户端时,就有对应的孙子进程被创建,且都变成了孤儿进程,被init进程抚养:

或者是忽略信号的方法,同时运行两个客户端,且其中一个退出后,可以看到并没有形成僵尸进程:

进程池版(简单介绍)

也可以提前创建好进程,每个进程都去执行while循环(从获取连接到提供io服务),这样也可以并发式地让多个客户端同时与服务端通信

  • 那么他们每个进程都需要通过accept获取网络文件,就存在着竞争关系,也就需要加锁(不然可能会出现多个进程打开同一个文件的情况)

多线程版

引入

但是,这样写出的代码需要创建出很多子进程

  • 不仅可能出现一个客户端对应一个子进程的情况
  • 而且创建进程的成本很高,很占据资源

实际上我们只是需要有人去执行任务就行

  • 所以多线程是我们的最佳选择
  • 它是cpu调度的基本单位,可以最低成本地实现我们的需求

问题+解决

但是线程也需要主线程去等待耶,那主线程还是会卡在join那里,直到等待到线程完成任务,这不符合我们的预期

  • 所以,我们让副线程与主线程分离 -- detach(之前一直没用过这个接口,但现在有它的用武之地了)
  • 线程退出时会自动释放资源,而不需要等待其他线程调用pthread_join函数

注意点

和父子进程不同的是,多个线程共享所在进程的文件描述符表

  • 注意是完全共享,而不是父子进程之间的写时拷贝模式
  • 所以不需要关闭
  • 一旦其中某个线程关闭了它,其他线程也就用不了了

因为我们要在类内部创建线程

  • 那么线程执行函数就得是static类型的

但这样就没有this指针了

  • 所以需要定义一个类型,将this指针封装进去
  • 也包括echo函数需要用到的数据(这样在函数内部强转指针后,就可以直接使用了)

服务端

代码

class tcp_server; //提前声明一下tcp_server 是个类类型,不然编译过不去struct p_data
{int fd_;uint16_t port_;std::string ip_;tcp_server *it_;
};void run(){init();sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);memset(&client_addr, 0, client_len);lg(INFO, "init success");while (true){int sockfd = accept(listen_sockfd_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);if (sockfd < 0){continue;}char client_ip[32];inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));uint16_t client_port = ntohs(client_addr.sin_port);lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip, client_port);// 单进程版// echo(sockfd, client_ip, client_port);// close(sockfd);// 多进程版 -- 孙子进程版// int ret = fork();// if (ret == 0)// {//     close(listen_sockfd_);//     int t = fork();//     if (t == 0)//     {//         echo(sockfd, client_ip, client_port);//     }//     exit(0);// }// close(sockfd);// waitpid(ret, nullptr, 0);// 多进程版 -- 忽略信号版// int ret = fork();// if (ret == 0)// {//     close(listen_sockfd_);//     echo(sockfd, client_ip, client_port);//     exit(0);// }// close(sockfd);// signal(SIGCHLD, SIG_IGN);// 多线程版pthread_t tid = 0;p_data *p = new p_data({sockfd, client_port, client_ip, this});pthread_create(&tid, nullptr, entrance, reinterpret_cast<void *>(p));}}static void *entrance(void *args){pthread_detach(pthread_self());p_data *p = reinterpret_cast<p_data *>(args);tcp_server *it = p->it_;it->echo(p->fd_, (p->ip_).c_str(), p->port_);delete p;return nullptr;}

运行情况 

当我们运行起两个客户端后,就可以看见有两个线程创建出来了:

线程池版

引入

虽然比起进程版本的来说,多线程的成本变小了,但仍然存在客户端和线程一对一的弊端

  • 访问量较大时,服务端还是可能带不起来
  • 而且是在客户端已经到来时才创建线程,效率比较低
  • 所以,线程池就可以使用了(之前写过,这里就直接使用了) -- 线程池(图解,本质,模拟实现代码),添加单例模式(懒汉思路+代码)-CSDN博客

过程介绍

首先回顾一下线程池的内容:

  • 提前创建出一定数量的线程,主线程push任务进队列
  • 如果有任务,空闲的线程去竞争任务,拿到任务的线程(pop)去执行任务
  • 如果没有任务,线程就等待任务的到来

在当时的线程池里,我们的重点在于如何放/取任务,但只有这些并不是一个完整的cp模型,在这里就可以填补上这个空缺了

  • 也就是任务的来源和后续的处理
  • 来源 : 客户端的访问
  • 处理 : 将消息封装后回显,然后交回给客户端(也就是我们的echo函数)
  • 这样,线程之间竞争任务就没那么激烈(因为会有部分线程陷于处理任务的状态)

并且,这里设计成每个线程只为客户端提供一次服务

  • 当然,这是要看场景的,这里只是一个echo回显的功能,短时/长时服务都可以
  • 短时服务可以减少服务器的压力
  • 而像shell那种需要长时间的保持,就不能这么写了,Shell 进程会等待用户的输入(有时候也会在等待期间处理其他后台任务:下载文件等)

也就是 -- 只在客户端需要io时,才分配线程去处理,并且在处理完成后,就断开与客户端的连接,当客户端需要io时再连接

服务端

代码 

    void run_pthread_pool(){// 初始化init();thread_pool<Task> *tp = thread_pool<Task>::get_instance();tp->init();sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);memset(&client_addr, 0, client_len);lg(INFO, "init success");while (true){int sockfd = accept(listen_sockfd_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);if (sockfd < 0){continue;}char client_ip[32];inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));uint16_t client_port = ntohs(client_addr.sin_port);lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip, client_port);Task t(sockfd,client_ip,client_port);tp->push(t);}}
task.hpp
#pragma once#include <iostream>
#include <string>
#include <stdio.h>#include "helper.hpp"
// 这里的任务是,服务端在收到客户端的连接后的后续工作class Task
{
public:Task() {} // 方便只是为了接收传参而定义一个对象Task(int fd, const char ip[32], const uint16_t port): sockfd_(fd), ip_(ip), port_(port){}void operator()(){char buffer[buff_size];memset(buffer, 0, sizeof(buffer));while (true){int n = read(sockfd_, buffer, sizeof(buffer) - 1);if (n < 0){lg(ERROR, "%s:%d read error, %s", ip_.c_str(), port_, strerror(errno));break;}else if (n == 0){lg(INFO, "%s:%d quit", ip_.c_str(), port_);break;}else{buffer[n] = 0;std::string res = process_info(buffer, ip_, port_);write(sockfd_, res.c_str(), res.size());}}}private:int sockfd_;uint16_t port_;std::string ip_;
};
thread_pool.hpp
#include <pthread.h>
#include <vector>
#include <queue>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <semaphore.h>
#include <iostream>struct thread
{pthread_t tid_;std::string name_;
};template <class T>
class thread_pool
{
private:void lock(){pthread_mutex_lock(&mutex_);}void unlock(){pthread_mutex_unlock(&mutex_);}void wait(){pthread_cond_wait(&cond_, &mutex_);}void signal(){pthread_cond_signal(&cond_);}T pop(){T t = task_.front();task_.pop();return t;}bool is_empty(){return task_.size() == 0;}static void *entry(void *args) // 类成员会有this参数,但入口函数不允许有多余参数{thread_pool<T> *tp = static_cast<thread_pool<T> *>(args); // this指针,用于拿到成员变量/函数while (true){tp->lock();while (tp->is_empty()){tp->wait();}T t = tp->pop();tp->unlock();t();}return nullptr;}public:static thread_pool<T> *get_instance(int num = 5){// 如果这样写,虽然保证了安全,但会在创建对象后,线程依然线性运行//  pthread_mutex_lock(&single_mutex_);//  if (myself_ == nullptr)//  {//      myself_ = new thread_pool<T>(num);//  }//  pthread_mutex_unlock(&single_mutex_);if (myself_ == nullptr) // 再加一层判断,就可以提高效率{pthread_mutex_lock(&single_mutex_);if (myself_ == nullptr){myself_ = new thread_pool<T>(num);//std::cout << "get instance success" << std::endl;}pthread_mutex_unlock(&single_mutex_);}return myself_;}void init(){for (size_t i = 0; i < num_; ++i){pthread_create(&(threads_[i].tid_), nullptr, entry, this);pthread_detach(threads_[i].tid_);}}void push(const T data){lock();task_.push(data);signal(); // 放在锁内,确保只有当前线程执行唤醒操作,不然可能会有多次操作unlock();}private:thread_pool(int num = 5): num_(num), threads_(num){pthread_cond_init(&cond_, nullptr);pthread_mutex_init(&mutex_, nullptr);}~thread_pool(){pthread_cond_destroy(&cond_);pthread_mutex_destroy(&mutex_);}private:std::vector<thread> threads_;std::queue<T> task_;int num_;pthread_cond_t cond_;pthread_mutex_t mutex_;static thread_pool<T> *myself_; // 每次外部想要线程池对象时,返回的都是这一个(只有静态成员变量,才能保证一个类只有一个)static pthread_mutex_t single_mutex_;
};template <class T>
thread_pool<T> *thread_pool<T>::myself_ = nullptr;template <class T>
pthread_mutex_t thread_pool<T>::single_mutex_ = PTHREAD_MUTEX_INITIALIZER;

因为task.hpp里面需要用到process_info函数,处理我们收到的信息,所以我将这个函数从服务端类内挪到了helper.hpp里(反正这个函数不需要用到类内成员)

helper.hpp
#pragma once#include <string>
#include <cstring>enum
{SOCK_ERROR = 1,BIND_ERROR,LISTEN_ERROR,CONNECT_ERROR
};const int buff_size = 1024;std::string get_time()
{time_t t = time(nullptr);struct tm *ctime = localtime(&t);char time_stamp[1024];snprintf(time_stamp, sizeof(time_stamp), "[%d-%d-%d %d:%d:%d]:",ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);return time_stamp;
}std::string generate_id(const std::string ip, const uint16_t port)
{return "[" + ip + ":" + std::to_string(port) + "]";
}std::string process_info(const std::string &info, const std::string ip, const uint16_t port)
{std::string time_stamp = get_time();std::string id = generate_id(ip, port);std::string res = id + time_stamp + info;return res;
}

运行情况

服务端启动起来后,就有五个新线程被创建出来(因为我们的默认线程数量是5):

相关文章:

基于tcp协议的网络通信(基础echo版.多进程版,多线程版,线程池版),telnet命令

目录 基础版 思路 辅助函数 服务端 代码 运行情况 -- telnet ip 端口号 传输的数据为什么没有转换格式 客户端 思路 代码 多进程版 引入 问题 解决 注意点 服务端 代码 运行情况 进程池版(简单介绍) 多线程版 引入 问题解决 注意点 服务端 代码 …...

Ubuntu20系统安装完后没有WIFI

Ubuntu20系统安装完后没有WIFI 查看后发现是缺少网卡&#xff0c;经过查询之后&#xff0c;发现是HRex39/rtl8852be 然后查询了Kernel版本 Check the Kernel Version in Linux $ uname -srm Linux 5.15.0-67-generic x86_64然后进行下载安装 Build(for kernel < 5.18) …...

计算机视觉——目标检测(R-CNN、Fast R-CNN、Faster R-CNN )

前言、相关知识 1.闭集和开集 开集&#xff1a;识别训练集不存在的样本类别。闭集&#xff1a;识别训练集已知的样本类别。 2.多模态信息融合 文本和图像&#xff0c;文本的语义信息映射成词向量&#xff0c;形成词典&#xff0c;嵌入到n维空间。 图片内容信息提取特征&…...

log4j2.xml配置文件不生效

问题 使用springboot配置log4j2&#xff0c;添加了依赖并排除默认的logging依赖&#xff0c;配置了log4j2.xml文件&#xff0c;放在scr目录下&#xff0c;运行可以在控制台输出日志&#xff0c;但不受配置文件影响 解决 配置文件log4j2.xml放在resources目录下生效...

QT信号与槽实现方式

1、第一种实现方式 在QT开发工具UI界面先拖入按钮&#xff0c;然后鼠标右键拖入按钮&#xff0c;点击选中槽&#xff0c;在页面选着需要的信号&#xff0c;然后OK&#xff0c;随即将会跳转到类的.cpp文件&#xff0c;&#xff08;这种UI代码结合的方式&#xff0c;会自动去绑定…...

Yarn面试重点

文章目录 1. 简述Yarn集群的架构2. Yarn 的任务提交流程是怎样的&#xff1f;3. yarn的资源调度的三种模型 1. 简述Yarn集群的架构 YARN&#xff08;Yet Another Resource Negotiator&#xff09;是Hadoop 2.x引入的资源管理器&#xff0c;用于管理Hadoop集群中的资源和作业调…...

高速口光口通信

1.通过transceiver ip 设置好硬件连接配置 2.open example 用自己的模块替换掉tx和rx数据模块 3.大小端问题—— 4.配置gt收发器的rx的k码时候需要设置anybyte便于高效率接收。 5.开发数据产生模块和接收校验模块都需要使用TXUSRCLK2,但是TXUSRCLK线速度/内部数据位宽。——…...

python--剑指offer--15. 二进制中1的个数

编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff08;如 Java&…...

uniapp h5 部署

uniapp 配置 服务器文件路径 打包文件结构 //nginx 配置 server {listen 8300;server_name bfqcwebsiteapp;charset utf-8;#允许跨域请求的域&#xff0c;* 代表所有add_header Access-Control-Allow-Origin *;#允许带上cookie请求add_header Access-Control-Allow-C…...

排序算法:快速排序(递归)

文章目录 一、创始人托尼霍尔的快速排序二、挖坑法三、前后指针法 所属专栏:C初阶 引言&#xff1a;这里所说的快速排序有三种&#xff0c;第一种是霍尔大佬自创的&#xff0c;还有一种叫做挖坑法&#xff0c;另外一种叫前后指针法 一、创始人托尼霍尔的快速排序 1.这里我们先…...

蓝桥杯每日一题(BFS)

1562 微博转发 开始思路错误点&#xff1a;在用拉链法保存关注信息的时候&#xff0c;因为要看一个用户发的有多少转发的&#xff0c;所以要以用户为坑位&#xff0c;所有关注这个坑位的用户为链表。&#xff08;开始弄反了&#xff09; e数组存某个用户的idx&#xff0c;ne是…...

【C语言】linux内核pci_save_state

一、中文注释 //include\linux\pci.h /* 电源管理相关的例程 */ int pci_save_state(struct pci_dev *dev);//drivers\pci\pci.c /*** pci_save_state - 在挂起前保存PCI设备的配置空间* dev: - 我们正在处理的PCI设备*/ int pci_save_state(struct pci_dev *dev) {int i;/* X…...

轻松打造完美原型:9款在线工具推荐

早年&#xff0c;UI设计师选择的工具有限&#xff0c;功能相对单一&#xff0c;大多数在线原型设计工具都是国外的&#xff0c;语言和网络都增加了设计工作的负担。如今&#xff0c;国内外有许多在线原型设计工具&#xff0c;不仅可以在浏览器上使用&#xff0c;而且还具有团队…...

Vue3中Pinia状态管理库学习笔记

pinia的基本使用 <template><div><h2>Home View</h2> <h2>count:{{ counterStore.count }}</h2><h2>count:{{ count }}</h2><button click"increment">count1</button></div> </template>…...

共谋企业出海新篇章纷享销客荣获数字中国企业峰会“卓越成果奖”

3月9日&#xff0c;2024数字中国企业峰会在杭州西湖中维香溢大酒店成功举办&#xff0c;众多数字化领域专家、知名企业 CIO 代表到场。峰会旨在推动数字化转型与创新发展&#xff0c;为企业出海和国际合作搭建交流与合作的平台。本次峰会的颁奖环节&#xff0c;纷享销客凭借其卓…...

【MySQL】group_concat 函数和 locate 函数运用之找到每篇文章的主题

力扣题 1、题目地址 2199. 找到每篇文章的主题 2、模拟表 表&#xff1a;Keywords Column NameTypetopic_idintwordvarchar (topic_id, word) 是该表的主键&#xff08;具有唯一值的列的组合&#xff09;。该表的每一行都包含一个主题的 id 和一个用于表达该主题的词。可…...

RedisCluster集群中的插槽为什么是16384个?

RedisCluster集群中的插槽为什么是16384个&#xff1f; CRC16的算法原理。 1.根据CRC16的标准选择初值CRCIn的值2.将数据的第一个字节与CRCIn高8位异或3.判断最高位&#xff0c;若该位为0左移一位&#xff0c;若为1左移一位再与多项式Hex码异或4.重复3至9位全部移位计算结束5…...

一直出现问题,发现服务器磁盘空间已满导致,腾出服务器磁盘空间命令

要解决服务器磁盘空间已满的问题&#xff0c;你可以按照以下步骤操作&#xff1a; 查看磁盘使用情况&#xff1a;使用df -h&#xff0c; du -s -h ./*命令来查看服务器的磁盘空间使用情况。查找大文件&#xff1a;使用du -a | sort -rn | head -5命令来找出占用空间最大的前5个…...

吴恩达机器学习笔记 二十三 倾斜数据集的误差指标 精确率 召回率 精确率与召回率的平衡 F1分数

如果数据集的正例和反例的比例非常倾斜&#xff0c;常用的错误指标如 准确率(accuracy) 并不好用。此时可以用精确率和召回率。 精确率&#xff08;precision&#xff09;&#xff1a;真阳的样本数/预测为阳的样本数真阳数/&#xff08;真阳假阳&#xff09; 召回率(recall):…...

无人游艇的研发和开发对于多个领域具有重要

无人游艇的研发和开发对于多个领域具有重要性。 首先&#xff0c;无人游艇可以在海上进行各种任务&#xff0c;如海洋科学研究、资源勘探和监测、海洋环境保护等。相比传统的人工操作船只&#xff0c;无人游艇可以长时间在海上工作&#xff0c;可以自动化执行任务&#xff0c;…...

在AI创业热潮下,如何抓住AI赚钱机会,实现人生逆袭

随着人工智能技术的迅猛发展,AI创业热潮正席卷全球。这不仅为科技领域的专业人士提供了无限的商机,也为普通人开辟了全新的赚钱途径。本文将为您揭示在AI创业热潮下,普通人如何抓住AI赚钱机会,实现人生逆袭,同时探讨哪些行业适合应用AI技术。 一、普通人如何抓住AI赚钱机…...

JETSON 配置并跑通 NanoDet

JETSON 配置 NanoDet 文章目录 JETSON 配置 NanoDetNanoDet 介绍源码环境搭建及测试配置 NanoDet 的环境环境配置过程中遇到的问题&#xff1a;环境配置完毕验证 NanoDet NanoDet 介绍 可以参考这个博客&#xff1a;NanoDet&#xff1a;这是个小于4M超轻量目标检测模型 源码 …...

突破编程_C++_C++11新特性(unordered_multimap)

1 概述 std::unordered_multimap 是一个哈希表实现的无序容器&#xff0c;它存储的元素是键值对&#xff0c;并且允许键的重复。这意味着同一个键可以关联多个值。在 std::unordered_multimap 中&#xff0c;元素的插入顺序是不确定的&#xff0c;并且不会因为元素的插入、删除…...

15.WEB渗透测试--Kali Linux(三)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;14.WEB渗透测试--Kali Linux&#xff08;二&#xff09;-CSDN博客 Kali工具使用 3389远…...

Android-Framework pm list packages和pm install返回指定应用信息

一、环境 高通 Android 13 注&#xff1a;Android10 和Android13有些差异&#xff0c;代码位置不变&#xff0c;参照修改即可 二、pm简单介绍 pm工具为包管理&#xff08;package manager&#xff09;的简称 可以使用pm工具来执行应用的安装和查询应用宝的信息、系统权限、…...

CSS

什么是CSS&#xff1f; CSS是一门语言&#xff0c;用于控制网页表现 CSS&#xff08;Cascading Style Sheet&#xff09;&#xff1a;层叠样式表 W3C标准&#xff1a;网页主要由三部分组成 结构&#xff1a;HTML表现&#xff1a;CSS行为&#xff1a;JavaScript CSS导入方式…...

算法详解——选择排序和冒泡排序

一、选择排序 选择排序算法的执行过程是这样的&#xff1a;首先&#xff0c;算法遍历整个列表以确定最小的元素&#xff0c;接着&#xff0c;这个最小的元素被置换到列表的开头&#xff0c;确保它被放置在其应有的有序位置上。接下来&#xff0c;从列表的第二个元素开始&#x…...

图论(蓝桥杯 C++ 题目 代码 注解)

目录 迪杰斯特拉模板&#xff08;用来求一个点出发到其它点的最短距离&#xff09;&#xff1a; 克鲁斯卡尔模板&#xff08;用来求最小生成树&#xff09;&#xff1a; 题目一&#xff08;蓝桥王国&#xff09;&#xff1a; 题目二&#xff08;随机数据下的最短路径&#…...

矩阵起源新一年喜报连连!

新春伊始 矩阵起源向大家分享 一连串好消息 首先&#xff0c;公司创始人兼CEO王龙先生获评“2023深圳创新突出贡献人物“。这一荣誉是对其在推动数据库行业技术创新和产品开发方面所做出的卓越贡献的认可。他的领导力和创新精神不仅引领我司取得了显著的成就&#xff0c;也为…...

牛客——紫魔法师(并查集)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 “サーヴァント、キャスター、Medea。”--紫魔法师 给出一棵仙人掌(每条边最多被包含于一个环&#xff0c;无自环&#xff0c;无重边&#xff0c;保证连通)&#xff0c;要求用最少的…...

关于网站建设相关文章/为企业策划一次网络营销活动

.matchmedia2011年的文章已于2018年更新。 自从CSS取代表格以来&#xff0c; 自适应设计是最令人兴奋的Web布局概念之一。 基础技术使用媒体查询来确定查看设备的类型&#xff0c;宽度&#xff0c;高度&#xff0c;方向&#xff0c;分辨率&#xff0c;长宽比和颜色深度&#x…...

免费行情软件app网站大全下载苹果/2345网址导航设为主页

安装CentOS前&#xff0c;你必须要有CentOS的镜像&#xff1a; 点击前往阿里云下载或 点击前往官网下载 OK&#xff01;下载好之后打开你的VMware或者其他支持Linux的软件&#xff0c;OK~接下来就是安装过程了&#xff01; 1. 首先点击新建虚拟机 2. 选择典型的配置类型 3. 选…...

做网站哪家强/网络稿件投稿平台

最近新项目,从Jdeveloper转到Eclipse开发.工欲善其事必先利其器.话不多说.直入正题. SVN server 1.SVN服务器的下载安装. http://subversion.apache.org/packages.html#windows 下载2进制包. 选择VisualSVN Server .(比较方便,可视化操作) 安装.(安装后默认将以服务形式自行随系…...

成都网站建设报价/磁力帝

2019独角兽企业重金招聘Python工程师标准>>> 20.23 告警系统邮件引擎 mail.sh内容 //其中mail.py内容到这里下载https://coding.net/u/aminglinux/p/aminglinux-book/git/blob/master/D22Z/mail.py log$1 t_sdate %s t_s2date -d "2 hours ago" %s if [ !…...

网站的站外优化/网络推广运营外包公司

作者 | JiekeXu来源 | JiekeXu之路&#xff08;ID: JiekeXu_IT&#xff09;转载请联系授权 | (微信ID&#xff1a;xxq1426321293)大家好&#xff0c;我是 JiekeXu,很高兴又和大家见面了&#xff0c;今天分享下 Oracle 认证证书真伪辨别方法。本文首发于微信公众号【JiekeXu之路…...

公司做网站能够带来的好处/seo怎么学

文|曾响铃 来源|科技向令说&#xff08;xiangling0815&#xff09; 张一鸣手下抖音、火山及西瓜三大短视频产品如火如荼&#xff0c;不论是数据量或是糟心的事惹得舆论关注总能冲在前边。前两天&#xff0c;不怀好意的人通过抖音勾搭上10岁未成年女童&#xff0c;污言秽语又让抖…...