套接字编程:TCP通信程序

简介: 套接字编程:TCP通信程序

一、服务端编写流程


1. 创建套接字


2. 为套接字绑定地址信息


3. 开始监听


将套接字状态置为LISTEN:


       1)告诉服务器,当前socket可以开始处理连接请求。


       2)若有客户端发送连接请求过来,服务器会为客户端创建一个新的socket,这个socket负责专门与该客户端进行通信。


4. 获取新建连接


       从已完成连接队列中取出一个新建套接字的描述符。这个描述符对应了指定的socket与指定客户端进行通信。


5. 收发数据


6. 关闭套接字


二、客户端编写流程


1. 创建套接字


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


3. 向服务器发起连接请求


       客户端的tcp套接字中也会保存完整的五元组。


4. 收发数据


5. 关闭套接字


三、相关接口


1. 创建套接字


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

   


       domain:地址域类型:      


               ipv4: AF_INET;


               ipv6:AF_INET6;


       type:套接字类型:


               SOCK_STREAM:提供字节流传输服务;


       protocol:协议类型:


               TCP:IPPROTO_TCP;


返回值:


       成功,返回一个套接字描述符;失败,返回-1。


2. 为套接字绑定地址信息


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


       sockfd:创建套接字返回的描述符;


       addr:要绑定的地址信息结构:


               ipv4:struct sockaddr_in;


               ipv6:struct sockaddr_in6;


       addrlen:地址信息长度;


返回值:


       成功,返回0;失败,返回-1。


3. 客户端:向服务器发起连接请求


int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


       sockfd:套接字描述符;


       *addr:服务器地址信息;


       addrlen:地址信息长度。


返回值:


       成功,返回0;失败,返回-1。


4. 服务端:监听接口


int listen(int sockfd, int backlog);


       sockfd:监听套接字描述符;


       backlog:服务器端同一时间最大并发连接数。


注意:第二个参数限制的是同一时间所能处理的最大连接请求数量,而不是服务器所能建立的总连接数量。


在内核中,存在一个socket连接队列,其最大容量就是backlog+1。如果连接队列已满,则新到的连接请求会被丢弃。


5. 服务端:获取新建连接


int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);


       listen_sockfd:监听套接字,决定获取的是哪个监听套接字的新建连接;


       addr:地址结构的空间首地址,用于接收新连接的客户端地址信息;


       *addrlen:用于指定想要获取的地址长度,以及返回实际的长度。


返回值:


       成功,返回新建连接的描述符;失败,返回-1。


6. 收发数据


       因为tcp通信的套接字中保存了完整的五元组,所以收发数据时,不需要再指定和获取对端的地址信息。


接收数据:


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


       sockfd:新建的套接字的描述符;


       buf:用于存放接收的数据的空间首地址;


       len:想要获取的数据长度;


       flag:0-阻塞接收;


返回值:


       成功,返回实际获取到的数据长度;出错,返回-1;连接断开,返回0。


发送数据:


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


       sockfd:新建的套接字的描述符;


       data:要发送的数据的空间首地址;


       len:要发送的数据长度;


       flag:0-默认阻塞发送;


返回值:


       成功,返回实际发送成功的数据长度;出错,返回-1。


7. 关闭套接字


int close(fd);


       fd:套接字描述符。


注:关闭套接字时,监听套接字一般不关闭,不需要与哪个客户端通信,则关闭那个对应的通信套接字即可。


四、代码实现


1. 头文件-套接字接口封装


#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
//封装TCPsocket类
#define MAX_LISTEN 5
#define CHECK_RETURN(X) if((X) == false) {return -1;}
class TCPsocket {
  private:
    int _sockfd;
  public:
    TCPsocket () : _sockfd(-1) {}
    //1.创建套接字
    bool Socket() {
      _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      if (_sockfd < 0) {
        perror("create socket error!");
        return false;
      }
      return true;
    } 
    //2.为套接字绑定地址信息
    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;
    }
    //客户端:3.向服务器发起连接请求
    bool Connect(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 = connect(_sockfd, (struct sockaddr*)&addr, len);
      if (ret < 0) {
        perror("connect error!");
        return false;
      }
      return true;
    }
    //服务端:3.开始监听
    bool Listen(int backlog = MAX_LISTEN) {
      int ret = listen(_sockfd, backlog);
      if (ret < 0) {
        perror("connect error!");
        return false;
      }
      return true;
    }
    //服务端:4. 获取新建连接
    bool Accept(TCPsocket *sock, std::string *ip = NULL, uint16_t *port = NULL) {
      struct sockaddr_in addr;
      socklen_t len = sizeof(struct sockaddr_in);
      int newfd = accept(_sockfd, (struct 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;
    }
    //4. 接收数据
    bool Recve(std::string *body) {
      char temp[4096] = {0};
      int ret = recv(_sockfd, temp, 4095, 0);
      if (ret < 0) {
        perror("recve error!");
        return false;
      }
      else if (ret == 0) {
        std::cout<<"peer shutdown!"<< std::endl;
        return false;
      }
      body -> assign(temp, ret);
      return true;
    }
    //5.发送数据
    bool Send(const std::string &body) {
      int ret = send(_sockfd, body.c_str(), body.size(), 0);
      if (ret < 0) {
        perror("send error!");
        return false;
      }
      return true;
    }
    //6.关闭套接字
    bool Close() {
      if (_sockfd != -1) close(_sockfd);
      return true;
    }
};


2. 服务端实现


2.1 多进程实现

#include "socket_tcp.hpp"
#include<signal.h>
int new_worker(TCPsocket& conn_sock) {
  pid_t pid = fork();
  if (pid < 0) {
    perror("fork error!");
    return -1;
  }
  else if(pid == 0){
    while (1) {
      std::string buf;
      bool ret = conn_sock.Recve(&buf);
      if (ret == false) {
        conn_sock.Close();
        break;
      }
      std::cout<<"clinet send: "<<buf<<std::endl;
      std::cout<<"server reply: ";
      fflush(stdout);
      std::cin>>buf;
      ret = conn_sock.Send(buf);
      if (ret == false) {
        conn_sock.Close();
        break;
      }
    }
    //出错,则关闭套接字,直接退出当前进程
    conn_sock.Close();
    exit(-1);
  }
  return 0;
}
int main() {
  signal(SIGCHLD, SIG_IGN);//或略进行退出信息;子进程退出则会直接释放资源
  TCPsocket sock;
  //1.创建套接字
  CHECK_RETURN(sock.Socket());
  //2。绑定地址信息
  CHECK_RETURN(sock.Bind("192.168.247.128", 8888));
  //3.开始监听
  CHECK_RETURN(sock.Listen());
  while (1) {
    //4.获取新建连接
    TCPsocket conn_sock;
    std::string client_ip;
    uint16_t client_port;
    bool ret = sock.Accept(&conn_sock, &client_ip, &client_port);
    if (ret == false) continue;
    std::cout<<"new connect:"<<client_ip<<"  "<<client_port<<std::endl;
    //5.使用新建连接与客户端通信
    //创建子进程负责通信
    new_worker(conn_sock);
    //父进程本质并不与客户端进行通信,所以父进程需要关闭该套接字,防止资源耗尽
    //因为父子进程数据独有,所以父进程的关闭不会影响子进程进行通信
    conn_sock.Close();
  }
  //6.关闭套接字
  sock.Close();
  return 0;
}


2.2 多线程实现


#include "socket_tcp.hpp"
#include<pthread.h>
void *entry(void *arg) {
  TCPsocket *conn_sock = (TCPsocket*)arg;
  while (1) {
    std::string buf;
    bool ret = conn_sock->Recve(&buf);
    if (ret == false) {
      conn_sock->Close();
      break;
    }
    std::cout<<"clinet send: "<<buf<<std::endl;
    std::cout<<"server reply: ";
    fflush(stdout);
    std::cin>>buf;
    ret = conn_sock->Send(buf);
    if (ret == false) {
      conn_sock->Close();
      break;
    }
  }
  conn_sock->Close();
  delete conn_sock;
}
bool new_worker(TCPsocket*conn_sock) {
  pthread_t tid;
  int ret = pthread_create(&tid, NULL, entry, (void*)conn_sock);
  if (ret != 0) {
    std::cout<<"create thread error!"<<std::endl;
    return false;
  }
  pthread_detach(tid);//设置分离属性,不关心返回值,退出直接释放资源
  return true;
}
int main() {
  TCPsocket sock;
  //1.创建套接字
  CHECK_RETURN(sock.Socket());
  //2。绑定地址信息
  CHECK_RETURN(sock.Bind("192.168.247.128", 8888));
  //3.开始监听
  CHECK_RETURN(sock.Listen());
  while (1) {
    //4.获取新建连接
    TCPsocket *conn_sock = new TCPsocket();
    std::string client_ip;
    uint16_t client_port;
    bool ret = sock.Accept(conn_sock, &client_ip, &client_port);
    if (ret == false) continue;
    std::cout<<"new connect:"<<client_ip<<"  "<<client_port<<std::endl;
    //5.使用新建连接与客户端通信
    new_worker(conn_sock);
    //注意:线程间共享文件描述符表,所以这里不能关闭套接字
  }
  //6.关闭套接字
  sock.Close();
  return 0;
}


3. 客户端实现


#include "socket_tcp.hpp"
int main(int argc, char *argv[]) {
  if (argc != 3) {
    std::cout<<"please add server address!"<<std::endl;
    std::cout<<"usage: ./clinet_tcp  ip  port"<<std::endl;
    return -1;
  }
  std::string server_ip = argv[1];
  uint16_t server_port = std::stoi(argv[2]);
  TCPsocket sock;
  //1.创建套接字
  CHECK_RETURN(sock.Socket());
  //2.绑定地址信息(客户端不推荐)
  //3.向服务器发起连接请求
  CHECK_RETURN(sock.Connect(server_ip, server_port));
  //4.与服务器通信
  while (1) {
    std::string buf;
    std::cout<<"client send:";
    fflush(stdout);
    std::cin>>buf;
    //发送数据
    bool ret = sock.Send(buf);
    if (ret == false) {
      perror("send error!");
      sock.Close();
      return -1;
    }
    //接收数据
    buf.clear();
    ret = sock.Recve(&buf);
    if (ret == false) {
      perror("recve error!");
      sock.Close();
      return -1;
    }
    std::cout<<"server reply:"<< buf << std::endl;
  }
  //5.关闭套接字
  sock.Close();
  return 0;
}


相关文章
|
网络协议 物联网 开发者
NB-IoT 通信之 TCP 收发数据 | 学习笔记
快速学习 NB-IoT 通信之 TCP 收发数据
815 0
NB-IoT 通信之 TCP 收发数据 | 学习笔记
|
4月前
|
网络协议 安全 Java
Java网络编程入门指南:TCP/IP协议与Socket通信
Java网络编程入门指南:TCP/IP协议与Socket通信
59 1
|
6月前
|
SQL 网络协议 前端开发
🚀超级简单的图解TCP/IP,看不懂来打我:OSI模型与通信示例🚀
🚀超级简单的图解TCP/IP,看不懂来打我:OSI模型与通信示例🚀
|
监控 网络协议 Java
【网络篇】第十篇——线程池版的TCP网络程序
【网络篇】第十篇——线程池版的TCP网络程序
【网络篇】第十篇——线程池版的TCP网络程序
|
监控 网络协议 安全
【网络篇】第九篇——多线程版的TCP网络程序
【网络篇】第九篇——多线程版的TCP网络程序
【网络篇】第九篇——多线程版的TCP网络程序
|
监控 网络协议
【网络篇】第八篇——多进程版的TCP网络程序
【网络篇】第八篇——多进程版的TCP网络程序
【网络篇】第八篇——多进程版的TCP网络程序
|
存储 网络协议
【网络篇】第七篇——网络套接字编程(三)(TCP详解)(二)
【网络篇】第七篇——网络套接字编程(三)(TCP详解)
【网络篇】第七篇——网络套接字编程(三)(TCP详解)(二)
|
网络协议 Linux
【网络篇】第七篇——网络套接字编程(三)(TCP详解)(一)
【网络篇】第七篇——网络套接字编程(三)(TCP详解)
【网络篇】第七篇——网络套接字编程(三)(TCP详解)(一)
|
监控 网络协议 安全
一文了解HTTP、HTTPS、TCP、UDP、Websocket(论点:概念、通信流程、异同点、应用领域)
一文了解HTTP、HTTPS、TCP、UDP、Websocket(论点:概念、通信流程、异同点、应用领域)
|
网络协议
Netty实现TCP通信
Netty实现TCP通信