Linux套接字编程(udp通信、tcp通信)

socket套接字编程:网络通信程序的编写(接口+流程)

udp协议:用户数据报协议

特性:无连接,不可靠,面向数据报

应用场景:实时性要求大于安全性要求的场景---视频传输

tcp协议:传输控制协议

特性:面向连接,可靠传输,面向字节流

应用场景:安全性要求大于实时性要求的场景---文件传输

在网络通信程序中,通信两端被分为:客户端,服务器端

客户端:通常是提供给客户的通信端,通常是编写通信程序中主动发起请求的一端

服务端:通常指被动接受请求,提供服务的通信端

客户端必须提前能够知道服务端的地址信息(ip+port)是多少,服务端的地址信息通常是固定的,并且是提前提供给客户端的

字节序转换接口

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);32位数据主机到网络字节序转换

uint16_t htons(uint16_t hostshort);16位数据主机到网络字节序转换

uint32_t ntohl(uint32_t netlong);32位数据网络到主机字节序转换

uint16_t ntohs(uint16_t netshort);16位数据网络到主机字节序转换

port端口转换使用s,ip转换用l,不能混用

将字符串点分十进制IP地址转换为整型网络字节序IP地址(“192.168.2.2” -> 0xc0a80202)

in_addr_t inet_addr(const char* cp)

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

char* inet_ntoa(struct in_addr in)   in.s_addr = 0xc0a80202

netstat命令:查看当前的网络状态信息

-a:查看所有

-t:查看tcp信息

-u:查看udp信息

-n:不以服务名称显示

-p:查看当前网络状态对应的进程

udp通信程序的编写:套接字接口

server通信流程:

(1)创建套接字(在内核中创建一个socket结构体)

(2)为套接字绑定地址信息,在创建套接字创建的socket结构体中加入IP+port信息(告诉操作系统主机收到的哪些数据应该交给当前的这个socket的接收缓冲区中,确定发送数据的源端地址信息)

(3)接收数据(当前进程从指定的socket接收缓冲区中取出数据)

(4)发送数据(将要发送的数据放到socket发送缓冲区中,内核选择合适时候封装发送)

(5)关闭套接字

client通信流程:

(1)创建套接字

(2)为套接字绑定地址(大多数情况下会忽略第2步,在发送数据时若socket没有绑定地址,则系统会选择合适的地址进行绑定)

(3)发送数据

(4)接收数据

(5)关闭套接字

接口认识:

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

domain:地址域类型---AF_INET(IPv4通信,使用IPv4的地址结构)

type:套接字类型---SOCK_STREAM(tcp通信使用) / SOCK_DGRAM(udp通信使用)

protocol:本次通信协议---IPPROTO_TCP(6) / IPPROTO_UDP(17) / 0(使用默认协议)

返回值:返回一个文件描述符---操作句柄,失败返回-1

(2)为套接字绑定地址信息

int bind(int sockfd, struct sockaddr* addr, socklen_t addrlen)

sockfd:创建套接字返回的操作句柄

addr:当前绑定的地址信息

socklen_t:地址信息长度

返回值:成功返回0,失败返回-1

(3)接收数据

ssize_t recvfrom(int sockfd, void* buf, int len, int flag, struct sockaddr* srcaddr, socklen_t* addrlen)

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)

udp服务端代码实现:

#include<stdio.h>                                                                                                   
#include<unistd.h>    
#include<string.h>    
#include<arpa/inet.h>   //字节序转换接口头文件    
#include<netinet/in.h>   //地址结构头文件,协议类型头文件    
#include<sys/socket.h>   //套接字接口头文件    
    
int main()    
{    
  //1.创建套接字    
  int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);   //获取套接字文件描述符,套接字类型为数据报,协议类型为udp  
  if(sockfd < 0)
  {                 
    perror("socket error");
    return -1;                 
  }               
       
  //2.为套接字绑定地址信息
  struct sockaddr_in addr;   //定义IPv4地址结构 
  addr.sin_family = AF_INET;   //设置IPv4地址域类型 
  addr.sin_port = htons(9000);   //设置地址端口,并将主机字节序转换为网络字节序
  addr.sin_addr.s_addr = inet_addr("192.168.191.130");   //设置IP地址,将点分十进制IP地址转换为网络字节序
  int len = sizeof(addr);   //计算地址信息长度
  int ret = bind(sockfd, (struct sockaddr*)&addr, len);
  if(ret < 0)
  {
    perror("bind error");
    return -1;
  }

  //3.接收数据
  while(1)
  {
    char buf[1024] = {0};   //创建缓冲区用来存放数据
    struct sockaddr_in paddr;   //获取本条数据的源端地址信息
    socklen_t len = sizeof(struct sockaddr_in);   //指定要接收多长的地址结构,并且返回实际接收的地址长度
    ret = recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&paddr, &len);
    if(ret < 0)
    {                                                                                                               
      perror("recvfrom error");
      return -1;
    }
    uint16_t cport = ntohs(paddr.sin_port);   //获取传入数据客户端的端口,并将网络字节序转换为主机字节序
    char* cip = inet_ntoa(paddr.sin_addr);   //获取客户端的IP地址并转换为点分十进制
    printf("client-[%d:%s] say: %s\n", cport, cip, buf);

    //4.回复数据
    memset(buf, 0x00, 1024);   //清空buf将值全部置为0
    printf("server say:");
    fflush(stdout);   //刷新输出缓冲区
    fgets(buf, 1023, stdin);   //从标准输入获取一行数据
    ret = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&paddr, len);
    if(ret < 0)
    {
      perror("sendto error");
      return -1;
    }
  }
  //5.关闭套接字
  close(sockfd);
  return 0;
}                                                                          

封装实现一个udpsocket类:

//封装实现一个udpsocket类                                                                                           
//通过实例化的对象调用对应的成员接口,可以实现udp客户端或服务端的搭建    
    
#include<cstdio>    
#include<iostream>    
#include<string>    
#include<unistd.h>    
#include<arpa/inet.h>    
#include<netinet/in.h>    
#include<sys/socket.h>    
    
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());   //c_str()函数返回一个指向数组的指针,该数组包含一个以null结尾>的字符序列(即C字符串),表示string对象的当前值。
      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, const 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, (sockaddr*)&addr, &len);
      if(ret < 0)
      {
        perror("recvfrom error");
        return false;
      }
      buf->assign(tmp, ret);   //assign函数是为字符串指定一个新值,替换其当前内容。自带申请空间拷贝数据,即将tmp里ret长度的数据赋值给buf
      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;
    }
};                                                                                                                  

udp客户端的实现:

include"udpsocket.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.191.130", 9000));
  //4.接收数据
    buf.clear();
    CHECK_RET(sock.Recv(&buf));
    std::cout << "server say: " << buf << "\n";
  }
  //5.关闭套接字
  sock.Close();
  return 0;
}                                                                                                                   

tcp通信程序的编写:

server通信流程:

(1)创建套接字

(2)为套接字绑定地址信息

(3)开始监听(告诉系统可以开始处理客户端的连接请求了,系统会为每一个新客户端创建一个新的套接字)

(4)获取新建连接

(5)收发数据(使用的是新建的套接字)

(6)关闭套接字

client通信流程:

(1)创建套接字

(2)为套接字绑定地址信息(不推荐主动绑定)

(3)向服务端发起连接请求

(4)收发数据

(5)关闭套接字

接口认识:

(1)开始监听

int listen(int sockfd, int backlog)

sockfd:描述符

backlog:服务端能够在同一时间处理的最大连接数

(2)客户端发送连接请求

int connect(int sockfd, struct sockaddr* srvaddr, socklen_t len)

sockfd:描述符

srvaddr:服务端地址信息

len:地址长度

返回值:成功返回0;失败返回-1

(3)服务端获取新建连接

int accept(int sockfd, struct sockaddr* cliaddr, socklen_t* addrlen)

sockfd:监听套接字---服务端最早创建的套接字,只用于获取新建连接

cliaddr:新连接的客户端地址信息

addrlen:输入输出参数,指定地址信息长度,以及返回实际长度

返回值:新建连接的套接字描述符---往后与客户端的通信都通过这个描述符完成

(4)收发数据:tcp通信因为socket结构中包含完整五元组因此不需要指定地址

ssize_t send(int sockfd, void* data, int len, int flag)

sockfd:描述符

data:要发送的数据

len:要发送的数据长度

flag:0---阻塞发送

返回值:成功返回实际发送的数据长度;失败返回-1;连接断开会触发异常(当发送缓冲区剩余空间大小小于要发送数据长度时,会发送等于剩余空间大小的数据,即可以只传输与一部分的数据)

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

sockfd:描述符

buf:空间地址

len:要接收的数据长度

flag:0---阻塞接收

返回值:成功返回实际接收的数据长度;出错返回-1;连接断开返回0

封装实现一个tcpsocket类:

  #include<cstdio>                                                                                                  
  #include<iostream>    
  #include<unistd.h>    
  #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;   //tcp操作句柄    
      
    public:    
      TcpSocket():_sockfd(-1){}   //构造函数    
      
      bool Socket()   //创建套接字    
      {    
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   //套接字类型选择字节流,通信协议选择tcp    
        if(_sockfd < 0)    
        {    
          perror("socket error");
          return false;
        }
        return true;
      }
  
      bool Bind(const std::string &ip, const uint16_t port)   //绑定地址信息
      {
        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_in);
        int ret = bind(_sockfd, (sockaddr*)&addr, len);
        if(ret < 0)
        {
          perror("bind error");                                                                                     
          return false;
        }
        return true;
      }
  
      bool Listen(int backlog = LISTEN_BACKLOG)   //服务端开始监听
      {
        int ret = listen(_sockfd, backlog);
        if(ret < 0)
        {
          perror("listen error");
          return false;
        }
        return true;
      }
  
      bool Connect(const std::string &ip, const int port)   //客户端发送连接请求,需传入服务端的ip地址以及端口号
      {
        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_in);
        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)   //服务端获取新建连接
      {
        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)
      {
        char tmp[4096] = {0};
        int ret = recv(_sockfd, tmp, 4096, 0);
        if(ret < 0)
        {
          perror("recv error");
          return false;
        }
        else if(ret == 0)   //连接断开
        {
          printf("peer shutdown");                                                                                  
          return false;
        }
        buf->assign(tmp, ret);   //将数据拷贝到buf里
        return true;
      }
  
      bool Send(const std::string &data)
      {
        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;   //改变total的值
        }
        return true;
      }
                                                                                                                    
      bool Close()   //关闭套接字
      {
        if(_sockfd != -1)
        {
          close(_sockfd);
        }
          return true;
      }
  };                                                                                                                

tcp客户端的实现:

#include "tcpsocket.hpp"                                                                                            
    
int main(int argc, char* argv[])    
{    
  //通过参数传入要连接的服务端的地址信息    
  if(argc != 3)    
  {    
    printf("usage: ./tcp_cli srvip srvport\n");    
    return -1;    
  }    
  std::string srvip = argv[1];   //保存服务端的ip地址    
  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));    
  //4.收发数据    
  while(1)    
  {    
    std::string buf;
    std::cout << "client say: ";
    std::cin >> buf;   //从标准输入获取数据放到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;
}                                                                                                                   

tcp服务端的实现:

#include "tcpsocket.hpp"                                                                                            
    
int main(int argc, char* argv[])    
{    
  //通过程序运行参数指定服务端要绑定的地址    
  if(argc != 3)    
  {    
    printf("usage: ./tcp_srv 192.168.191.130 9000\n");    
    return -1;    
  }    
  std::string srvip = argv[1];   //获取服务端ip地址    
  uint16_t srvport = std::stoi(argv[2]);   //获取服务端端口    
  TcpSocket lst_sock;   //实例化服务端监听套接字对象    
  CHECK_RET(lst_sock.Socket());   //创建套接字    
  CHECK_RET(lst_sock.Bind(srvip, srvport));   //绑定地址信息    
  CHECK_RET(lst_sock.Listen());   //开始监听    
  while(1)   //循环获取新建连接    
  {    
    TcpSocket clisock;   //实例化创建的新建连接对象    
    std::string cliip;   //保存客户端的ip地址    
    uint16_t cliport;   //保存客户端的端口    
    bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);    
    if(ret == false)
    {
      continue;
    }
    //收发数据使用获取的新建套件字进行通信
    std::cout << "get new connect: " << cliip << "-" << cliport << "\n";
    std::string buf;
    ret = clisock.Recv(&buf);   //接收数据
    if(ret == false)
    {
      clisock.Close();
      continue;
    }
    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();
    }
  }
  lst_sock.Close();   //关闭套接字
  return 0;
}                                                                                                                   

注意:程序在服务端与一个客户端进行一次通信后,此时客户端再次向服务端发送数据会出现问题,服务端无法接收客户端发送的数据,客户端也无法正常发送数据。原因是程序卡在了服务端获取新建连接accept这个接口处,因此下面的收发数据都无法完成。

由于accept、recv、send都是阻塞接口,任意一个接口的调用,都有可能会导致服务端流程阻塞

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

解决方案:多执行流并发处理,为每一个客户端都创建一个执行流负责与这个客户端进行通信

好处:主线程卡在获取新建连接这里,不会影响客户端的通信;某个客户端的通信阻塞,也不会影响主线程以及其他线程

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

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

通过创建多线程完成对tcp服务端的实现:

#include "tcpsocket.hpp"                                                                                            
    
//线程入口函数,专门负责与客户端的通信    
void* thr_entry(void* arg)    
{    
  TcpSocket *clisock = (TcpSocket*)arg;    
  while(1)    
  {    
    //收发数据使用获取的新建套件字进行通信    
    std::string buf;    
    bool 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[])
{
  //通过程序运行参数指定服务端要绑定的地址
  if(argc != 3)                                                                                                     
  {
    printf("usage: ./tcp_srv 192.168.191.130 9000\n");
    return -1;
  }
  std::string srvip = argv[1];   //获取服务端ip地址
  uint16_t srvport = std::stoi(argv[2]);   //获取服务端端口
  TcpSocket lst_sock;   //实例化服务端监听套接字对象
  CHECK_RET(lst_sock.Socket());   //创建套接字
  CHECK_RET(lst_sock.Bind(srvip, srvport));   //绑定地址信息
  CHECK_RET(lst_sock.Listen());   //开始监听
  while(1)   //循环获取新建连接
  {
    TcpSocket *clisock = new TcpSocket();   //实例化创建的新建连接对象
    std::string cliip;   //保存客户端的ip地址
    uint16_t cliport;   //保存客户端的端口
    bool ret = lst_sock.Accept(clisock, &cliip, &cliport);
    if(ret == false)
    {
      continue;
    }
    std::cout << "get new connect: " << cliip << "-" << cliport << "\n";
    //创建线程专门负责与指定客户端的通信                                                                            
    pthread_t tid;
    pthread_create(&tid, NULL, thr_entry, (void*)clisock);
    pthread_detach(tid);   //线程分离,线程退出会自动释放资源,不需要被等待
  }
  lst_sock.Close();   //关闭套接字
  return 0;
}                                                                                                                   

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

通过创建子进程完成对tcp服务端的实现:

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

int main(int argc, char* argv[])
{
  //通过程序运行参数指定服务端要绑定的地址
  if(argc != 3)
  {
    printf("usage: ./tcp_srv 192.168.191.130 9000\n");
    return -1;
  }
  signal(SIGCHLD, SIG_IGN);   //忽略SIGCHLD信号,即子进程退出直接释放资源,不会成为僵尸进程
  std::string srvip = argv[1];   //获取服务端ip地址
  uint16_t srvport = std::stoi(argv[2]);   //获取服务端端口
  TcpSocket lst_sock;   //实例化服务端监听套接字对象
  CHECK_RET(lst_sock.Socket());   //创建套接字
  CHECK_RET(lst_sock.Bind(srvip, srvport));   //绑定地址信息
  CHECK_RET(lst_sock.Listen());   //开始监听
  while(1)   //循环获取新建连接
  {
    TcpSocket clisock;   //实例化创建的新建连接对象
    std::string cliip;   //保存客户端的ip地址
    uint16_t cliport;   //保存客户端的端口
    bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);
    if(ret == false)
    {
      continue;
    }
    //收发数据使用获取的新建套件字进行通信
    std::cout << "get new connect: " << cliip << "-" << cliport << "\n";
    pid_t pid = fork();   //创建子进程
    if(pid < 0)   
    {
      clisock.Close();
      continue;
    }
    else if(pid == 0)   //子进程完成与客户端的通信
    {  
      while(1)                                                                                                      
      {
        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);                                                                                                      
    }
    //父子进程数据独有,父进程关闭不会对子进程造成影响
    clisock.Close();   //释放的是父进程中的clisock
  }
  lst_sock.Close();   //关闭套接字
  return 0;
}                                                                                                                   

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值