欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux之TCP和守護(hù)進(jìn)程詳解

 更新時(shí)間:2025年08月06日 08:43:04   作者:??小陳在拼命??  
本文系統(tǒng)講解了TCP網(wǎng)絡(luò)編程實(shí)現(xiàn),涵蓋服務(wù)端創(chuàng)建(套接字、綁定、監(jiān)聽(tīng))、多進(jìn)程/線(xiàn)程處理、客戶(hù)端連接管理及守護(hù)進(jìn)程設(shè)計(jì),重點(diǎn)解析了TCP三次握手、四次揮手、全雙工通信機(jī)制與連接狀態(tài)管理

一、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ù)(詳細(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)配置靜態(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)境的具體操作方法

    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為例)

    這篇文章主要介紹了linux如何編譯安裝新內(nèi)核支持NTFS文件系統(tǒng)(以redhat7.2x64為例),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2016-10-10
  • Apache 文件上傳與文件下載案例詳解

    Apache 文件上傳與文件下載案例詳解

    寫(xiě)一個(gè)Apache文件上傳與文件下載的案例以供今后學(xué)習(xí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧
    2017-07-07
  • Linux系統(tǒng)中添加地址解析功能的全流程

    Linux系統(tǒng)中添加地址解析功能的全流程

    在 Linux 系統(tǒng)中,地址解析功能是網(wǎng)絡(luò)通信的基礎(chǔ),無(wú)論是訪問(wèn)網(wǎng)站、發(fā)送郵件,還是進(jìn)行其他網(wǎng)絡(luò)操作,都需要將域名解析為 IP 地址,本文將詳細(xì)介紹如何在 Linux 系統(tǒng)中添加地址解析功能,包括使用 /etc/hosts 文件和配置DNS服務(wù)器,需要的朋友可以參考下
    2025-07-07
  • Linux之Centos8創(chuàng)建CA證書(shū)教程

    Linux之Centos8創(chuàng)建CA證書(shū)教程

    大家好,本篇文章主要講的是Linux之Centos8創(chuàng)建CA證書(shū)教程,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽
    2021-12-12
  • Linux加載vmlinux調(diào)試

    Linux加載vmlinux調(diào)試

    今天小編就為大家分享一篇關(guān)于Linux加載vmlinux調(diào)試,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-02-02
  • 淺談linux rwxrwxrwt文件夾屬性

    淺談linux rwxrwxrwt文件夾屬性

    下面小編就為大家?guī)?lái)一篇淺談linux rwxrwxrwt文件夾屬性。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-12-12
  • linux下SVN配置實(shí)現(xiàn)項(xiàng)目目錄自動(dòng)更新以及源碼安裝的操作方法

    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

最新評(píng)論