網絡編程(網絡基礎、套接字編程、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 / 路由器
  • 鏈路層:負責相鄰設備之間的數據傳輸; 以太網協議 / 交換機
  • 物理層:負責物理光電信號的傳輸; 以太網協議 / 集線器

網絡通信數據的封裝與分用流程

在這裏插入圖片描述
主機字節序:一個主機字節序的大小端取決於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套接字(socket)接口介紹

1、創建套接字

int socket(int domain, int type, int protocol);

domain:地址域,確定本次socket通信使用哪種協議版本的地址結果,不同協議版本有不同的地址結構。AF_INT IPV4網絡協議。
type:套接字類型(流式套接字- - -SOCK_STREAM / 數據報套接字- - -SOCK_DGRAM)
protocol:協議類型(通常就是傳輸層協議的選擇IPPROTO_TCP / IPPROTO_UDP),默認爲0,流式默認tcp / 數據報默認udp。
返回值:文件描述符 - - -非負整數- - -套接字所有其它接口的操作句柄;失敗返回 -1;

2、爲套接字綁定地址信息

int bind(int sockfd, struct sockaddr *addr, socklen_t len);

sockfd:創建套接字返回的操作句柄;
addr:要綁定的地址信息;
len:要綁定的地址信息長度;

在這裏插入圖片描述
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、接收數據

不僅僅接收數據,還要通過接收得知這個數是誰發的,以便於進行回覆。

ssize_t recvfrom(int sockfd, char *buf, int len, int flag, struct sockaddr *peer_addr, socklen_t *addrlen);

sockfd::socket操作句柄;
buf:一塊緩衝區,用於接收從接收緩衝區中取出的數據;
len:想要接收的數據長度;
flag:操作選項標誌,默認爲0,表示阻塞操作;
peer_addr:發送方的地址信息;
addrlen:想要獲取的地址信息長度以及返回實際長度;
返回值:成功返回實際接收到的數據字節長度;失敗返回-1;

4、發送數據

ssize_t sendto(int sockfd, char *data, int len, int flag, struct sockaddr *peer_addr, socklen_t addrlen);

sockfd:socket操作句柄;
data:要發送的數據首地址;
len:要發送數據長度;
flag:默認爲0,表示阻塞操作;
peer_addr:接收方的地址信息;
addrlen:地址信息長度;
返回值:成功返回實際發送的數據的字節長度,失敗返回-1;

5、關閉套接字

int socket(int domain, int type, int protocol);

使用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是隻需要知道對端地址就可以直接發送數據)。
在這裏插入圖片描述
在這裏插入圖片描述

tcp套接字(socket)接口介紹

1、創建套接字

int socket(int domain, int type, int protocol);

type:SOCK_DGRAM - - - 數據報套接字 / SOCK_STREAM - - -流式套接字
protocol:IPPROTO_TCP

2、綁定地址信息

int bind(int sockfd, struct sockaddr *addr, socklen_t len);

3、開始監聽

listen(int sockfd, int backlog);

sockfd:將哪個套接字設置爲監聽狀態,並且監聽狀態後可以開始接收客戶端連接請求。
backlog:同一時間的併發連接數,決定同一時間最多接收多少個客戶端的連接請求。
在這裏插入圖片描述
4、獲取新建連接
從已完成連接隊列中取出一個 socket,並且返回這個socket的描述符操作句柄。

int accept(int sockfd, struct sockaddr *cli_addr, socklen_t *len);

sockfd:監聽套接字,表示要獲取哪個 tcp 服務端套接字的新建連接;
cli——addr:這個新的套接字對應的客戶端地址信息
len:地址信息長度;
返回值:新建socket 套接字的描述符 - - - 外部程序中的操作句柄;

5、接受數據 / 發送數據
因爲 tcp 通信套接字中已經標識了五元組,因此不需要接收數據的時候獲取對方地址信息,發送數據的時候也不需要指定對方的地址信息。

ssize_t recv(int sockfd, char *buf, int len, int flag);

默認阻塞,沒有數據則等待,連接斷開返回0,不再阻塞

ssize_t send(int sockfd, char *data, int len, int flag);

默認阻塞,緩衝區數據滿了則等待,連接斷開則觸發SIGPIPE異常

6、關閉套接字

int close(fd);

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

int connect(int sockfd, struct sockaddr *srv_addr, int len);

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章