Linux之TCP和守護(hù)進(jìn)程詳解
一、TCP網(wǎng)絡(luò)程序
1.1 TCP服務(wù)端
成員變量:
int _listensock; // 監(jiān)聽(tīng)的文件描述符 string _ip; // 服務(wù)端ip uint16_t _port; // 端口號(hào) bool _isrunning; // 服務(wù)器是否在運(yùn)行
1.1.1 InitServer-創(chuàng)建服務(wù)端
1、創(chuàng)建套接字socket

- socket()打開(kāi)一個(gè)網(wǎng)絡(luò)通訊端口,如果成功的話(huà),就像open()一樣返回一個(gè)文件描述符;
- 應(yīng)用程序可以像讀寫(xiě)文件一樣用read/write在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù);
- 如果socket()調(diào)用出錯(cuò)則返回-1;
- 對(duì)于IPv4, family參數(shù)指定為AF_INET;
- 對(duì)于TCP協(xié)議,type參數(shù)指定為SOCK_STREAM, 表示面向流的傳輸協(xié)議
- protocol參數(shù)的介紹從略,指定為0即可。
2、綁定套接字bind

- 服務(wù)器程序所監(jiān)聽(tīng)的網(wǎng)絡(luò)地址和端口號(hào)通常是固定不變的,客戶(hù)端程序得知服務(wù)器程序的地址和端口號(hào)后 就可以向服務(wù)器發(fā)起連接; 服務(wù)器需要調(diào)用bind綁定一個(gè)固定的網(wǎng)絡(luò)地址和端口號(hào);
- bind()成功返回0,失敗返回-1。
- bind()的作用是將參數(shù)sockfd和myaddr綁定在一起, 使sockfd這個(gè)用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽(tīng)myaddr所描述的地址和端口號(hào);
- struct sockaddr *是一個(gè)通用指針類(lèi)型,myaddr參數(shù)實(shí)際上可以接受多種協(xié)議的sockaddr結(jié) 構(gòu)體,而它們的長(zhǎng)度各不相同,所以需要第三個(gè)參數(shù)addrlen指定結(jié)構(gòu)體的長(zhǎng)度;
我們的程序中對(duì)myaddr參數(shù)是這樣初始化的:

注意:其實(shí)大部分的接口都已經(jīng)幫我們考慮到大小端的問(wèn)題了,只不過(guò)ip和port需要我們寫(xiě)到OS里,所以需要我們自己去轉(zhuǎn)化!!
- (1)將整個(gè)結(jié)構(gòu)體清零;
- (2)設(shè)置地址類(lèi)型為AF_INET;
- (3)網(wǎng)絡(luò)地址為INADDR_ANY(或者是0.0.0.0), 這個(gè)宏表示本地的任意IP地址,因?yàn)榉?wù)器可能有多個(gè)網(wǎng)卡,每個(gè)網(wǎng)卡也可能綁 定多個(gè)IP地址, 這樣設(shè)置可以在所有的IP地址上監(jiān)聽(tīng),直到與某個(gè)客戶(hù)端建立了連接時(shí)才確定下來(lái)到底用哪個(gè)IP地址;
- (4)端口號(hào)為SERV_PORT, 我們定義為8080;
這兩步和之前UDP的基本一樣

3、TCP是面向連接的,服務(wù)器一般都是一個(gè)比較被動(dòng)的狀態(tài),要等待客戶(hù)端和他建立連接關(guān)系。所以他需要不斷保持一個(gè)監(jiān)聽(tīng)的狀態(tài) listen

listen()聲明sockfd處于監(jiān)聽(tīng)狀態(tài), 并且最多允許有backlog個(gè)客戶(hù)端處于連接等待狀態(tài), 如果接收到更多 的連接請(qǐng)求就忽略, 這里設(shè)置不會(huì)太大(一般是5),
- listen()成功返回0,失敗返回-1;

全部代碼:
void InitServer() // 創(chuàng)建服務(wù)器
{
// 1/創(chuàng)建套接字
_listensock = socket(AF_INET, SOCK_STREAM, 0); // 面向字節(jié)流;
if (_listensock < 0)
{
lg(Fatal, "create socket,errno:%d,errstring:%s", errno, strerror(errno));
exit(SocketError);
}
lg(Info, "create socket success,_listsock:%d", _listensock);
// 2/開(kāi)始綁定
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 先清空,然后再填進(jìn)去
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 轉(zhuǎn)網(wǎng)絡(luò)序列
inet_aton(_ip.c_str(), &local.sin_addr);
// 開(kāi)始綁定
if (bind(_listensock, (sockaddr *)&local, sizeof(local)) < 0) // 如果綁定失敗
{
lg(Fatal, "bind errno,errno:%d,errstring:%s", errno, strerror(errno));
exit(BindError);
}
lg(Info, "bind socket success,_listsock:%d", _listensock);
// 3/tcp和udp的區(qū)別就是要面向連接 要被動(dòng)地等待別人來(lái)連接
if (listen(_listensock, backlog) < 0)
{
lg(Fatal, "listen errno,errno:%d,errstring:%s", errno, strerror(errno));
}
lg(Info, "listen socket success,_listsock:%d", _listensock);
}1.1.2 Run-運(yùn)行服務(wù)器(單進(jìn)程版)
1、接收客戶(hù)端的連接請(qǐng)求 accept

- 三次握手完成后, 服務(wù)器調(diào)用accept()接受連接;
- 如果服務(wù)器調(diào)用accept()時(shí)還沒(méi)有客戶(hù)端的連接請(qǐng)求,就阻塞等待直到有客戶(hù)端連接上來(lái);
- addr是一個(gè)傳出參數(shù),accept()返回時(shí)傳出客戶(hù)端的地址和端口號(hào);
- 如果給addr 參數(shù)傳NULL,表示不關(guān)心客戶(hù)端的地址;
- addrlen參數(shù)是一個(gè)傳入傳出參數(shù)(value-result argument), 傳入的是調(diào)用者提供的, 緩沖區(qū)addr的長(zhǎng)度 以避免緩沖區(qū)溢出問(wèn)題, 傳出的是客戶(hù)端地址結(jié)構(gòu)體的實(shí)際長(zhǎng)度(有可能沒(méi)有占滿(mǎn)調(diào)用者提供的緩沖區(qū));
我們的服務(wù)器程序結(jié)構(gòu)是這樣的:


accept的返回值是什么意義??
我們會(huì)發(fā)現(xiàn)accept返回的也是一個(gè)文件描述符,那么這個(gè)文件描述符跟我們剛開(kāi)始的那個(gè)listensock是什么關(guān)系呢??
舉例子:假如我們?cè)诩倨谌ヒ粋€(gè)旅游圣地,到中午的時(shí)候很多當(dāng)?shù)氐牟宛^為了生意都會(huì)安排人站在外頭去拉客,比方說(shuō)你們一行人走到一家魚(yú)莊面前,這時(shí)候魚(yú)莊門(mén)口有個(gè)張三馬上靠過(guò)來(lái)開(kāi)始給你介紹這里餐館的特色,邀請(qǐng)你去魚(yú)莊吃飯,這個(gè)時(shí)候你們正好也餓了于是就進(jìn)去了 ,但是你們進(jìn)去的時(shí)候張三并沒(méi)有進(jìn)去,而是喊了李四這個(gè)服務(wù)員過(guò)來(lái)給你們服務(wù),自己又繼續(xù)出去拉客了,后來(lái)張三又不斷拉來(lái)新的客人,又不斷有王五,趙六……服務(wù)員也出來(lái)了,所以張三只關(guān)注于拉客,而李四等服務(wù)員只提供服務(wù),他們各自專(zhuān)注著自己的事情卻可以把整個(gè)魚(yú)莊經(jīng)營(yíng)得非常好?。?!
所以我們之前的listensock就是我們的張三,他就是專(zhuān)門(mén)負(fù)責(zé)和客戶(hù)端建立連接的,而accept返回值的sockfd是就相當(dāng)于是我們的李四,是專(zhuān)門(mén)給客戶(hù)端提供服務(wù)的!!
2、開(kāi)始給客戶(hù)端提供服務(wù)


我們用telnet模擬客戶(hù)端就可以進(jìn)行測(cè)試了!!


但是這樣有一個(gè)很尷尬的地方就是,我們當(dāng)前這個(gè)單進(jìn)程版的必須等到這個(gè)服務(wù)結(jié)束了才會(huì)去進(jìn)行下一個(gè)客戶(hù)端的連接,這樣顯然是不符合我們的要求的!(有點(diǎn)像客流量很多 但是餐館只有一張桌子 這樣效率很低?。。?/span>!所以我們接下來(lái)要嘗試 多進(jìn)程版、多線(xiàn)程版、線(xiàn)程池版
1.1.3 Run-運(yùn)行服務(wù)器(多進(jìn)程版)
多進(jìn)程思路:讓子進(jìn)程替我去完成工作,而我繼續(xù)去響應(yīng)鏈接?。?/span>
(1)父進(jìn)程把文件描述符表拷貝給子進(jìn)程后,父進(jìn)程就要把sockfd給關(guān)了(讓服務(wù)完全由子進(jìn)程去做,如果子進(jìn)程退出了意味著服務(wù)結(jié)束,這樣正好可以把這個(gè)文件給關(guān)了) 而子進(jìn)程可以把listensockfd給關(guān)了(讓鏈接完全由父進(jìn)程去做,防止子進(jìn)程誤操作)
(2)但是如果父進(jìn)程阻塞等待的話(huà),又會(huì)和單進(jìn)程一樣,而如果用非阻塞輪詢(xún)又會(huì)浪費(fèi)cpu資源,且增加程序設(shè)計(jì)的復(fù)雜性 所以我們要思考其他辦法!!
方法1:讓孫子進(jìn)程去做 然后子進(jìn)程退出 這樣的話(huà)父進(jìn)程立馬可以返回 而孫子進(jìn)程會(huì)被系統(tǒng)領(lǐng)養(yǎng) 由系統(tǒng)回收

方法2:直接將SIGCHLD信號(hào)設(shè)成SIG_IGN 那么系統(tǒng)就不會(huì)把退出的進(jìn)程轉(zhuǎn)成僵尸進(jìn)程

1.1.4 Run-運(yùn)行服務(wù)器 (多線(xiàn)程版)
但是多進(jìn)程太耗費(fèi)資源了?。∷晕覀儜?yīng)該考慮多線(xiàn)程版?。?/span>
(1)多線(xiàn)程不需要關(guān)閉文件描述符 因?yàn)槭枪蚕淼乃詻](méi)有多余的
(2)如果join的話(huà)又會(huì)阻塞住,所以我們可以直接將線(xiàn)程給分離了,這樣主線(xiàn)程就不關(guān)心了!
(3)定義一個(gè)類(lèi)將屬性傳給線(xiàn)程


如果線(xiàn)程調(diào)用的函數(shù)寫(xiě)在里面,默認(rèn)有this指針,所以必須把他設(shè)置成靜態(tài)成員函數(shù)!!
但是靜態(tài)成員函數(shù)不能調(diào)用非靜態(tài)成員函數(shù),所以我們可以把對(duì)象指針傳進(jìn)去,通過(guò)這個(gè)對(duì)象指針來(lái)調(diào)用成員函數(shù)。
1.1.5 Run-運(yùn)行服務(wù)器(線(xiàn)程池版漢英翻譯)
我不希望你客戶(hù)端連接成功后我才去創(chuàng)建一個(gè)線(xiàn)程,而一個(gè)客戶(hù)斷開(kāi)了又得釋放進(jìn)程,這樣效率太低了?。《椅也幌虢o你提供這種長(zhǎng)服務(wù)(就是你當(dāng)前客戶(hù)端如果請(qǐng)求太多的話(huà),我就得一直專(zhuān)門(mén)服務(wù)你,這就是長(zhǎng)服務(wù)),而多線(xiàn)程并不適合長(zhǎng)服務(wù),因?yàn)榫€(xiàn)程的個(gè)數(shù)是確定的,不能讓你一直給一個(gè)客戶(hù)端服務(wù),所以我們要嘗試把他修改成短服務(wù)(就是這個(gè)客戶(hù)端一旦接受了你的一次請(qǐng)求他就斷掉 繼續(xù)去服務(wù)別的客戶(hù)端)!!
所以我們(1)一方面需要通過(guò)線(xiàn)程池來(lái)避免線(xiàn)程被重復(fù)創(chuàng)建和釋放的過(guò)程,(2)另一方面把長(zhǎng)服務(wù)設(shè)置成短服務(wù)!!(3)然后將具體任務(wù)封裝起來(lái)交給線(xiàn)程池去完成,這樣可以解耦
我們可以將這個(gè)任務(wù)設(shè)置成英漢翻譯
dict.txt
apple:蘋(píng)果... banana:香蕉... red:紅色... yellow:黃色... the: 這 be: 是 to: 朝向/給/對(duì) and: 和 I: 我 in: 在...里 that: 那個(gè) have: 有 will: 將 for: 為了 but: 但是 as: 像...一樣 what: 什么 so: 因此 he: 他 her: 她 his: 他的 they: 他們 we: 我們 their: 他們的 his: 它的 with: 和...一起 she: 她 he: 他(賓格) it: 它
Init.hpp(讀取一個(gè)文件 然后分割到哈希表中)
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
const std::string dictname = "./dict.txt";
const std::string sep = ":";
//yellow:黃色...
static bool Split(std::string &s, std::string *part1, std::string *part2)
{
auto pos = s.find(sep);
if(pos == std::string::npos) return false;
*part1 = s.substr(0, pos);
*part2 = s.substr(pos+1);
return true;
}
class Init
{
public:
Init()
{
std::ifstream in(dictname);
if(!in.is_open())
{
lg(Fatal, "ifstream open %s error", dictname.c_str());
exit(1);
}
std::string line;
while(std::getline(in, line))
{
std::string part1, part2;
Split(line, &part1, &part2);
dict.insert({part1, part2});
}
in.close();
}
std::string translation(const std::string &key)
{
auto iter = dict.find(key);
if(iter == dict.end()) return "Unknow";
else return iter->second;
}
private:
std::unordered_map<std::string, std::string> dict;
};Task.hpp 任務(wù)
#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"
extern Log lg;
Init init;
class Task
{
public:
Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
: sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
{
}
Task()
{
}
void run()
{
// 測(cè)試代碼
char buffer[4096];
// Tcp是面向字節(jié)流的,你怎么保證,你讀取上來(lái)的數(shù)據(jù),是"一個(gè)" "完整" 的報(bào)文呢?
ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?
if (n > 0)
{
buffer[n] = 0;
std::cout << "client key# " << buffer << std::endl;
std::string echo_string = init.translation(buffer);
// sleep(5);
// // close(sockfd_);
// lg(Warning, "close sockfd %d done", sockfd_);
// sleep(2);
n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在
if(n < 0)
{
lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
}
}
else if (n == 0)
{
lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);
}
else
{
lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);
}
close(sockfd_);
}
void operator()()
{
run();
}
~Task()
{
}
private:
int sockfd_;
std::string clientip_;
uint16_t clientport_;
};線(xiàn)程池ThreadPool:
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
struct ThreadInfo
{
pthread_t tid;
std::string name;
};
static const int defalutnum = 10;
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 ThreadSleep()
{
pthread_cond_wait(&cond_, &mutex_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
std::string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads_)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *HandlerTask(void *args)
{
ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
std::string name = tp->GetThreadName(pthread_self());
while (true)
{
tp->Lock();
while (tp->IsQueueEmpty())
{
tp->ThreadSleep();
}
T t = tp->Pop();
tp->Unlock();
t();
}
}
void Start()
{
int num = threads_.size();
for (int i = 0; i < num; i++)
{
threads_[i].name = "thread-" + std::to_string(i + 1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
}
}
T Pop()
{
T t = tasks_.front();
tasks_.pop();
return t;
}
void Push(const T &t)
{
Lock();
tasks_.push(t);
Wakeup();
Unlock();
}
static ThreadPool<T> *GetInstance()
{
if (nullptr == tp_) // ???
{
pthread_mutex_lock(&lock_);
if (nullptr == tp_)
{
std::cout << "log: singleton create done first!" << std::endl;
tp_ = new ThreadPool<T>();
}
pthread_mutex_unlock(&lock_);
}
return tp_;
}
private:
ThreadPool(int num = defalutnum) : threads_(num)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
ThreadPool(const ThreadPool<T> &) = delete;
const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
std::vector<ThreadInfo> threads_;
std::queue<T> tasks_;
pthread_mutex_t mutex_;
pthread_cond_t 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;
1.1.6 服務(wù)端寫(xiě)入時(shí)客戶(hù)端退出了怎么辦
對(duì)一個(gè)對(duì)端已經(jīng)關(guān)閉的socket調(diào)用兩次write, 第二次將會(huì)生成SIGPIPE信號(hào), 該信號(hào)默認(rèn)結(jié)束進(jìn)程.

1.1.7 服務(wù)端全部代碼
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //套接字類(lèi)型的頭文件
#include <strings.h> //bzero的頭文件
#include <cstring>
#include <arpa/inet.h>
#include "Log.hpp"
#include <functional>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include"ThreadPool.hpp"
#include "Task.hpp"
using namespace std;
typedef function<string(const string &)> func_t;
Log lg; // 命令對(duì)象 用來(lái)打印日志信息
enum
{
UsageError = 1, // 使用有誤
SocketError, // 創(chuàng)建套接字有誤
BindError, // 綁定有誤
ListenError, // 監(jiān)聽(tīng)有誤
};
const int defaultfd = -1;
const uint16_t defaultport = 8080;
const string defaultip = "0.0.0.0";
const int size = 1024;
const int backlog = 10; // 但是一般不要設(shè)置的太大
class ThreadData
{
public:
ThreadData(int fd, uint16_t &port, const string &ip, TcpServer *t) : sockfd(fd), clientport(port),clientip(ip), tsvr(t)
{
}
public:
int sockfd;
string clientip;
uint16_t clientport;
TcpServer *tsvr;//通過(guò)對(duì)象讓靜態(tài)成員函數(shù)調(diào)用 非靜態(tài)成員方法
};
class TcpServer
{
public:
TcpServer(uint16_t &port, const string &ip = defaultip) : _listensock(defaultfd), _port(port), _ip(ip), _isrunning(false)
{
}
static void *Routine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->tsvr->Service(td->sockfd, td->clientport, td->clientip); //通過(guò)傳一個(gè)對(duì)象指針來(lái)調(diào)用類(lèi)內(nèi)的成員函數(shù)
delete td;
return nullptr;
}
void Service(int sockfd, uint16_t clientport, string clientip)
{
char buffer[size];
while (true)
{
// 服務(wù)端要先接收客戶(hù)端的數(shù)據(jù)
ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 讀到緩沖區(qū)中
if (n > 0) // 大于0表示讀取成功 =0表示當(dāng)前沒(méi)有數(shù)據(jù)可讀了 <0說(shuō)明讀取失敗
{
buffer[n] = 0; // 把我們讀到的信息當(dāng)成是字符串的形式來(lái)處理
cout << "client say#" << buffer << endl;
string echo_string = "tcpserver echo#";
echo_string += buffer;
// 對(duì)buffer簡(jiǎn)單加工完之后往客戶(hù)端寫(xiě)入
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // 沒(méi)有什么數(shù)據(jù)可讀的了 所以八成是客戶(hù)端鏈接斷開(kāi)了 我服務(wù)端不能崩
{
lg(Info, "client quit, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
break;
}
else // 讀取失敗 可能是文件描述符被關(guān)閉了
{
lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
}
}
}
void InitServer() // 創(chuàng)建服務(wù)器
{
// 1/創(chuàng)建套接字
_listensock = socket(AF_INET, SOCK_STREAM, 0); // 面向字節(jié)流;
if (_listensock < 0)
{
lg(Fatal, "create socket,errno:%d,errstring:%s", errno, strerror(errno));
exit(SocketError);
}
lg(Info, "create socket success,_listsock:%d", _listensock);
// 2/開(kāi)始綁定
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 先清空,然后再填進(jìn)去
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 轉(zhuǎn)網(wǎng)絡(luò)序列
inet_aton(_ip.c_str(), &local.sin_addr);
// 開(kāi)始綁定
if (bind(_listensock, (sockaddr *)&local, sizeof(local)) < 0) // 如果綁定失敗
{
lg(Fatal, "bind errno,errno:%d,errstring:%s", errno, strerror(errno));
exit(BindError);
}
lg(Info, "bind socket success,_listsock:%d", _listensock);
// 3/tcp和udp的區(qū)別就是要面向連接 要被動(dòng)地等待別人來(lái)連接
if (listen(_listensock, backlog) < 0)
{
lg(Fatal, "listen errno,errno:%d,errstring:%s", errno, strerror(errno));
}
lg(Info, "listen socket success,_listsock:%d", _listensock);
}
///
void Run() // 啟動(dòng)服務(wù)器
{
ThreadPool<Task>::GetInstance()->Start();
signal(SIGPIPE,SIG_IGN);
_isrunning = true;
lg(Info, "tcpServer is running....");
// 1、accept嘗試獲取新鏈接
while (_isrunning) // 不斷獲取新鏈接
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int sockfd = accept(_listensock, (struct sockaddr *)&client, &len); // 這個(gè)id是用來(lái)做服務(wù)的!!
if (sockfd < 0) // 如果獲取失敗 應(yīng)該獲取下一個(gè) 而不是直接結(jié)束
{
lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));
continue;
}
lg(Info, "accept success,sockfd:%d", sockfd);
// 2、將客戶(hù)端的信息弄出來(lái)
uint16_t clientport = ntohs(client.sin_port);
char clientip[32]; // 輸出型參數(shù)
inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
// 根據(jù)新連接來(lái)進(jìn)行通信服務(wù)-version單進(jìn)程版
// Service(sockfd, clientport, clientip);
// close(sockfd);
// version2 多進(jìn)程版-讓子進(jìn)程幫我做服務(wù),而我繼續(xù)去鏈接
// pid_t id=fork();
// if(id==0) //child 讓他去服務(wù)
// {
// close(_listensock);//關(guān)掉 防止誤操作
// if(fork()>0) exit(0);//子進(jìn)程退掉 讓孫子進(jìn)程來(lái)做
// Service(sockfd, clientport, clientip);//孫子進(jìn)程此時(shí)已經(jīng)被 system領(lǐng)養(yǎng)了
// close(sockfd);
// exit(0);
// }
// //father
// pid_t rid=waitpid(id,nullptr,0);//子進(jìn)程一進(jìn)去就退出了,所以父進(jìn)程會(huì)馬上返回繼續(xù)去鏈接
// (void)rid;//rid沒(méi)用過(guò) 所以用一下防止警告
// //signal(SIGCHLD,SIG_IGN);
// version3 多線(xiàn)程版??!
// ThreadData *td = new ThreadData(sockfd, clientport, clientip, this);
// pthread_t tid;
// pthread_create(&tid, nullptr, Routine, td);
//version4 線(xiàn)程池英漢詞典
Task t(sockfd, clientip, clientport);
ThreadPool<Task>::GetInstance()->Push(t);
}
}
~TcpServer()
{
}
private:
int _listensock; // 監(jiān)聽(tīng)的文件描述符
string _ip; // 服務(wù)端ip
uint16_t _port; // 端口號(hào)
bool _isrunning; // 服務(wù)器是否在運(yùn)行
};1.2 客戶(hù)端
客戶(hù)端幫我們發(fā)送connect請(qǐng)求的時(shí)候,會(huì)自動(dòng)bind

客戶(hù)端需要調(diào)用connect()連接服務(wù)器;
connect和bind的參數(shù)形式一致, 區(qū)別在于bind的參數(shù)是自己的地址, 而connect的參數(shù)是對(duì)方的地址;
connect()成功返回0,出錯(cuò)返回-1;
單進(jìn)程版客戶(hù)端:
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./tcpclient serverip serverport
int main(int argc,char* argv[]) //必須知道服務(wù)器的ip和端口號(hào)
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
//1、第一步 創(chuàng)建套接字
int sockfd=socket(AF_INET, SOCK_STREAM, 0);
if(sockfd<0)
{
cerr << "socker error" << endl;
return 1;
}
//2/OS幫助們bind
struct sockaddr_in server;//輸出型參數(shù)
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
// server.sin_addr.s_addr = inet_addr(serverip.c_str());//字符串轉(zhuǎn)四字節(jié)
inet_pton(AF_INET,serverip.c_str(), &(server.sin_addr));
socklen_t len = sizeof(server);
//3、向服務(wù)端發(fā)送鏈接請(qǐng)求
int n=connect(sockfd,(struct sockaddr*)&server,len);
if(n<0)//如果鏈接失敗
{
cerr<<"connect error……" <<endl;
return 2;
}
//鏈接成功
cout<<"connect sucess"<<endl;
string message; //用來(lái)
char inbuffer[1024];//接收讀取的緩沖區(qū)
while(true)
{
cout<<"please enter@";
getline(cin,message); //將獲取的信息放到message中 發(fā)到服務(wù)端
// 1. 數(shù)據(jù) 2. 給誰(shuí)發(fā)
ssize_t n=write(sockfd,message.c_str(),message.size());//可以直接通過(guò)write寫(xiě)到文件里
//一般不會(huì)寫(xiě)失敗,因?yàn)榉?wù)器一般都不關(guān)
//從文件里讀
ssize_t s = read(sockfd,inbuffer,sizeof(inbuffer));//讀到我們的緩沖區(qū)里
//會(huì)將結(jié)果帶回來(lái)
if(s > 0)
{
inbuffer[s] = 0;
cout << inbuffer << endl;
}
}
close(sockfd);
}線(xiàn)程池版英漢翻譯客戶(hù)端:
(1)需要改成短服務(wù),所以鏈接在請(qǐng)求一次后就得斷掉,所以while循環(huán)必須寫(xiě)在鏈接的前面
(2)我們平時(shí)掉線(xiàn)了 就是服務(wù)端和客戶(hù)端斷開(kāi)了,這個(gè)時(shí)候我們客戶(hù)端要繼續(xù)嘗試跟服務(wù)端建立連接,當(dāng)然也要限制連接次數(shù) 所以可以用一個(gè)do while循環(huán)放在鏈接那里
因?yàn)槊刻幚硪淮握?qǐng)求就要斷掉,所以while循環(huán)必須寫(xiě)到鏈接前面
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[]) // 必須知道服務(wù)器的ip和端口號(hào)
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 2/OS幫助們bind
struct sockaddr_in server; // 輸出型參數(shù)
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
// server.sin_addr.s_addr = inet_addr(serverip.c_str());//字符串轉(zhuǎn)四字節(jié)
inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
// 3、向服務(wù)端發(fā)送鏈接請(qǐng)求
while (true)
{
int cnt = 5; // 重連次數(shù)
int isreconnect = false; // 是否要嘗試重連
// 創(chuàng)建套接字
int sockfd =0;
sockfd=socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
cerr << "socker error" << endl;
return 1;
}
do
{
int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0) // 如果鏈接失敗
{
isreconnect = true;
--cnt;
cerr << "connect error……" << endl;
}
else
break; // 鏈接成功就跳出去
} while (cnt && isreconnect);
if (cnt == 0)
{
cerr << "user ofline" << endl;
break;
}
// 鏈接成功
cout << "connect sucess" << endl;
string message; // 用來(lái)
char inbuffer[1024]; // 接收讀取的緩沖區(qū)
cout << "please enter@";
getline(cin, message); // 將獲取的信息放到message中 發(fā)到服務(wù)端
// 1. 數(shù)據(jù) 2. 給誰(shuí)發(fā)
ssize_t n = write(sockfd, message.c_str(), message.size()); // 可以直接通過(guò)write寫(xiě)到文件里
if (n < 0)
{
std::cerr << "write error..." << std::endl;
// break; 短服務(wù)不用出去
}
// 一般不會(huì)寫(xiě)失敗,因?yàn)榉?wù)器一般都不關(guān)
// 從文件里讀
n = read(sockfd, inbuffer, sizeof(inbuffer)); // 讀到我們的緩沖區(qū)里
// 會(huì)將結(jié)果帶回來(lái)
if (n > 0)
{
inbuffer[n] = 0;
cout << inbuffer << endl;
}
close(sockfd);
}
return 0;
}當(dāng)然如果執(zhí)行的是長(zhǎng)服務(wù)的話(huà),一旦寫(xiě)入失敗就要break出去重連??!
二、守護(hù)進(jìn)程
服務(wù)端在我們ctrl c或者關(guān)掉xshell的時(shí)候就會(huì)被殺死,但是我們希望無(wú)論如何這個(gè)服務(wù)端是一直在跑的??!所以我們必須守護(hù)進(jìn)程!!
2.1 Session和前后臺(tái)進(jìn)程
每當(dāng)一個(gè)用戶(hù)登錄的就是 默認(rèn)就會(huì)形成一個(gè)session,然后分配一個(gè)bash進(jìn)程
前臺(tái)進(jìn)程后后臺(tái)進(jìn)程的關(guān)鍵在于誰(shuí)擁有鍵盤(pán)文件!!
1、執(zhí)行可執(zhí)行程序的時(shí)候在后面加個(gè)& 該進(jìn)程就會(huì)變成后臺(tái)進(jìn)程
2、通過(guò)jobs命令可以看到所有后臺(tái)任務(wù)

3、該序號(hào)叫做后臺(tái)進(jìn)程任務(wù)號(hào),我們可以使用fg+序號(hào)將后臺(tái)進(jìn)程提到前臺(tái)
4、如果我們將一個(gè)后臺(tái)進(jìn)程提到前臺(tái)之后后悔了,我們可以ctrl+z向前臺(tái)進(jìn)程發(fā)送19號(hào)信號(hào),此時(shí)當(dāng)前臺(tái)進(jìn)程被暫停時(shí),bash進(jìn)程就會(huì)自動(dòng)移到前臺(tái)進(jìn)程(因?yàn)樵诿钚兄?,前臺(tái)必須存在),而暫停的進(jìn)程自動(dòng)放到后臺(tái)。 然后通過(guò)bg+序號(hào)將因?yàn)闀和1环旁诤笈_(tái)的進(jìn)程恢復(fù)運(yùn)行!
2.2 進(jìn)程間關(guān)系



1、PGID叫進(jìn)程組ID,一個(gè)組的是一樣的 ,只啟動(dòng)一個(gè)進(jìn)程的話(huà)就自成一組
2、sessionid用的就是bash進(jìn)程的pid,而多個(gè)進(jìn)程組在同一個(gè)session里面sid是一樣的
3、如果我們關(guān)掉OS,那么后臺(tái)進(jìn)程會(huì)收到用戶(hù)登錄和退出的影響 因此我們需要守護(hù)進(jìn)程化

2.3 如何做到
要嘗試自成一個(gè)會(huì)話(huà)!!


setsid 自成會(huì)話(huà) 不能是組長(zhǎng),所以我們必須fork出子進(jìn)程,然后退出父進(jìn)程,讓子進(jìn)程執(zhí)行后面的代碼,所以 守護(hù)進(jìn)程的本質(zhì)也是孤兒進(jìn)程!!
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
// 1. 忽略其他異常信號(hào)
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
// 2. 將自己變成獨(dú)立的會(huì)話(huà)
if (fork() > 0)
exit(0);
setsid();
// 3. 更改當(dāng)前調(diào)用進(jìn)程的工作目錄
if (!cwd.empty())
chdir(cwd.c_str());
// 4. 標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯(cuò)誤重定向至/dev/null
int fd = open(nullfile.c_str(), O_RDWR);
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}(1)忽略其他異常信號(hào)
(2)用setsid將自己變成獨(dú)立會(huì)話(huà)
(3)更改當(dāng)前的工作目錄
(4)標(biāo)準(zhǔn)輸出、輸入、錯(cuò)誤重定向到/dev/null (守護(hù)進(jìn)程必須和他們解關(guān)聯(lián),如果我們不往顯示器而是文件寫(xiě)入的話(huà)還好,但如果我們直接關(guān)閉描述符的話(huà)顯然會(huì)導(dǎo)致printf和cout出錯(cuò)?。《?dev/null就是相當(dāng)于是一個(gè)垃圾桶文件,可以把不關(guān)心的內(nèi)容丟到里面去)

所以我們將上述代碼在Run函數(shù)中運(yùn)行 然后同時(shí)把我們的日志改成寫(xiě)到文件中?。?/span>

如果我們想殺掉的話(huà)就得用kill -9 PID


2.4 為什么我們能遠(yuǎn)程登錄Linux呢?
其實(shí)ssh就是守護(hù)進(jìn)程,我們向他發(fā)送鏈接請(qǐng)求,認(rèn)證后再登錄,然后分配一個(gè)會(huì)話(huà),然后將命令再遠(yuǎn)端執(zhí)行完后再返回給你

一般來(lái)說(shuō)守護(hù)進(jìn)程我們一般在他的名字后面加一個(gè)-D
三、TCP協(xié)議的通訊流程
3.1 TCP的三次握手和四次揮手

3.2 TCP通信全雙工
TCP是全雙工的 ,因?yàn)榘l(fā)送和接受緩沖區(qū)是分開(kāi)的,多線(xiàn)程時(shí)雖然不能多人讀,但是支持同時(shí)讀寫(xiě)??!


3.3 如何理解鏈接
對(duì)于服務(wù)器來(lái)說(shuō),同時(shí)存在大量連接,那么誰(shuí)來(lái)打開(kāi)、誰(shuí)來(lái)關(guān)閉、連接狀態(tài)是什么,所以O(shè)S必須要先描述再組織來(lái)管理鏈接
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Ubuntu19.10開(kāi)啟ssh服務(wù)(詳細(xì)過(guò)程)
這篇文章主要介紹了Ubuntu19.10開(kāi)啟ssh服務(wù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Linux系統(tǒng)配置靜態(tài)IP地址的詳細(xì)步驟
在安裝Linux后,系統(tǒng)的網(wǎng)絡(luò)IP地址默認(rèn)是自動(dòng)分配的,這將導(dǎo)致每次啟動(dòng)Linux系統(tǒng)后,系統(tǒng)的IP地址都會(huì)發(fā)生改變,此文以CentOS7系統(tǒng)環(huán)境為例,詳細(xì)介紹如何配置Linux系統(tǒng)的靜態(tài)IP地址,需要的朋友可以參考下2024-04-04
centos7.2搭建LAMP環(huán)境的具體操作方法
下面小編就為大家?guī)?lái)一篇centos7.2搭建LAMP環(huán)境的具體操作方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
linux如何編譯安裝新內(nèi)核支持NTFS文件系統(tǒng)(以redhat7.2x64為例)
這篇文章主要介紹了linux如何編譯安裝新內(nèi)核支持NTFS文件系統(tǒng)(以redhat7.2x64為例),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-10-10
Linux之Centos8創(chuàng)建CA證書(shū)教程
大家好,本篇文章主要講的是Linux之Centos8創(chuàng)建CA證書(shū)教程,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽2021-12-12
linux下SVN配置實(shí)現(xiàn)項(xiàng)目目錄自動(dòng)更新以及源碼安裝的操作方法
下面小編就為大家分享一篇linux下SVN配置實(shí)現(xiàn)項(xiàng)目目錄自動(dòng)更新以及源碼安裝的操作方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12

