国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二)

這篇具有很好參考價值的文章主要介紹了【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

網(wǎng)絡(luò)編程套接字(二)

簡單TCP服務(wù)器實現(xiàn)

我們將會使用到的頭文件放在comm.h文件中

#include <iostream>
#include <memory.h>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
創(chuàng)建套接字

創(chuàng)建過程和UDP服務(wù)器幾乎完全一樣,除了使用的是TCP服務(wù)器使用的是流式服務(wù)(SOCK_STREAM),UDP使用的是數(shù)據(jù)包服務(wù)(SOCK_DGRAM)

#include "comm.h"

class TcpServer {
public:
  TcpServer(){};
  void InitServer(){
    sock = socket(AF_INET, SOCK_STREAM, 0);     
    if (sock < 0) {
      std::cerr << "socket error" << std::endl;
      exit(2);
    }
  }
  ~TcpServer(){
    if (sock >= 0) close(sock);
  }
private:
  void Socket();
  int sock;
};
服務(wù)器綁定

綁定的過程和UDP服務(wù)器也是相同的,可以看著復(fù)習(xí)一下

     // 2 服務(wù)器綁定
      sockaddr_in local;
      memset(&local, 0, sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;
      if (bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0) {
        std::cerr << "bind error" << std::endl;
        exit(3);
      }
      std::cout << "server bind success" << std::endl;             

定義好struct sockaddr_in結(jié)構(gòu)體后,最好使用memset對該結(jié)構(gòu)體進(jìn)行初始化,也可以使用bzero函數(shù)進(jìn)行清空

void bzero(void *s, size_t n);
服務(wù)器監(jiān)聽

TCP服務(wù)器是面向連接的,,客戶端在正式向TCP服務(wù)器發(fā)送數(shù)據(jù)之前必須先于TCP服務(wù)器建立連接,然后才可以進(jìn)行通信

因此TCP服務(wù)器需要時刻注意是否有客戶端發(fā)來連接請求,需要將TCP創(chuàng)建的套接字設(shè)置成監(jiān)聽狀態(tài)

int listen(int socket, int backlog);
  • socket : 需要設(shè)置為監(jiān)聽狀態(tài)的套接字對應(yīng)的文件描述符
  • backlog: 全連接隊列的最大長度。如果有多個客戶端同時發(fā)來連接請求,此時未被服務(wù)器處理的連接會先被放入連接隊列,該參數(shù)代表這個連接隊列的最大長度,一般設(shè)置成5或者10即可
  • 監(jiān)聽成功返回0,失敗返回-1同時設(shè)置錯誤碼
	  // 3 服務(wù)器監(jiān)聽
      if (listen(listen_sock, 10) < 0) {
        std::cerr << "listen error" << std::endl;
        exit(4);
      }

這里我們發(fā)現(xiàn)上文的sockfd其實是一個被監(jiān)聽的文件描述符,為了變量命名更容易讓人理解,我們把sockfd改為listen_sock,并且在初始化TCP服務(wù)器中,只有套接字創(chuàng)建成功,綁定成功,監(jiān)聽成功,TCP服務(wù)器的初始化才算完成

vim 替換單詞

全文替換 ::#sockfd#listen_sock#g 使用 :#str1#str2#g進(jìn)行全文替換,將str1全部替換成str2

局部替換: : 20, 30s#str1#str2#g (將20到30行內(nèi)的str1替換成str2)

當(dāng)前行替換: : s#str1#str2#g (將光標(biāo)所在行內(nèi)的str1 替換成 str2)

服務(wù)器接收連接

TCP服務(wù)器初始化后就可以開始運(yùn)行了,但是TCP服務(wù)器與客戶端在進(jìn)行網(wǎng)絡(luò)通信之前,服務(wù)器需要先獲取到客戶端的連接請求

 int accept(int socket, struct sockaddr *restrict address,
              socklen_t *restrict address_len);
  • socket : 特定的監(jiān)聽套接字,表示從該監(jiān)聽文件中獲取連接

  • address : 對端網(wǎng)絡(luò)相關(guān)的屬性信息

  • addrlen: 傳入希望讀取到的address結(jié)構(gòu)體的長度,返回實際讀取到的address結(jié)構(gòu)體的長度,是一個輸入輸出參數(shù)

  • 獲取連接成功返回接收到的套接字的文件描述符,獲取連接失敗返回-1,同時設(shè)置錯誤碼

監(jiān)聽套接字和accept函數(shù)返回套接字的區(qū)別

  • 監(jiān)聽套接字:用于獲取連接請求信息,accept函數(shù)不斷從監(jiān)聽文件中獲取新連接
  • accept返回套接字:用于為這個連接提供服務(wù),進(jìn)行真正的業(yè)務(wù)數(shù)據(jù)傳輸

服務(wù)端獲取連接

  • accept函數(shù)獲取連接時可能會失敗,但是服務(wù)器不能因為獲取某一個連接失敗就退出,因此服務(wù)端獲取連接失敗后還需要繼續(xù)獲取連接

  • 如果需要將獲取到的客戶端IP地址和端口號信息進(jìn)行輸出,需要調(diào)用inet_ntoa函數(shù)將整數(shù)IP轉(zhuǎn)換成字符串IP,調(diào)用ntohs函數(shù)將網(wǎng)絡(luò)序列轉(zhuǎn)換成主機(jī)序列

  • inet_ntoa函數(shù)會先將整數(shù)IP轉(zhuǎn)換成主機(jī)序列,然后再將其轉(zhuǎn)換成字符串IP

 void Start() {
    for (;;) {
      // 獲取連接
      struct sockaddr_in peer;
      memset(&peer, 0, sizeof(peer));
      socklen_t len = sizeof(peer);
      int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
      if (sock < 0) {
        std::cerr << "accept error, continue" << std::endl;
        continue;
      }
      std::string client_ip = inet_ntoa(peer.sin_addr);  
      short client_port = ntohs(peer.sin_port);
      cout << "get a new link->"  << sock << "[" << client_ip << ":" << client_port << "]" << endl;
      Service(sock, client_ip, client_port);      // 進(jìn)行業(yè)務(wù)處理
    }
  }

服務(wù)器處理請求

現(xiàn)在服務(wù)器已經(jīng)可以和客戶端建立連接了,接下來就是到了通信階段。我們只需要通過對accept函數(shù)打開的網(wǎng)絡(luò)文件進(jìn)行讀寫,就可以完成網(wǎng)絡(luò)數(shù)據(jù)的傳輸和接收了。為了能讓雙方都可以看到現(xiàn)象,這里就實現(xiàn)一個簡單的回聲TCP服務(wù)器

ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
ssize_t read(int fildes, void *buf, size_t nbyte);
  • fd:特定的文件描述符,表示從該文件描述符中讀取數(shù)據(jù)

  • buffer : 數(shù)據(jù)的存儲位置,表示將讀取數(shù)據(jù)到該緩沖區(qū)中

  • count : 期望從該文件描述符中讀取的字節(jié)數(shù)

  • 返回值大于零代表本次讀取到的字節(jié)數(shù),等于零表示對端對出,小于零說明讀取出現(xiàn)錯誤

ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset);
ssize_t write(int fildes, const void *buf, size_t nbyte);
  • fd: 特定的文件描述符,表示將把數(shù)據(jù)寫入該文件描述符對應(yīng)的文件

  • buffer : 需要寫入的數(shù)據(jù)

  • count :期望寫入的字節(jié)數(shù)

  • 寫入成功返回實際寫入的字節(jié)數(shù),寫入失敗返回-1,同時錯誤碼被設(shè)置

還需要注意到,服務(wù)端讀取的數(shù)據(jù)是從服務(wù)套接字中獲取的,如果客戶端斷開連接服務(wù)結(jié)束那么需要關(guān)閉對應(yīng)文件。因為文件描述符本質(zhì)就是數(shù)組的下標(biāo),是有限的,如果一直占用會導(dǎo)致文件描述符泄漏

void Service(int sock, std::string client_ip, short client_port) {
    for (;;) {
#define BUFFER_SIZE 128
      char buffer[BUFFER_SIZE];
      ssize_t size = read(sock, buffer, BUFFER_SIZE - 1);  				// 讀取請求
      if (size > 0) {          // 讀取成功
        buffer[size] = 0;
        std::cout << client_ip << ":" << client_port << " # " << buffer << std::endl;
        std::string response = "tcp server say # ";
        response += buffer;
        if (write(sock, response.c_str(), response.size()) < 0) {       // 發(fā)送響應(yīng)
          std::cerr << "write response error" << std::endl;
        } else {
          std::cout << "send response success" << std::endl;
        }
      } else if (size == 0) { // 對端關(guān)閉連接
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      } else {                // 讀取失敗
        std::cerr << "read request error" << std::endl;
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      }
    }
  }

簡單TCP客戶端實現(xiàn)

創(chuàng)建套接字
class TcpClient{
public:  
  TcpClient(std::string& ip, short port)
    : sockfd(-1), server_ip(ip), server_port(port){}
  ~TcpClient(){
    if (sockfd > 0) close (sockfd);
  };

  int ClientInit() {
    // 創(chuàng)建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
      std::cerr << "socket error" << std::endl;
      exit(2);
    }
    std::cout << "socket success" << std::endl;

    return 0;
  }
private:
  int sockfd;
  std::string server_ip;
  short server_port;
};


客戶端發(fā)起連接

由于客戶端不需要用戶手動綁定也不需要監(jiān)聽,所以客戶端創(chuàng)建好套接字后就可以直接向服務(wù)端發(fā)起連接請求

 int connect(int socket, const struct sockaddr *address, socklen_t address_len);
  • socket : 特定的套接字,表示通過該套接字發(fā)起連接請求
  • address : 對端的網(wǎng)絡(luò)相關(guān)信息
  • addrlen: 傳入的addr結(jié)構(gòu)體的長度
  • 綁定成功返回0,連接失敗返回-1,同時錯誤碼被設(shè)置
  void Start() {
    // 發(fā)送連接請求
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    if (connect(sockfd, (struct sockaddr*)&server, sizeof(server)) < 0) {
      std::cerr << "connect error" << std::endl;
      exit(3);
    }
    std::cout << "connect success" << std::endl;
    Request();
  }

客戶端發(fā)起請求

這里的代碼邏輯非常簡單,可以稍微看一看

void Request() {
    for (; ;) {
      std::string msg;
      getline(cin, msg);
      if (msg == "quit") {
        break;
      }
      write(sockfd, msg.c_str(), msg.size());

#define BUFFER_SIZE 128
      char buffer[BUFFER_SIZE];
      ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
      if (size > 0) {
        buffer[size] = 0;
        std::cout << buffer << std::endl;
      }
    }
  }

服務(wù)器簡單測試

首先使用telnet進(jìn)行連接測試,可以看到服務(wù)器可以正常建立連接??刂?code>telnet給服務(wù)器發(fā)送信息服務(wù)器可以接收并能返回響應(yīng)

使用netstat命令查看,可以看到一個名為tcp_server的進(jìn)程正處于監(jiān)聽狀態(tài)

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二),網(wǎng)絡(luò)編程,計算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò)

使用我們的客戶端連接,可以看到服務(wù)端可以打印客戶端的IP地址和端口號以及發(fā)送的數(shù)據(jù),客戶端也可以接收服務(wù)器發(fā)來的響應(yīng)。客戶端一旦退出,服務(wù)器也會立刻接收到并作出反應(yīng)。

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二),網(wǎng)絡(luò)編程,計算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò)

服務(wù)器簡單測評

當(dāng)我們僅僅使用一個客戶端連接服務(wù)端時,客戶端可以正常享受服務(wù)器的服務(wù)。但是若再來一個客戶端時,雖然新來的客戶端也可以成功建立連接,但是我們的服務(wù)器正在為第一個客戶端提供服務(wù),無法立馬處理第二個客戶端的請求。只有等第一個客戶端推出后,才能對第二個客戶端發(fā)來的數(shù)據(jù)進(jìn)行打印

單執(zhí)行流服務(wù)器

這是因為我們的服務(wù)器是一個單執(zhí)行流的,這種服務(wù)一一次只能為一個客戶端提供服務(wù)。

單執(zhí)行流服務(wù)器為什么可以同時和多個客戶端建立連接

當(dāng)服務(wù)端在給第一個客戶端提供服務(wù)期間,第二個客戶端發(fā)送連接請求時是成功的,這是因為連接其實已經(jīng)建立,只是服務(wù)端還沒有調(diào)用accept函數(shù)將連接獲取上來罷了

前文在介紹listen接口的時候提到一個參數(shù)backlog,實際上操作系統(tǒng)在底層會為我們維護(hù)一個連接隊列。服務(wù)端沒有accept的新連接會被放在這個連接隊列中,而這個隊列的最大長度是由backlog決定的。所以雖然服務(wù)端沒有使用accept獲取第二個客戶端發(fā)來的請求,但實際上鏈接已經(jīng)建成了

那么如何解決服務(wù)器只能給一個客戶端提供服務(wù)這個問題呢?? 很簡單只要提供多進(jìn)程版的服務(wù)器或者多線程版的就可以了

多進(jìn)程版TCP服務(wù)器

當(dāng)服務(wù)端調(diào)用accept函數(shù)獲取到新連接,并未新連接打開網(wǎng)絡(luò)文件后,讓當(dāng)前執(zhí)行流調(diào)用fork()函數(shù)創(chuàng)鍵子進(jìn)程,讓子進(jìn)程為父進(jìn)程獲取到的鏈接提供服務(wù),由于父子進(jìn)程是兩個不同的執(zhí)行流,父進(jìn)程創(chuàng)建子進(jìn)程后可以繼續(xù)從監(jiān)聽套接字中獲取新連接,不需要關(guān)心服務(wù)

子進(jìn)程會繼承父進(jìn)程的文件描述符表

文件描述符表是隸屬于一個進(jìn)程的,子進(jìn)程創(chuàng)建后會"復(fù)制"一份父進(jìn)程的文件描述符表,之后父子進(jìn)程之間會保持獨(dú)立性。對于套接字文件(網(wǎng)絡(luò)文件)也是一樣的,父進(jìn)程創(chuàng)建的子進(jìn)程也會繼承父進(jìn)程的套接字文件信息,此時子進(jìn)程也就能對特定的套接字文件進(jìn)行讀寫操作

等待子進(jìn)程問題

當(dāng)父進(jìn)程創(chuàng)建子進(jìn)程后,父進(jìn)程是必須等待子進(jìn)程退出的,以防止子進(jìn)程變成僵尸進(jìn)程造成內(nèi)存泄漏。如果服務(wù)端進(jìn)行阻塞式等待子進(jìn)程,那么服務(wù)端還是必須等待客戶端的服務(wù)完畢才能獲取下一個服務(wù)請求,顯然不合理。若采用非阻塞方式等待子進(jìn)程,那么服務(wù)端就必須將所有子進(jìn)程的PID保存下來,并每隔一段時間要對所有鏈接進(jìn)行檢測,顯然非常麻煩

總之,無論采用阻塞或非阻塞的方式等待子進(jìn)程,都不能很好的幫助我們將獲取鏈接和提供服務(wù)分離。

不等待子進(jìn)程退出的方式

1、 捕捉SIGCHLD信號,將其處理動作設(shè)置成忽略

2、讓父進(jìn)程創(chuàng)建子進(jìn)程,子進(jìn)程再創(chuàng)建孫子進(jìn)程,子進(jìn)程退出,孫子進(jìn)程就被操作系統(tǒng)領(lǐng)養(yǎng)并為客戶端進(jìn)行服務(wù)

捕捉SIGCHLD信號
  void Start() {    
    signal(SIGCHLD, SIG_IGN); 								// 忽略SIGCHLD信號    
    for (;;) {    
      // 獲取連接    
      struct sockaddr_in peer;    
      memset(&peer, 0, sizeof(peer));    
      socklen_t len = sizeof(peer);    
      int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);    
      if (sock < 0) {    
        std::cerr << "accept error, continue" << std::endl;    
        continue;    
      }    
      std::string client_ip = inet_ntoa(peer.sin_addr);      
      short client_port = ntohs(peer.sin_port);    
      cout << "get a new link->"  << sock << "[" << client_ip << ":" << client_port << "]" << endl;                                                                                               
      pid_t id = fork();    								// 創(chuàng)建子進(jìn)程執(zhí)行服務(wù)
      if (id == 0) {    
        Service(sock, client_ip, client_port);    
        exit(0);    
      }    
    }    
  }    

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二),網(wǎng)絡(luò)編程,計算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò)

孫子進(jìn)程提供服務(wù)
  void Start() {
     // signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD信號
    for (;;) {
      // 獲取連接
      struct sockaddr_in peer;
      memset(&peer, 0, sizeof(peer));
      socklen_t len = sizeof(peer);
      int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
      if (sock < 0) {
        std::cerr << "accept error, continue" << std::endl;
        continue;
      }
      std::string client_ip = inet_ntoa(peer.sin_addr);  
      short client_port = ntohs(peer.sin_port);
      cout << "get a new link->"  << sock << "[" << client_ip << ":" << client_port << "]" << endl;
        
        
        
      pid_t id = fork();
      if (id == 0) {    // 子進(jìn)程
        pid_t id = fork();
        if(id == 0){    // 孫子進(jìn)程
          Service(sock, client_ip, client_port);
          exit(0);
        }
        exit(0);
      }
      waitpid(id, NULL, 0);  // 直接將子進(jìn)程回收了
    }
  }
while :; do ps -axj | head -1 && ps -axj | grep tcp_server | grep -v grep; echo "############"; sleep 1; done  # 監(jiān)視腳本

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-aZOZXrxO-1688734711200)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230707151802143.png)]

可以看到子進(jìn)程直接就推出了,孫子進(jìn)程正在為客戶端提供服務(wù)。當(dāng)客戶端推出后,孫子進(jìn)程直接就被操作系統(tǒng)回收了。它的PPID為1號進(jìn)程,表明這是一個孤兒進(jìn)程

多線程版TCP服務(wù)器

創(chuàng)建進(jìn)程的成本非常高,而創(chuàng)建線程的成本就會小很多,因為線程本質(zhì)就是再進(jìn)程地址空間中運(yùn)行的,創(chuàng)建出來的線程共享大部分資源。因此實現(xiàn)多執(zhí)行流的服務(wù)器最好采用多線程進(jìn)行實現(xiàn)

while :; do ps -aL | head -1 && ps -aL | grep tcp_server| grep -v grep; echo "#########################"; sleep 1; done

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二),網(wǎng)絡(luò)編程,計算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò)

// 1、參數(shù)列表
struct Args{
  Args(int _sock, std::string& _ip, short _port)
    : sock(_sock), ip(_ip), port(_port) {}

  int sock;
  std::string ip;
  short port;
};

void Start() {
    for (;;) {
      // 獲取連接
      struct sockaddr_in peer;
      memset(&peer, 0, sizeof(peer));
      socklen_t len = sizeof(peer);
      int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
      if (sock < 0) {
        std::cerr << "accept error, continue" << std::endl;
        continue;
      }
      std::string client_ip = inet_ntoa(peer.sin_addr);  
      short client_port = ntohs(peer.sin_port);
      cout << "get a new link->"  << sock << "[" << client_ip << ":" << client_port << "]" << endl;
        
      // 2、編寫多線程部分
      pthread_t tid; 
      struct Args *args = new struct Args(sock, client_ip, client_port);
      pthread_create(&tid, NULL, Service, (void*)args); 
      pthread_detach(tid);
    }
  }

  // 3、將Service函數(shù)改為靜態(tài)函數(shù),使用struct Args* 指針將三個參數(shù)構(gòu)成結(jié)構(gòu)體傳進(jìn)去
  static void* Service(void* arg) {
    struct Args* args = (struct Args*)arg;
    int sock = args->sock;
    std::string client_ip = args->ip;
    short client_port = args->port;
    delete args;
      
      
      
    for (;;) {
#define BUFFER_SIZE 128
      char buffer[BUFFER_SIZE];
      ssize_t size = read(sock, buffer, BUFFER_SIZE - 1);
      if (size > 0) {          // 讀取成功
        buffer[size] = 0;
        std::cout << client_ip << ":" << client_port << " # " << buffer << std::endl;
        std::string response = "tcp server say # ";
        response += buffer;
        if (write(sock, response.c_str(), response.size()) < 0) {
          std::cerr << "write response error" << std::endl;
        } else {
          std::cout << "send response success" << std::endl;
        }
      } else if (size == 0) { // 對端關(guān)閉連接
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      } else {                // 讀取失敗
        std::cerr << "read request error" << std::endl;
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      }
    }
  }

該對線程服務(wù)器存在的問題

  • 每當(dāng)新線程到來時,服務(wù)端的主線程才會為客戶端創(chuàng)建新線程,而服務(wù)結(jié)束又會將該線程銷毀。就像我們?nèi)ナ程贸燥?,我們?nèi)チ耸程冒⒁滩砰_始做飯。效率低下。
  • 如果有大量的客戶端連接請求到來,計算機(jī)就要一一創(chuàng)建服務(wù)線程,線程越多CPU的壓力也就越大。因為CPU要在這些線程之間來回切換,線程間切換的成本就變得很高,此外線程變多,每個線程被調(diào)度到的時間就變長了,用戶體驗變差

解決方案

  • 可以預(yù)先創(chuàng)建一批線程,當(dāng)有客戶端請求連接到來時就為這些線程提供服務(wù)。而不是客戶端來了才創(chuàng)建線程
  • 當(dāng)某個線程對客戶端提供服務(wù)完成后,不讓該線程推出,讓該線程繼續(xù)給下一個客戶端提供服務(wù)。如果沒有可以讓線程先進(jìn)入休眠狀態(tài)
  • 服務(wù)端創(chuàng)建的一批線程數(shù)量不能太多。此外,如果有海量客戶端接連到來,可以將這些新來的連接放在等待隊列中進(jìn)行排隊,等服務(wù)端這一批線程有空閑線程后再將連接拿上來處理

實際解決上述問題就是要讓我們再服務(wù)端引入線程池。線程池可以預(yù)先存儲線程并使線程循環(huán)往復(fù)的工作,并且線程池中還有一個任務(wù)隊列可以用于存儲任務(wù)。如果有任務(wù)就從任務(wù)隊列中Pop任務(wù),并調(diào)用任務(wù)對應(yīng)的Run函數(shù)對任務(wù)進(jìn)行處理,如果沒有任務(wù)就進(jìn)入休眠狀態(tài)

線程池版TCP服務(wù)器

線程池的實現(xiàn)在多線程那一章已經(jīng)講過了,這里直接套用了

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>

#define THREAD_NUM 5


template<typename T>
class Thread_Pool{
public:
  static Thread_Pool* GetInstance(size_t _thread_num = THREAD_NUM);
  static void* Routine(void* arg);
  ~Thread_Pool();

  void PushTask(const T& task);
  void PopTask(T* task);

  

private:
  Thread_Pool(size_t _thread_num);
    
private
  bool IsEmpty() { return task_que.empty(); }
  void QueueLock() { pthread_mutex_lock(&mtx); }
  void QueueUnLock() { pthread_mutex_unlock(&mtx); }
  void Wait() { pthread_cond_wait(&cond, &mtx); }
  void Wakeup() { pthread_cond_signal(&cond); }

private:

  size_t thread_num;
  std::queue<T> task_que;
  pthread_mutex_t mtx;
  pthread_cond_t cond;
  static Thread_Pool* instance;
};

template<typename T>
Thread_Pool<T>* Thread_Pool<T>::instance = nullptr;

template<typename T>
Thread_Pool<T>* Thread_Pool<T>::GetInstance(size_t _thread_num) {
  if (instance == nullptr) 
    instance = new Thread_Pool(_thread_num);
  return instance;
}

template<typename T>
Thread_Pool<T>::Thread_Pool(size_t _thread_num) 
  : thread_num(_thread_num){
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);
    for (int i = 0; i < thread_num; i++) {
      pthread_t tid;
      pthread_create(&tid, NULL, Routine, (void*)this);
      pthread_detach(tid);
    }
  }

template<typename T>
Thread_Pool<T>::~Thread_Pool() {
  pthread_mutex_destroy(&mtx);
  pthread_cond_destroy(&cond);
}

template<typename T> 
void Thread_Pool<T>::PushTask(const T& task) {
  QueueLock();
  task_que.push(task);
  QueueUnLock();
  Wakeup();
}

template<typename T>
void* Thread_Pool<T>::Routine(void* arg) {
  Thread_Pool<T>* tp = (Thread_Pool<T>*)arg;
  tp->QueueLock();
  while (tp->IsEmpty()) {
    tp->Wait();
  }
  T* task = new T;
  tp->PopTask(task);
  tp->QueueUnLock();

  task->Run();
  // std::cout << task << std::endl; // for test
  delete task;
}

template<typename T>
void Thread_Pool<T>::PopTask(T* task) {
  *task = task_que.front();
  task_que.pop();
}

現(xiàn)在想向服務(wù)器中引入線程池,因此在服務(wù)器類中新增一個線程池的指針成員

  • 在實例化服務(wù)器對象時,先將線程池指針初始化為空
  • 當(dāng)服務(wù)器初始化完畢,進(jìn)入正常運(yùn)行階段使用GetInstance接口獲取單例線程池。
  • 主線程之后就用于獲取連接,然后將獲取到的客戶端ip, port以及打開的網(wǎng)絡(luò)文件sockfd打包成一個任務(wù)交給線程池的任務(wù)隊列

線程池中的線程就通過不斷獲取任務(wù)隊列中的任務(wù),通過task中包含的信息為客戶端提供服務(wù)

這實際也是一個生產(chǎn)消費(fèi)模型,其中監(jiān)聽進(jìn)程就是任務(wù)的生產(chǎn)者,線程池中的若干線程就是消費(fèi)者,交易場所就是線程池中的任務(wù)隊列

任務(wù)類的設(shè)計

任務(wù)類中必須包含服務(wù)器和客戶端進(jìn)行通信所需要的數(shù)據(jù)信息,包含網(wǎng)絡(luò)套接字,客戶端的IP,客戶端的端口號。表示該任務(wù)是為哪一個客戶端提供服務(wù)的,使用的是哪一個網(wǎng)絡(luò)文件

任務(wù)類中還必須帶有一個Run()方法,線程池中的線程拿到數(shù)據(jù)后交給Run方法對任務(wù)進(jìn)行處理(通信),這個方法實際就是上文實現(xiàn)的Service函數(shù),將其放入任務(wù)類中充當(dāng)Run()方法,但是這樣實際上并不利于軟件分層。我們可以給任務(wù)類新增一個仿函數(shù),當(dāng)任務(wù)執(zhí)行Run方法處理任務(wù)時就可以以回調(diào)的方式處理該任務(wù)

#pragma once 
#include <iostream>
#include "handler.hpp"

class Task{
public:
  Task() {};
  ~Task() {};
  Task(int _sockfd, std::string& _client_ip, short _client_port)
    : sockfd(_sockfd), client_ip(_client_ip), client_port(_client_port){}
  void Run(){ handler(sockfd, client_ip, client_port); }
private:
  int sockfd;
  std::string client_ip;
  short client_port;
  Handler handler;
  
};

仿函數(shù)類 Handler類

使用Handler類可以讓我們的服務(wù)器處理不同的任務(wù),實際想要怎么處理這個任務(wù)得由Handler函數(shù)定。如果想讓服務(wù)器處理其它任務(wù),只需要修改Handler當(dāng)中的()重載函數(shù)即可,比如可以增加一個int 類型參數(shù)flag,當(dāng)flag ==1 , flag == 2 ……的時候的就可以提供不同的處理方法

#include "comm.h"

class Handler{
public:
  Handler(){};
  ~Handler(){};
  void operator()(int sock, std::string client_ip, int client_port) {
    for (;;) {
#define BUFFER_SIZE 128
      char buffer[BUFFER_SIZE];
      ssize_t size = read(sock, buffer, BUFFER_SIZE - 1);
      if (size > 0) {          // 讀取成功
        buffer[size] = 0;
        std::cout << client_ip << ":" << client_port << " # " << buffer << std::endl;
        std::string response = "tcp server say # ";
        response += buffer;
        if (write(sock, response.c_str(), response.size()) < 0) {
          std::cerr << "write response error" << std::endl;
        } else {
          std::cout << "send response success" << std::endl;
        }
      } else if (size == 0) { // 對端關(guān)閉連接
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      } else {                // 讀取失敗
        std::cerr << "read request error" << std::endl;
        std::cout << client_ip << ":" << client_port << " quit..." << std::endl;
        close(sock);
        break;
      }
    }
  }
};

【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二),網(wǎng)絡(luò)編程,計算機(jī)網(wǎng)絡(luò),網(wǎng)絡(luò)

現(xiàn)在無論有多少個客戶端發(fā)送請求,服務(wù)端只會有5個線程為其提供服務(wù),線程池中的線程數(shù)不會因為客戶端的增多而增多。這些線程也不會因為客戶端的退出而退出
參考文章:「2021dragon」的文章
原文鏈接:https://blog.csdn.net/chenlong_cxy/article/details/124650187文章來源地址http://www.zghlxwxcb.cn/news/detail-533543.html

到了這里,關(guān)于【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程套接字(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 計算機(jī)網(wǎng)絡(luò)套接字編程實驗-TCP多進(jìn)程并發(fā)服務(wù)器程序與單進(jìn)程客戶端程序(簡單回聲)

    1.實驗系列 ·Linux NAP-Linux網(wǎng)絡(luò)應(yīng)用編程系列 2.實驗?zāi)康?·理解多進(jìn)程(Multiprocess)相關(guān)基本概念,理解父子進(jìn)程之間的關(guān)系與差異,熟練掌握基于fork()的多進(jìn)程編程模式; ·理解僵尸進(jìn)程產(chǎn)生原理,能基于|sigaction()或signal(),使用waitpid()規(guī)避僵尸進(jìn)程產(chǎn)生; ·

    2024年02月12日
    瀏覽(37)
  • 計算機(jī)網(wǎng)絡(luò)套接字編程實驗-TCP單進(jìn)程循環(huán)服務(wù)器程序與單進(jìn)程客戶端程序(簡單回聲)

    1.實驗系列 ·Linux NAP-Linux網(wǎng)絡(luò)應(yīng)用編程系列 2.實驗?zāi)康?·理解并掌握在程序運(yùn)行時從命令行讀取數(shù)據(jù)的C語言編程方法; ·理解并掌握基于命令參數(shù)設(shè)置并獲取IP與Port的C語言編程方法; ·理解并掌握套接字地址的數(shù)據(jù)結(jié)構(gòu)定義與地址轉(zhuǎn)換函數(shù)應(yīng)用; ·理解并掌握網(wǎng)絡(luò)字節(jié)序

    2024年02月11日
    瀏覽(43)
  • 計算機(jī)網(wǎng)絡(luò)--網(wǎng)絡(luò)編程(1)

    計算機(jī)網(wǎng)絡(luò)--網(wǎng)絡(luò)編程(1)

    簡單認(rèn)識一下傳輸層中的UDP和TCP: TCP:有鏈接,可靠傳輸,面向字節(jié)流,全雙工 UDP:無連接,不可靠傳輸,面向數(shù)據(jù)報,全雙工 有鏈接類似于打電話,通了就是有鏈接。沒通就一直在等待。 無連接類似于發(fā)短信,只管發(fā),不管到。 可靠傳輸就是保證信息傳輸?shù)目煽啃浴>?/p>

    2024年02月11日
    瀏覽(34)
  • 【計算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    【計算機(jī)網(wǎng)絡(luò)】4 Socket網(wǎng)絡(luò)編程

    目錄 寫在前面的話 概覽 環(huán)境 URL請求程序: 2. 系統(tǒng)時間查詢 服務(wù)端 T_TCPServer.py代碼 客戶端 T_TCPClient.py代碼 運(yùn)行效果 3. 網(wǎng)絡(luò)文件傳輸 服務(wù)端 TF_TCPServer.py代碼 運(yùn)行效果(后面加了遠(yuǎn)程功能,效果圖暫時還在本地) 4. 網(wǎng)絡(luò)聊天室 服務(wù)端 UDPServer.py代碼 客戶端 UDPClient.py代碼 運(yùn)

    2024年02月01日
    瀏覽(34)
  • 【計算機(jī)網(wǎng)絡(luò)】Socket編程

    【計算機(jī)網(wǎng)絡(luò)】Socket編程

    IP地址:公網(wǎng)IP,用于唯一標(biāo)識互聯(lián)網(wǎng)中的一臺主機(jī) 源IP,目的IP:對于一個報文來講,從哪來,到哪去。 源IP指將數(shù)據(jù)發(fā)送過來的IP地址,目的IP指將數(shù)據(jù)發(fā)送給下一個設(shè)備的IP地址(mac地址的變化) 意義: 指導(dǎo)一個報文該如何進(jìn)行路徑選擇,目的IP是讓我們根據(jù)目標(biāo)進(jìn)行路徑選

    2024年02月08日
    瀏覽(29)
  • 【計算機(jī)網(wǎng)絡(luò)】socket編程基礎(chǔ)

    【計算機(jī)網(wǎng)絡(luò)】socket編程基礎(chǔ)

    因特網(wǎng)上的每臺計算機(jī)都有一個唯一的IP地址,如果一臺主機(jī)上的數(shù)據(jù)要傳輸?shù)搅硪慌_主機(jī),那么對端主機(jī)的IP地址就應(yīng)該作為該數(shù)據(jù)傳輸時的目的IP地址。但僅僅知道目的IP地址是不夠的,當(dāng)對端主機(jī)收到數(shù)據(jù)后,對端還需要對該主機(jī)作出相應(yīng),因此對端主機(jī)也需要發(fā)送數(shù)據(jù)

    2024年02月15日
    瀏覽(37)
  • 計算機(jī)網(wǎng)絡(luò)技術(shù)與JAVA網(wǎng)絡(luò)編程URL編程-----JAVA入門基礎(chǔ)教程-----計算機(jī)網(wǎng)絡(luò)經(jīng)典

    import org.junit.jupiter.api.Test; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class URLTest { public static void main(String[] args) { //URL:統(tǒng)一資源定位符(種子),一個URL就定位著互聯(lián)網(wǎng)上某個資源的地址 //http:應(yīng)用層協(xié)議,IP地址,端口號,資源地址,參數(shù)

    2024年02月15日
    瀏覽(99)
  • 計算機(jī)網(wǎng)絡(luò)技術(shù)與JAVA網(wǎng)絡(luò)編程UDP編程-----JAVA入門基礎(chǔ)教程-----計算機(jī)網(wǎng)絡(luò)經(jīng)典

    import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.*; public class UDP { public static void main(String[] args) { DatagramSocket datagramSocket = null; try { datagramSocket = new DatagramSocket(); InetAddress inetAddress = InetAddress.getByName(\\\"127.0.0.1\\\"); int port = 9090; byte[] byte

    2024年02月15日
    瀏覽(36)
  • 【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(3)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 poll()????????? ?遵

    2024年02月09日
    瀏覽(26)
  • 【計算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(5)

    ?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計算機(jī)網(wǎng)絡(luò)協(xié)議,API 對關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡化了程序編程。 ? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。 connect()?????????

    2024年02月08日
    瀏覽(34)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包