天天看點

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

一、網絡基礎1

網絡的劃分

  • 區域網路(覆寫範圍在1000m内)
  • 城域網(覆寫範圍在20㎞内)
  • 廣域網(覆寫範圍大于20km),(網際網路 / 網際網路是更大的國際性的廣域網- - - 容災性更強,以太網 / 令牌環網 是組網方式)

IP位址

uint32_t - - - 無符号4個位元組的整數

1、在網絡中作為主機的唯一辨別,網絡中主機之間的定位(哪個主機與哪個主機之間進行通信),通過IP位址進行辨別。

2、網絡中每條資料中都會包含:源端的IP位址 / 對端的IP位址;

3、ipv4 : uint32_t - - -DHCP/NAT

ipv6 : uint8_t addr[16] - - - 推廣度還很低

端口号

uint16_t - - -無符号2個位元組的整數

在一台主機上唯一辨別一個程序 - - - - 編寫通信程式的時候,必須告訴計算機,發往哪個端口的資料應該交給我處理;

一個端口隻能被一個程序占用,然而一個程序可以使用多個端口

在網絡通信的每條資料中,都會包含有 源端端口 / 對端端口 - - - 辨別了這個資料從哪個程序發送出來,要交給哪個程序處理。

網絡通信協定

網絡通信證的資料格式約定,遵循統一通信協定标準,才能實作實質通信,實作網絡互聯

協定分層

根據通信場景的不同,提供的服務不同,使用的協定不同進行的層次劃分

典型協定分層

OSI七層參考模型:應用層、表示層、會話層、傳輸層、網絡層、鍊路層、實體層。

TCP/IP五層模型:應用層、傳輸層、網絡層、鍊路層、實體層。

  • 應用層:負責應用程式之間如何溝通;HTTP / FTP / DNS / DHCP…
  • 傳輸層:負責程序之間的資料傳輸; TCP / UDP
  • 網絡層:負責位址管理與路由選擇; IP / 路由器
  • 鍊路層:負責相鄰裝置之間的資料傳輸; 以太網協定 / 交換機
  • 實體層:負責實體光電信号的傳輸; 以太網協定 / 集線器

網絡通信資料的封裝與分用流程

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

主機位元組序:一個主機位元組序的大小端取決于cpu架構 - - - X86 / MIPS

int a =0x 01 02 03 04 -> 高位 000000001 00000010 00000011 00000100 低位

uchar *b = (uchar *)&a 記憶體低位址 b[0] b[1] b[2] b[3] 記憶體高位址

大端位元組序:低位址存高位 b[0]=01、 b[1]=02、 b[2]=03、 b[3]=04

小端位元組序:低位址存低位 b[0] =04、 b[1]=03、 b[2]=02、 b[3]=01

網絡位元組序:網絡通信中的位元組序标準(将自己的資料位元組序轉換成标準位元組序再進行傳輸) - - - 避免因為主機位元組序不同造成是資料二義。

  • 位元組序:cpu對記憶體中資料存儲是順序;
  • 主機位元組序的分類:大端位元組序、小端位元組序;

主機位元組序跟網絡通信的關系:不同主機位元組序的主機進行通信容易造成資料二義性。

網絡通信中,存儲單元大于一個位元組的資料類型需要進行網絡位元組序的轉換。

判斷一個主機的位元組序:

union{int a;  char b;}  tmp_t --- 聯合體成員共用同一份空間;
tmp_t tmp;   tmp.a=1 if(tmp.b==1){小端}
           

網絡通信程式編寫的時候,到底在傳輸層用 tcp 協定好還是 udp 協定好?

tcp:傳輸控制協定,面向連接配接,可靠傳輸,面向位元組流。(tcp 保證可靠傳輸,但是傳輸速度沒有 udp 快)。

udp:使用者資料協定,無連接配接,不可靠,面向資料報。(tcp應用于安全性要求高的場景/udp應用于實時性要求高的場景)。

二、udp套接字程式設計

網絡通信中的資料,必須包含:源端IP、源端端口、對端IP、對端端口、協定。

udp通信程式設計

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

用戶端不主動綁定位址端口,是為了降低端口沖突的機率。

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

udp套接字(socket)接口介紹

1、建立套接字

domain:位址域,确定本次socket通信使用哪種協定版本的位址結果,不同協定版本有不同的位址結構。AF_INT IPV4網絡協定。

type:套接字類型(流式套接字- - -SOCK_STREAM / 資料報套接字- - -SOCK_DGRAM)

protocol:協定類型(通常就是傳輸層協定的選擇IPPROTO_TCP / IPPROTO_UDP),預設為0,流式預設tcp / 資料報預設udp。

傳回值:檔案描述符 - - -非負整數- - -套接字所有其它接口的操作句柄;失敗傳回 -1;

2、為套接字綁定位址資訊

sockfd:建立套接字傳回的操作句柄;

addr:要綁定的位址資訊;

len:要綁定的位址資訊長度;

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

bind 可以綁定不同的位址結構,為了實作接口統一,是以使用者定義位址結構的時候,定義自己需要的位址結構(例如:ipv4就使用struct sockaddr_in),但是進行綁定的時候,統一類型強轉成為sockaddr* 類型。

bind(fd, struct sockaddr *addr, len);
{
if(addr->sa_family == AF_inet)
	{
	//綁定IPV4位址資訊,這個結構體按照sockaddr_in進行解析
	}
else if(addr->sa_family == AF_INET6)
	{//IPV6位址綁定,按照IPV6位址結構解析}
else if(addr->sa_family == AF LOCAL) 
	{}
}
           

3、接收資料

不僅僅接收資料,還要通過接收得知這個數是誰發的,以便于進行回複。

sockfd::socket操作句柄;

buf:一塊緩沖區,用于接收從接收緩沖區中取出的資料;

len:想要接收的資料長度;

flag:操作選項标志,預設為0,表示阻塞操作;

peer_addr:發送方的位址資訊;

addrlen:想要擷取的位址資訊長度以及傳回實際長度;

傳回值:成功傳回實際接收到的資料位元組長度;失敗傳回-1;

4、發送資料

sockfd:socket操作句柄;

data:要發送的資料首位址;

len:要發送資料長度;

flag:預設為0,表示阻塞操作;

peer_addr:接收方的位址資訊;

addrlen:位址資訊長度;

傳回值:成功傳回實際發送的資料的位元組長度,失敗傳回-1;

5、關閉套接字

使用c++封裝一個 UdpSocket 類,執行個體化的每一個對象都是udp 套接字,并且能夠通過成員接口實作Udp通信流程。

class UspSocket
{
public:
	bool Socket();   //建立套接字
	bool Bind(const std::string &ip, uint16_t prot);  //為套接字綁定位址資訊
	bool Recv(std::string *buf, std::string *ip, uint16_t *port);   //接收資料,擷取發送端位址資訊
	bool Send(const std::string &data, onst std::string &ip, const uint16_t port);   //發送資料
	bool Close();   //關閉套接字
private:
	int _sockfd;
}
           

網絡位元組序的轉換接口

uint32_t htonl(uint32_t hostlong);   //hton ---主機位元組序到網絡位元組序的轉換
uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);   //ntoh --- 網絡位元組序到主機位元組序的轉換
uint16_t ntohs(uint16_t netshort);

in_addr_t inet_addr(const char *cp);    //将字元串的點分十進制IP位址轉換成網絡位元組序的整數IP位址

char *inet_ntoa(struct in_addr in);     //将網絡位元組序的整數IP位址轉換成字元串點分十進制IP位址

const char *inet_ntop(int af,const void *src, char *dst, socklen_t size);   //将網絡位元組序的整數IP位址,轉換成字元串的IP位址(相容IPV6)

int inet_pton(int af, const char *src, void *dst);   //将字元串的IP位址轉換成網絡位元組序的整數IP位址
           

udpsocket服務端(C語言)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>//位址結構體定義的頭檔案
#include <arpa/inet.h>//位元組序轉換接口的頭檔案
#include <sys/socket.h>//套接字接口的頭檔案


int main()
{
    uint16_t port = 9000;
    char *ip = "172.31.43.144";
    //建立套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //綁定位址資訊: 1.定義位址結構 / 2. 位址資訊指派 / 3. 進行綁定 
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//指派位址域類型
    addr.sin_port = htons(port);//指派端口
    addr.sin_addr.s_addr = inet_addr(ip);//指派IP位址 
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(sockfd, (struct sockaddr*)&addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    //接收資料: 不但要接收資料,還要接收發送方位址資訊
    char tmp[4096] = {0};
    struct sockaddr_in cli_addr;
    char cli_ip[24] = {0};
    uint16_t cli_port = 0;
    ret = recvfrom(sockfd, tmp, 4096, 0, (struct sockaddr*)&cli_addr, &len);
    if (ret < 0) {
        perror("recvfrom error");
        return -1;
    }
    strcpy(cli_ip, inet_ntoa(cli_addr.sin_addr));
    cli_port = ntohs(cli_addr.sin_port);
    //發送資料: 将接收到的資料在回送給用戶端
    ret = sendto(sockfd, tmp, ret, 0, (struct sockaddr*)&cli_addr, len);
    if (ret < 0) {
        perror("sendto error");
        return -1;
    }
    //關閉套接字
    close(sockfd);
    return 0;
}

           

udpsocket服務端(C++語言)

#include <cstdio>
#include <string>
#include <netinet/in.h>//包含位址結構資訊
#include <arpa/inet.h>//位元組序轉換接口
#include <sys/socket.h>//套接字接口資訊

class UdpSocket {
    public:
        UdpSocket():_sockfd(-1){}
        bool Socket() {//建立套接字
            //socket(位址域, 套接字類型, 協定類型)
            _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if (_sockfd < 0) {
                perror("socket error");
                return false;
            }
            return true;
        }
        // 為套接字綁定位址資訊
        bool Bind(const std::string &ip,  uint16_t port) {
            //定義IPV4位址結構 struct sockaddr_in
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);//htons将主機位元組序短整型資料轉換為網絡位元組序資料
            addr.sin_addr.s_addr = inet_addr(ip.c_str());//将字元串IP位址轉換為網絡位元組序
            //bind(描述符, 位址資訊, 位址資訊長度)
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
            if (ret < 0) {
                perror("bind error");
                return false;
            }
            return true;
        } 
        //接收資料,擷取發送端位址資訊
        bool Recv(std::string *buf,  std::string *ip=NULL,  uint16_t *port=NULL) {
            //recvfrom(套接字句柄,接收緩沖區,資料長度,标志, 源端位址,位址長度)
            struct sockaddr_in peer_addr;
            socklen_t len = sizeof(struct sockaddr_in);
            char tmp[4096] = {0};
            int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
            if (ret < 0) {
                perror("recvfrom error");
                return false;
            }
            buf->assign(tmp, ret); // assign從指定字元串中截取指定長度的資料到buf中
            if (port != NULL) {
                *port = ntohs(peer_addr.sin_port);//網絡位元組序到主機位元組序的轉換
            }
            if (ip != NULL) {
                *ip = inet_ntoa(peer_addr.sin_addr);//網絡位元組序到字元串IP位址的轉換
            }
            return true;
        }
        // 發送資料
        bool Send(const std::string &data, const std::string &ip, const uint16_t port) {
            //sendto(套接字句柄,資料首位址,資料長度,标志,對端位址資訊,位址資訊長度)
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = sendto(_sockfd, data.c_str(), data.size(), 0, 
                    (struct sockaddr*)&addr, len);
            if (ret < 0) {
                perror("sendto error");
                return false;
            }
            return true;
        }
        bool Close(){
            if (_sockfd > 0) {
                close(_sockfd);
                _sockfd = -1;
            }
            return true;
        }// 關閉套接字
    private:
        int _sockfd;
};

           

udpsocket用戶端(C語言)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main()
{
    //1.建立套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //2.綁定位址資訊(不推薦)
    //3.發送資料: 要給誰發送什麼資料--發送的對端位址一定是服務端綁定的位址
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(9000);
    srv_addr.sin_addr.s_addr = inet_addr("172.31.43.144");
    socklen_t len = sizeof(struct sockaddr_in);
    char tmp[1024] = {0};
    fgets(tmp, 1024, stdin);
    sendto(sockfd, tmp, strlen(tmp), 0, (struct sockaddr*)&srv_addr, len);
    //4.接收資料
    char buf[1024] = {0};
    //對于用戶端來說,本身久知道服務端位址,是以其實根本不用接收服務端位址資訊
    recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&srv_addr, &len);
    printf("server say:%s\n", buf);
    //5.關閉套接字
    close(sockfd);
    return 0;
}

           

udpsocket用戶端(C++語言)

#include <iostream>
#include <string>
#include "udpsocket.hpp"

#define CHECK_RET(q) if((q)==false){return -1;}
int main (int argc, char *argv[])
{
    //用戶端參數擷取的IP位址是服務端綁定的位址,也就是用戶端發送資料的目标位址
    //不是為了自己綁定的
    if (argc != 3) {
        std::cout << "Usage: ./udp_cli ip port\n";
        return -1;
    }
    std::string srv_ip = argv[1];
    uint16_t srv_port = std::stoi(argv[2]);

    UdpSocket cli_sock;
    //建立套接字
    CHECK_RET(cli_sock.Socket());
    //綁定位址(不推薦)
    while(1) {
        //發送資料
        std::cout << "client say:";
        std::string buf;
        std::cin >> buf;
        if (buf == "quit") {
            break;
        }
        CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
        //接收資料
        buf.clear();
        CHECK_RET(cli_sock.Recv(&buf));//預設參數可以不用賦予
        std::cout << "server say: " << buf << std::endl;
    }
    //關閉套接字
    cli_sock.Close();
    //...
    
    return 0;
}

           

三、tcp套接字程式設計

面向連接配接,可靠傳輸,面向位元組流。

面向連接配接:必須建立了連接配接保證雙方都具有資料收發的能力,才能開始通信;(udp是隻需要知道對端位址就可以直接發送資料)。

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)
網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

tcp套接字(socket)接口介紹

1、建立套接字

type:SOCK_DGRAM - - - 資料報套接字 / SOCK_STREAM - - -流式套接字

protocol:IPPROTO_TCP

2、綁定位址資訊

3、開始監聽

sockfd:将哪個套接字設定為監聽狀态,并且監聽狀态後可以開始接收用戶端連接配接請求。

backlog:同一時間的并發連接配接數,決定同一時間最多接收多少個用戶端的連接配接請求。

網絡程式設計(網絡基礎、套接字程式設計、udp/tcp用戶端與服務端)

4、擷取建立連接配接

從已完成連接配接隊列中取出一個 socket,并且傳回這個socket的描述符操作句柄。

sockfd:監聽套接字,表示要擷取哪個 tcp 服務端套接字的建立連接配接;

cli——addr:這個新的套接字對應的用戶端位址資訊

len:位址資訊長度;

傳回值:建立socket 套接字的描述符 - - - 外部程式中的操作句柄;

5、接受資料 / 發送資料

因為 tcp 通信套接字中已經辨別了五元組,是以不需要接收資料的時候擷取對方位址資訊,發送資料的時候也不需要指定對方的位址資訊。

預設阻塞,沒有資料則等待,連接配接斷開傳回0,不再阻塞

預設阻塞,緩沖區資料滿了則等待,連接配接斷開則觸發SIGPIPE異常

6、關閉套接字

7、向服務端發送連接配接請求

srv_addr:服務端位址資訊 - - - 給誰發送連接配接請求

connect這個接口也會在sockfd 的套接字socket 中描述對端位址資訊

封裝一個TcpSocket類

每一個執行個體化的對象都是一個socket 通信連接配接,通過這個對象的成員完成通信流程

class TcpSocket{
public:
	 TcpSocket();
	 bool Socket();
	 bool Bind(const std::string &ip, uint16_t port);
	 bool Listen(int backlog = MAX_LISTEN);
	 bool Accept(TcpSocket *new_sock, std::string *ip = NULL, uint16_t *port = NULL);
	 bool Recv(std::string *buf);
	 bool Send(const std::string &data);
	 bool Close();
	 bool Connect(const std::string &ip, uint16_t port);
private:
	int _sockfd;
}
           

while(1)

{

  • 1、擷取新連接配接;

    一旦擷取到一個新連接配接,就啟動一個新的執行流(多線程/多程序),讓這個新的執行流去與用戶端進行通信

    a、因為沒有新連接配接到來的阻塞,不會影響與用戶端的通信

    b、與用戶端通信的阻塞不會影響擷取新連接配接

  • 2、通過新連接配接接收指定用戶端資料;
  • 3、通過新連接配接向指定用戶端發送資料;

}

目前,在一個執行流中,完成好多個操作,擷取新連接配接、接收資料局、發送資料,然而這幾個操作都有可能導緻流程阻塞,因為我們在固定流程下,有可能會對沒有資料到來的socket進行操作,是以導緻阻塞。

tcppsocket(C++頭檔案hpp)

#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

//listen的第二個參數決定同一時間能夠接收多少用戶端連接配接
//并不決定整體通信能夠接收多少用戶端連接配接
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q)==false){return -1;}

class TcpSocket {
    public:
        TcpSocket ():_sockfd(-1){}
        bool Socket() {
            //socket(位址域, 套接字類型, 協定類型)
            _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (_sockfd < 0) {
                perror("socket error");
                return false;
            }
            return true;
        }
        bool Bind(const std::string &ip, uint16_t port) {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
            if (ret < 0) {
                perror("bind error");
                return false;
            }
            return true;
        }
        bool Listen(int backlog = MAX_LISTEN) {
            //listen(套接字描述符, 最大并發連接配接數)
            int ret = listen(_sockfd, backlog);
            if (ret < 0) {
                perror("listen error");
                return false;
            }
            return true;
        }
        bool Accept(TcpSocket *new_sock,  std::string *ip=NULL, uint16_t *port=NULL) {
            //建立套接字描述符 = accept(監聽套接字描述符, 用戶端位址資訊,位址長度);
            struct sockaddr_in addr;
            socklen_t len = sizeof(addr);
            int new_fd = accept(_sockfd, (struct sockaddr*)&addr, &len);
            if (new_fd < 0) {
                perror("accept error");
                return false;
            }
            new_sock->_sockfd = new_fd;
            if (ip != NULL) {
                (*ip) = inet_ntoa(addr.sin_addr);
            }
            if (port != NULL) {
                *port = ntohs(addr.sin_port);
            }
            return true;
        }
        bool Recv(std::string *buf) {
            //recv(通信套接字描述符,緩沖區首位址,接收資料長度, 标志位-0阻塞)
            char tmp[4096] = {0};
            int ret = recv(_sockfd, tmp, 4096, 0);
            if (ret < 0) {
                perror("recv error");
                return false;
            }else if (ret == 0) {//recv預設阻塞,沒有資料就會等待,傳回0,表示連接配接斷開
                printf("connection broken\n");
                return false;
            }
            buf->assign(tmp, ret);
            return true;
        }
        bool Send(const std::string &data) {
            //send(描述符,要發送資料首位址,要發送的資料長度,标志位-0阻塞)
            int ret = send(_sockfd, data.c_str(), data.size(), 0);
            if (ret < 0) {
                perror("send error");
                return false;
            }
            return true;
        }
        bool Close() {
            if (_sockfd > 0) {
                close(_sockfd);
                _sockfd = -1;
            }
            return true;
        }
        bool Connect(const std::string &ip, uint16_t port) {
            //向服務端發起連接配接
            //connect(描述符, 服務端位址資訊, 位址資訊長度)
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
            if (ret < 0) {
                perror("connect error");
                return false;
            }
            return true;
        }
    private:
        int _sockfd;
};
           

tcppsocket服務端(C++語言)

/*1. 建立套接字
  2. 綁定位址資訊
  3. 開始監聽
  4. 擷取新連接配接
  5. 收發資料
  6. 關閉套接字
*/
#include <iostream>
#include "tcpsocket.hpp"

int main (int argc, char *argv[])
{
    if (argc != 3) {
        std::cout << "Usage: ./tcp_srv ip port\n";
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    
    TcpSocket lst_sock;
    CHECK_RET(lst_sock.Socket());//建立監聽套接字
    CHECK_RET(lst_sock.Bind(ip, port));//為監聽套接字綁定位址
    CHECK_RET(lst_sock.Listen());//開始監聽
    while(1) {
        TcpSocket new_sock;
        bool ret = lst_sock.Accept(&new_sock);//通過監聽套接字擷取建立連接配接
        if (ret == false) {
            continue;//服務端不能因為或一個建立套接字失敗就退出
        }
        std::string buf;
        new_sock.Recv(&buf);//通過建立連接配接與指定用戶端進行通信
        std::cout << "client say: " << buf << std::endl;

        buf.clear();
        std::cout << "server say: ";
        std::cin >> buf;
        new_sock.Send(buf);
    }
    lst_sock.Close();
    return 0;
}
           

tcppsocket用戶端(C++語言)

/*
	實作tcp用戶端流程
    1. 建立套接字     
    2. 綁定位址資訊(不推薦)
    3. 向服務端發起連接配接請求
    4. 收發資料
    5. 關閉套接字
*/
#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"

void sigcb(int no) {
    //SIGCHLD信号是一個非可靠信号,有可能丢失
    //是以在一次信号進行中,就需要處理到沒有子程序退出為止
    while(waitpid(-1, NULL, WNOHANG) > 0);//傳回值大于0,表示有子程序退出
}
int main (int argc, char *argv[])
{
    if (argc != 3) {
        std::cout << "Usage: ./tcp_srv ip port\n";
        return -1;
    }
    signal(SIGCHLD, sigcb);
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    
    TcpSocket lst_sock;
    CHECK_RET(lst_sock.Socket());//建立監聽套接字
    CHECK_RET(lst_sock.Bind(ip, port));//為監聽套接字綁定位址
    CHECK_RET(lst_sock.Listen());//開始監聽
    while(1) {
        TcpSocket new_sock;
        bool ret = lst_sock.Accept(&new_sock);//通過監聽套接字擷取建立連接配接
        if (ret == false) {
            continue;//服務端不能因為或一個建立套接字失敗就退出
        }
        int pid = fork();//子程序複制父程序,父程序有的子程序都有
        if (pid == 0) {
            while(1) {
                std::string buf;
                new_sock.Recv(&buf);//通過建立連接配接與指定用戶端進行通信
                std::cout << "client say: " << buf << std::endl;

                buf.clear();
                std::cout << "server say: ";
                std::cin >> buf;
                new_sock.Send(buf);
            }
            new_sock.Close();//new_sock父子程序各有各的  ,父子程序資料獨有
            exit(0);
        }
        new_sock.Close();//父程序關閉自己的不使用的socket,不影響子程序
    }
    lst_sock.Close();
    return 0;
}
           

繼續閱讀