网络基础:套接字编程,UDP和TCP通信程序

字节序

字节序:cpu对内存中数据进行存取的顺序

主机字节序的分类:小端、大端

小端:低地址存低位
大端:低地址存高位

在这里插入图片描述
编写代码判断主机字节序:

#include<iostream>
using namespace std;
void check_sys1()
{
	int a = 1;
	char* b = (char*)&a;
	if (*b == 1)
		cout << "小端" << endl;
	if (*b == 0)
		cout << "大端" << endl;
}
void check_sys2()
{
	//联合类型的所有数据共用一块内存,内存大小根据最大的数据类型决定
	union UN {
		int a;
		char b;
	}u;
	u.a = 1;
	if(u.b==1)
		cout << "小端" << endl;
	if(u.b==0)
		cout << "大端" << endl;
}
int main()
{
	check_sys1();
	check_sys2();
	return 0;
}

在这里插入图片描述

主机字节序对网络通信的影响:如果通信两端主机字节序不同,可能会造成数据二义性。

解决方案:订立网络通信字节序标准,规定网络中的数据都按照网络字节序进行存取。网络字节序-----其实是大端字节序

发送方将数据转换成网络字节序后进行发送,接收方根据自己的的主机字节序将接收到的数据进行转换。

字节序只针对存储单元大于一个字节的数据类型。
注意:字符串实际上是单字节存储,所以不需要转换。

套接字编程

socket 套接字编程:网络通信程序的编写

分类:UDP协议通信程序的/TCP协议通信程序

区别:
UDP协议:用户数据报协议
特点:无连接,不可靠,面向数据报
应用场景:实时性要求大于安全性要求----例如视频传输

TCP协议:传输控制协议
特点:面向连接,可靠,面向字节流
应用场景:安全性要求大于实时性要求----例如文件传输

客户端与服务端

在网络通信程序中,通信两端被分为客户端和服务端,
客户端:提供给客户的通信段,通常是通信程序中主动发起请求的一端。
客户端必须提前知道服务端的地址信息(ip地址+port端口)才能发送请求,通常是被提前写在应用程序中,并且通常是固定不变的。

服务端:通常是指被动接受请求,提供服务的通信端。
在这里插入图片描述

★ netstat命令 ★

netstat命令:查看当前网络状态信息
-a:查看所有
-t :查看TCP信息
-u:查看UDP信息
-n:不以服务名称显示,以具体地址端口显示
-p:查看当前网络状态对应的进程

UDP通信程序

通信流程:

1:38

接口:

1、创建套接字:int socket(int domain,int type,int protocol);

domain:地址域类型----指定使用的是什么样的地址结构:AF_INET----IPV4通信,使用IPV4地址结构
type:套接字类型;SOCK_STREAM:流式套接字 / SOCK_DGRAM:数据报套接字

注意:TCP协议必须使用SOCK_STREAM,UDP必须使用SOCK_DGRAM

protocol:本次通信所使用的协议;IPPROTO_IP=6 / IPPROTO_UDP=17(可以使用宏,也可以使用数字)
返回值:成功,返回一个文件描述符----操作句柄;失败,返回-1。

2、为套接字绑定地址信息:int bind(int sockfd,struct sockaddr* addr,socklen_t addrlen);

sockfd:socket() 创建套接字返回的操作句柄
addr:当前绑定的地址信息
socklen_t addrlen:地址信息长度
返回值:成功返回0;失败返回-1。

3、接收数据:ssize_t recvfrom(int sockfd,void* buf,int len,int flag,struct addr* srcaddr,socklen_t * addrlen);

ssize_t:有符号int;
size_t:无符号int

sockfd:创建套接字返回的操作句柄
buf:用于存放接收到的数据的空间地址
len:需要接受的数据长度
flag:选项标志,通常默认为0,表示阻塞接收
srcaddr:本条数据的源端地址信息
addrlen:输入输出参数,指定要接收多长的地址长度,但实际可能并没有那么长,所以还会返回实际接收到的地址长度
返回值:成功,返回实际接收到的数据长度;失败或出错返回-1。

4、发送数据:ssize_t sendto(int sockfd,void *data,int len,int flag,struct sockaddr* peeraddr,socklen_t addrlen);

sockfd:操作句柄
data:要发送的数据的首地址
len:要发送的数据长度
flag:默认为0,阻塞发送
peeraddr:对端地址信息
addrlen:地址结构长度
返回值:成功,返回实际发送的数据长度;失败,返回-1。

5、关闭套接字:int close(int fd);

fd:操作句柄

流程外的重要接口:

字节序转换接口:

unint32_t htonl(uint32_t hostlong);----32位数据主机字节序到网络字节序的转换
unint16_t htons(uint32_t hostshort);----16位数据主机字节序到网络字节序的转换
unint32_t ntohl(uint32_t netlong);----32位数据网络字节序到主机字节序的转换
unint16_t ntohs(uint32_t netshort);----16位数据网络字节序到主机字节序的转换
注意:port端口转换使用htons/ntohs,ip转换使用htonl/ntohl,不能混用。

将字符串点分十进制IP地址转换为整型网络字节序IP地址:

“192.168.2.2”————》0xc0a80202
in_addr_t inet_addr(const char* cp);

将网络字节序IP地址转换为字符串点分十进制IP地址:

0xc0a80202————》“192.168.2.2”
char* inet_ntoa(struct in_addr in);

以上接口仅限于IPV4地址使用

不限于IPV4的地址转换:

int inet_pton(int af,const char* src,void* dst);
const char* inet_ntop(int af,void* src,char* dst,socklen_t size);
(1)这两个函数的af参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为af参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
(2)第一个函数尝试转换由src指针所指向的字符串,并通过dst指针存放二进制结果,若成功则返回值为1,否则如果所指定的af而言输入字符串不是有效的表达式格式,那么返回值为0.
(3)inet_ntop进行相反的转换,从数值格式(src)转换到表达式(dst)。inet_ntop函数的src参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。size参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果size太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。

服务端代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>//字节序转换接口头文件
#include<netinet/in.h>//地址结构/协议类型头文件
#include<sys/socket.h>//套接字接口文件


int main()
{
  //1.创建套接字
  //int socket(地址域类型,套接字类型,协议类型);
  int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  if(sockfd<0)
  {
    perror("socket error:");
    return -1;
  }
  //2.为套接字绑定地址信息
  //int bind(操作句柄,地址结构信息,地址长度);
  struct sockaddr_in addr;//定义ipv4地址结构
  addr.sin_family=AF_INET;
  addr.sin_port=htons(9000);//设置端口
  addr.sin_addr.s_addr=inet_addr("192.168.85.128");
  int len=sizeof(addr);
  int ret=bind(sockfd,(struct sockaddr*)&addr,len);
  if(ret<0)
  {
    perror("bind error:");
      return -1;
  }
  while(1)
  {  
    //3.接收数据
    //recvfrom(句柄,空间,长度,标志,对端地址,地址长度)
    char buf[1024]={0};
    struct sockaddr_in paddr;
    int len=sizeof(struct sockaddr_in);

    ret= recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&paddr,&len);
    if(ret<0)
    {
      perror("recv error:");
        return -1;
    }
    uint16_t cport=ntohs(paddr.sin_port);
    char* cip=inet_ntoa(paddr.sin_addr);
    printf("client-[%s:%d]client say:%s\n",cip,cport,buf);
    //4.回复数据
    memset(buf,0x00,1024);
    printf("server say:");
    fflush(stdout);
    fgets(buf,1023,stdin);
    ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&paddr,len);
    if(ret<0)
    {
      perror("send error:");
      return -1;
    }
  }
  //5.关闭套接字
  close(sockfd);
  return 0;
}

客户端代码

头文件:

/*
 * 封装一个udp_socket类
 * 通过实例化的对象调用对应的成员接口实现udp客户端及服务端搭建
 */ 

#include<iostream>
#include<string>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<cstdio>
using namespace std;

class UdpSocket{
  private:
    int _sockfd;
  public:
    UdpSocket():_sockfd(-1)
    {}

    bool Socket()
    {
      _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(_sockfd<0){
        perror("socket error:");
          return false;
      }
      return true;
    }
    bool Bind(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;
     ret=bind(_sockfd,(struct sockaddr*)&addr,len);
     if(ret<0){
       perror("bind error:");
         return false;
     }
     return true;
    }
  
    bool Send(std::string &data,const std::string &ip,int 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=sendto(_sockfd,data.c_str(),data.size(),0,(struct sockaddr*)&addr,len);
    if(ret<0){
      perror("sendto error:");
      return false;
    }
     return true;
    }

    bool Recv(std::string *buf,std::string *ip=NULL,int  *port=NULL){
      struct sockaddr_in addr;
      socklen_t len=sizeof(struct sockaddr_in);
      char tmp[4096]={0};
      int ret=recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&addr,&len);
      if(ret<0){
        perror("recvfrom error:");
        return false;
      }
      buf->assign(tmp,ret); //申请ret长度的空间,并且将tmp的数据拷贝过去
      if(ip!=NULL){

        *ip=inet_ntoa(addr.sin_addr);
      }
      if(port!=NULL){
        *port=ntohs(addr.sin_port);
      }

      return true;
    }
    
    bool Close()
    {
      if(_sockfd!=-1){
        close(_sockfd);
      }
      return true;
    }
};

主程序

#include"udp_socket.hpp"

#define CHECK_RET(q) if((q)==false){return -1;}

int main(){
  UdpSocket sock;

  //1.创建套接字
  CHECK_RET(sock.Socket());
  //2.绑定地址信息(客户端不推荐执行这一操作)
  while(1){
    //3.发送数据
    std::cout<<"client say:";
    std::string buf;
    std::cin>>buf;
    CHECK_RET(sock.Send(buf,"192.168.85.128",9000));

    //4.接收数据
    buf.clear();
    CHECK_RET(sock.Recv(&buf));
    std::cout<<"server say:"<<buf<<endl;
  }
  //5.关闭套接字
  sock.Close();
  return 0;
}

输出结果:
在这里插入图片描述

TCP通信程序

通信流程:

在这里插入图片描述

创建连接的 原理:

  1. 服务端创建一个套接字 S1(包含sip+sport+tcp) ,并将其置于listen状态,开始处理客户端的请求(S1仅用于接收新的客户端连接);
  2. 客户端向服务端发送一个连接请求,服务端通过复制 S1 为该客户端创建一个新的套接字(包含sip+sport+dip+dport+tcp);

接口:

1、创建套接字:int socket(int domain,int type,int protocol);

2、绑定地址信息:int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

3、开始监听:int listen(int sockfd,int backlog);

sockfd:套接字描述符
backlog:服务端在同一时间能够处理的最大连接数
内核中有一个已完成连接队列----存储已经完成的客户端连接,如果这个队列已满,操作系统将不能再接收新的连接请求。
已完成连接队列的大小=backlog+1;

syn泛洪攻击:向服务端大量发送连接请求,阻碍服务端正常运转;解决办法:已完成连接队列,防火墙

4、客户端发送连接请求:int connect(int sockfd,struct sockaddr*srvaddr,socklen_t len);

sockfd: 套接字描述符
srvaddr: 服务端地址信息
len: 地址长度
返回值:成功返回0;失败返回-1。

5、服务端获取新建连接;int accept(int s, struct sockaddr *cliaddr, socklen_t *addrlen);

sockfd:监听套接字----服务端最早创建的,只用于获取新链接的套接字
cliaddr: 新的连接的客户端地址信息
addrlen:输入输出参数,指定地址信息长度,以及返回实际长度
返回值:新建连接的描述符----往后与客户端的通信都通过这个描述符完成。

6、收发数据:tcp通信因为socket中含有完整的五元组,所以收发数据数据都不需要指定地址
ssize_t send(int sockfd, void *data, int len, int flag);

sockfd: 描述符
data:要发送的数据
len:数据长度
flag:0----默认阻塞
返回值:成功返回实际发送的长度;失败返回-1;连接断开会触发异常

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

sockfd:描述符
buf:存放接收到的数据的空间地址
flag:0-----阻塞接收
返回值:成功返回实际收到的数据长度;出错返回-1;连接断开返回0.

7、关闭套接字:int close(int sockfd);

代码实例:

封装的socket类:


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

#define CHECK_RET(q) if((q)==false){return -1;}
#define LISTEN_BACKLOG 5

class TcpSocket{
  private:
    int _sockfd;
  public:
    TcpSocket():_sockfd(-1){}

    bool 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,const 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[0]);
      socklen_t len=sizeof(sockaddr);
      int ret=bind(_sockfd,(sockaddr*)&addr,len);
      if(ret<0){
        perror("bind error:");
        return false;
      }
      return true;
    }

    bool Listen(int backlog = LISTEN_BACKLOG){
      //listen(描述符,同一时间最大连接数);
      int ret=listen(_sockfd,backlog);
      if(ret<0){
        perror("listen error");
        return false;
      }
      return true;
    }

    bool Connect(const std::string &ip,const 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[0]);
      socklen_t len=sizeof(sockaddr);
      int ret=connect(_sockfd,(sockaddr*)&addr,len);
      if(ret<0){
        perror("connect error");
        return false;
      }
      return true;
    }

    bool Accept(TcpSocket* sock,std::string *ip=NULL,uint16_t *port=NULL){
      //int accept(监听套接字,获取客户端地址,长度)
      sockaddr_in addr;
      socklen_t len=sizeof(sockaddr_in);
      int newfd=accept(_sockfd,(sockaddr*)&addr,&len);
      if(newfd<0){
        perror("accept error");
        return false;
      }
      sock->_sockfd=newfd;
      if(ip!=NULL){
        *ip=inet_ntoa(addr.sin_addr);
      }
      if(port!=NULL){
        *port=ntohs(addr.sin_port);
      }
      return true;
    }

    bool Recv(std::string *buf){
      //int recv(描述符,空间,数据长度,标志位)
      //返回值:实际h获取的大小   0---连接断开;-1------出错了
      char tmp[4096]={0};
      int ret=recv(_sockfd,tmp,4096,0);
      if(ret<0){
        perror("recv error");
        return false;
      }else if(ret==0){
        printf("连接断开!\n");
        return false;
      }
      buf->assign(tmp,ret);
      return true;
    }

    bool Send(const std::string &data){
      //int send(描述符,数据,长度,标志位);
      int total=0;
      while(total<data.size()){
        int ret=send(_sockfd,&data[0]+total,data.size()-total,0);
        if(ret<0){
          perror("send error");
          return false;
        }
        total+=ret;
      }
      return true;
    }

    bool Close(){
      if(_sockfd!=-1){
        close(_sockfd);
      }
      return true;
    }   
};

服务端代码

#include "tcpsocket.hpp"

int main(int argc,char* argv[]){
 
  //命令行: ./tcp_srv 192.168.2.2   9000
  if(argc!=3){
    printf("格式:./tcp_src 192.168.2.2 9000\n");
    return -1;
  }

  std::string srvip=argv[1];
  uint16_t srvport=std::stoi(argv[2]);
  TcpSocket lst_sock;//监听套接字,仅用于接受新连接
  //1.创建套接字
  CHECK_RET(lst_sock.Socket());
  //2.绑定地址信息
  CHECK_RET(lst_sock.Bind(srvip,srvport));
  //3.开始监听
  CHECK_RET(lst_sock.Listen());
  while(1){
    //4.获取新连接
   TcpSocket cli_sock;//客户端套接字
   std::string cli_ip;//客户端ip地址
   uint16_t cli_port; //客户端端口
   bool ret=lst_sock.Accept(&cli_sock,&cli_ip,&cli_port);
   if(ret==false){
     //客户端发生错误,服务端不能退出,继续处理下一个客户端的请求
     continue;
   }
   std::cout<<"获取新建连接:"<<cli_ip<<":"<<cli_port<<std::endl;
   //5.收发数据---使用获取的新建套接字进行通信
   std::string buf;
   ret=cli_sock.Recv(&buf);
   if(ret==false){
     cli_sock.Close();
     continue;
   }
   std::cout<<"client say:"<<buf<<std::endl;
   buf.clear();
   std::cout<<"server say:";
   std::cin>>buf;
   ret=cli_sock.Send(buf);
  
   if(ret==false){
     cli_sock.Close();
   }

  }
  //6.关闭套接字
  lst_sock.Close();
  return 0;
}

客户端代码

#include "tcpsocket.hpp"

int main(int argc, char *argv[])
{
    //通过参数传入要连接的服务端的地址信息
    if (argc != 3) {
        printf("输入格式: ./tcp_cli srvip srvport\n");
        return -1;
    }
    std::string srvip = argv[1];
    uint16_t srvport = std::stoi(argv[2]);

    TcpSocket cli_sock;
    //1. 创建套接字
    CHECK_RET(cli_sock.Socket());
    //2. 绑定地址信息(不推荐)
    //3. 向服务端发起连接
    CHECK_RET(cli_sock.Connect(srvip, srvport));
    while(1){   
      //4. 收发数据
      std::string buf;
      std::cout<<"client say:";
      std::cin>>buf;
      CHECK_RET(cli_sock.Send(buf));   //发送数据
      buf.clear();
    
      CHECK_RET(cli_sock.Recv(&buf));   //接收数据
      std::cout<<"server say:"<<buf<<std::endl;
    }

    //5. 关闭套接字
    CHECK_RET(cli_sock.Close());
    return 0;
}

出现问题:

上述代码虽能正常运行,但同时也存在很大的问题:
accept、recv和send都是阻塞接口,任意一个接口的调用都有可能会导致服务端流程阻塞

本质原因:当前的服务端不知道什么时候又新连接到来,什么时候哪个客户端有数据到来,因此流程只能固定的去调用接口,但是这种调用方式可能会造成阻塞

解决方案:

多执行流并发处理----为每个客户端创建一个执行流负责这个客户端的通信
好处:

  1. 即使主线程被卡在获取新连接这一步,也不会影响其他执行流中客户端的通信
  2. 某个客户端阻塞,不会影响主线程和其他线程

具体操作:在主线程中获取新建连接,一旦获取到了就创建一个执行流,通过这个新建连接与客户端进行通信

多线程:普通线程与主线程数据共享,指定入口函数执行;主线程不能随意释放套接字,因为资源共享,一旦释放,其他线程无法使用

多进程:子进程复制了父进程,但是数据独有;要注意僵尸进程的处理,注意父子进程数据独有,父进程用不到套接字资源因此创建子进程之后要记得释放掉,否则会造成资源泄露。

优化后的代码

多线程:服务端

#include "tcpsocket.hpp"
#include <pthread.h>

void *thr_entry(void *arg)
{
    bool ret;
    TcpSocket *clisock = (TcpSocket*)arg;
    while(1) {
        //5. 收发数据--使用获取的新建套接字进行通信
        std::string buf;
        ret = clisock->Recv(&buf);
        if (ret == false) {
            clisock->Close();
            delete clisock;
            return NULL;
        }
        std::cout << "client say: " << buf << std::endl;

        buf.clear();
        std::cout << "server say: ";
        std::cin >> buf;
        ret = clisock->Send(buf);
        if (ret == false) {
            clisock->Close();
            delete clisock;
            return NULL;
        }
    }
    clisock->Close();
    delete clisock;
    return NULL;
}
int main(int argc, char *argv[])
{
    //通过程序运行参数指定服务端要绑定的地址
    // ./tcp_srv 192.168.2.2 9000
    if (argc != 3) {
        printf("usage: ./tcp_src 192.168.2.2 9000\n");
        return -1;
    }
    std::string srvip = argv[1];
    uint16_t srvport = std::stoi(argv[2]);
    TcpSocket lst_sock;//监听套接字
    //1. 创建套接字
    CHECK_RET(lst_sock.Socket());
    //2. 绑定地址信息
    CHECK_RET(lst_sock.Bind(srvip, srvport));
    //3. 开始监听
    CHECK_RET(lst_sock.Listen());
    while(1) {
        //4. 获取新建连接
        TcpSocket *clisock = new TcpSocket();
        std::string cliip;
        uint16_t cliport;
        bool ret = lst_sock.Accept(clisock, &cliip,&cliport);
        if (ret == false) {
            continue;
        }
        std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
        //创建线程专门负责与指定客户端的通信
        pthread_t tid;
        pthread_create(&tid, NULL, thr_entry, (void*)clisock);
        pthread_detach(tid);
    }
    //6. 关闭套接字
    lst_sock.Close();
    return 0;
}

多进程:服务端

#include "tcpsocket.hpp"
#include <signal.h>
#include <sys/wait.h>

void sigcb(int no)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

void worker(TcpSocket &clisock)
{  //child process
    bool ret;
    while(1) {
        //5. 收发数据--使用获取的新建套接字进行通信
        std::string buf;
        ret = clisock.Recv(&buf);
        if (ret == false) {
            clisock.Close();
            exit(0);
        }
        std::cout <<"client say: "<<buf<<std::endl;
        buf.clear();
        std::cout << "server say: ";
        std::cin >> buf;
        ret = clisock.Send(buf);
        if (ret == false) {
            clisock.Close();
            exit(0);
        }
    }
    clisock.Close();//释放的是子进程的clisock
    exit(0);

    return;
}
int main(int argc, char *argv[])
{
    //通过程序运行参数指定服务端要绑定的地址
    // ./tcp_srv 192.168.2.2 9000
    if (argc != 3) {
        printf("usage: ./tcp_src 192.168.2.2 9000\n");
        return -1;
    }
    signal(SIGCHLD, SIG_IGN);
    //signal(SIGCHLD, sigcb);
    std::string srvip = argv[1];
    uint16_t srvport = std::stoi(argv[2]);
    TcpSocket lst_sock;//监听套接字
    //1. 创建套接字
    CHECK_RET(lst_sock.Socket());
    //2. 绑定地址信息
    CHECK_RET(lst_sock.Bind(srvip, srvport));
    //3. 开始监听
    CHECK_RET(lst_sock.Listen());
    while(1) {
        //4. 获取新建连接
        TcpSocket clisock;
        std::string cliip;
        uint16_t cliport;
        bool ret = lst_sock.Accept(&clisock, &cliip,&cliport);
        if (ret == false) {
            continue;
        }
        std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
        pid_t pid = fork();
        if (pid < 0) {
            clisock.Close();
            continue;
        }else if (pid == 0) {
            worker(clisock);
        }
        //父子进程数据独有,父进程关闭不会对子进程造成影响
        clisock.Close();//释放的是父进程中的clisock
    }
    //6. 关闭套接字
    lst_sock.Close();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值