一、什么是TCP协议
1.1 、TCP是传输层的协议,TCP需要连接,TCP是一种可靠性传输协议,TCP是面向字节流的传输协议;
二、TCPserver端的搭建
2.1、我们最终好实现的效果是
客户端在任何时候都能连接到服务端,然后向服务端发送请求,服务端回应客户端的请求;且当客户端向服务端发送请求连接失败时(断网或者服务器故障),会有一个重新连接的状态,在某个时间段内可以不停尝试重新连接;在这里我们把请求和回应请求设计为一个英文翻译,即客户端发送英文,服务端翻译英文并把结果返回给客户端;
2.2 TCPserver第一步创建套接字
2.3、 准备数据
2.4、绑定
2.5、监听
以上初始化工作完成,接下来服务器运行:
2.6、获取连接
2.7、让线程池取获取并处理任务
自己实现的简单版的线程池:
#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任务编写一下,即收发任务
#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入口函数
#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
#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中加入守护进程
守护进程的实现:
#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、翻译服务的实现
#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 编译运行
.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小时在后台跑着: