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