一、什么是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.cppg++ -o $@ $^ -std=c++11
tcpserver:Main.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f tcpserver tcpclient
服務運行只要運行一次起來后有就會7*24小時在后臺跑著: