TCP通讯程序的编写

目录

1.TCP和UDP区别

2.TCP通讯程序的编写流程

3.实现一次循环通信

4.改进---多进程方式实现多次通信 

5.改进---多线程方式实现多次通信 

6.改进---多线程方式+字典方式实现多次通信 


 

1.TCP和UDP区别

TCP:面向连接、可靠传输、面向字节流  应用场景:文件传输(安全性高于实时性)

UDP:无连接、不可靠、面向数据报  应用场景:视频、音频(实时性高于安全性)

2.TCP通讯程序的编写流程

服务器端:

  1. 创建套接字
  2. 为套接字绑定地址信息
  3. 开始监听---将套接字状态置为LISTEN 告诉系统这个套接字可以开始处理连接,tcp服务器会为每个客户端创建一个新的套接字用于与指定客户端通信
  4. 获取新建连接socket的套接字描述符
  5. 收发数据
  6. 关闭套接字

客户端:

  1. 创建套接字
  2. 绑定地址信息(不推荐客户端主动绑定地址信息)
  3. 向服务器发起数据
  4. 收发数据
  5. 关闭套接字

3.实现一次循环通信

代码如下:

tcp_socket.hpp:

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

#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
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,uint16_t port)
       {
         struct sockaddr_in addr;//先定义一个ipv4的地址结构
         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 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;
     }
     
   //服务器开始监听
    bool Listen(int backlog = MAX_LISTEN)
     {
      int ret = listen(_sockfd,backlog);
      if(ret < 0)
      {
        perror("listen error");
        return false;
      }
     return true;
    }

  //获取新建连接
    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;
    }

   //接受数据
  bool Recv(std::string *body)
  {
   char tmp[4096] = {0};
   int ret = recv(_sockfd,tmp,4096,0);
   if(ret < 0)
   {
    perror("recv error");
    return false;
   }
   else if(ret == 0)
   {
    std::cout<<"peer shutdown!\n";
    return false;
   }
   body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
   return true;
 }



   //发送数据
    bool Send(const std::string &body)
    {
       int ret;
       ret = send(_sockfd,body.c_str(),body.size(),0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
     return true;
    }

  //关闭套接字
  bool Close()
  {
    if(_sockfd!= -1)
    {
      close(_sockfd);
    }
   return true;
  }
  



};

tcp_srv.cpp:

#include "tcp_socket.hpp"
int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());
   while(1)
   {
    //获取新建连接
     TcpSocket conn_sock;
     std::string cliip;
     uint16_t cliport;
     bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
     if(ret < 0)
     {
      continue;
     }
    std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
     //使用新建连接与客户端通信
     std::string buf;
     ret = conn_sock.Recv(&buf);
     if(ret == false)
     {
      conn_sock.Close();
      continue;
     }
      std::cout<<"client say:"<<buf<<std::endl;
      std::cout<<"server say:";
      fflush(stdout);
      std::cin>>buf;
      ret = conn_sock.Send(buf);
      if(ret == false)
      {
       conn_sock.Close();
       continue;
      }
     }
   
   // 关闭套接字
   lst_sock.Close();
   return 0;
  }

tcp_cli.cpp:

#include "tcp_socket.hpp"

int main(int argc,char* argv[])
{
  if(argc!= 3)
  {
    std::cout<<"please input server address!\n";
    std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    return -1;
  }
 std::string srv_ip = argv[1];
 uint16_t srv_port = std::stoi(argv[2]);
 TcpSocket cli_sock;
 //创建套接字
   CHECK_RES(cli_sock.Socket());
 //绑定地址信息(客户端不推荐)
 //向服务器发起连接
   CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
   while(1)
   {
     //与服务器通信
     std::string buf;
     std::cout<<"client say:";
     fflush(stdout);
     std::cin>>buf;
     bool ret = cli_sock.Send(buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    buf.clear();
    ret = cli_sock.Recv(&buf);
    if(ret == false)
    {
     cli_sock.Close();
     return -1;
    }
    std::cout<<"server say:"<<buf<<std::endl;
  }
 //关闭套接字
  cli_sock.Close();
  return 0;
 }

makefile:

all:tcp_srv tcp_cli
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@

测试结果如下:

先运行服务器端程序:

 再运行客户端程序:

 客户端发送数据:

服务器端收到客户端发送的数据并进行回复:

客户端收到了服务器端的回复:

 

4.改进---多进程方式实现多次通信 

 服务器端代码改动 (cp tcp_srv.cpp process_srv.cpp):

process_srv.cpp代码如下:

#include "tcp_socket.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.Recv(&buf);
      if(ret == false)
      {
        conn_sock.Close();
        break;
      }
      std::cout<<"client say:"<<buf<<std::endl;
      std::cout<<"server say:";
      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 lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());
   while(1)
   {
    //获取新建连接
     TcpSocket conn_sock;
     std::string cliip;
     uint16_t cliport;
     bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
     if(ret < 0)
     {
      continue;
     }
    std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
     //使用新建连接与客户端通信
    new_worker(conn_sock);
    //父进程必须关闭套接字,否则连接的客户端越多,创建的套接字越多
    //但父进程本质并不与客户端通信,所以最后会资源耗尽
    //因此必须关闭,而父子进程数据独有,因此父进程的关闭并不影响子进程
    conn_sock.Close();

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

makefile改动:

all:tcp_srv tcp_cli process_srv
process_srv:process_srv.cpp
	g++ -std=c++11 $^ -o $@
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@

测试结果(可实现多次通信):

 当前存在的问题:

若客户端1和客户端2都向服务器发送了数据,服务器在客户端1发送了数据之后并没有回复,在2发了之后才回复,此时回复的是客户端1。

测试结果如下(1向服务器发送tianyijingheile,2向服务器发送ganjinxiakeba,服务器端二者都可收到,回复xiake是回复给了客户端1):

客户端1:

客户端2:

 服务器端:

5.改进---多线程方式实现多次通信 

 cp tcp_srv.cpp thread_srv.cpp

thread_srv.cpp:

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

void* entry(void* arg)
{
 TcpSocket *conn_sock = (TcpSocket*)arg;
   while(1)
  {  
     std::string buf;
     bool ret = conn_sock->Recv(&buf);
     if(ret == false)
     {
      conn_sock->Close();
      break;
     }
      std::cout<<"client say:"<<buf<<std::endl;
      std::cout<<"server say:";
      fflush(stdout);
      std::cin>>buf;
      ret = conn_sock->Send(buf);
      if(ret == false)
      {
       conn_sock->Close();
       break;
      }
    }
  conn_sock->Close();
  delete conn_sock;
  return NULL;
}
bool new_worker(TcpSocket *conn_sock)
{
  pthread_t tid;
  int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);
  if(ret != 0)
  {
      std::cout<<"thread create error\n";
      return false;
  }
  pthread_detach(tid);
  return true;
 }

int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());
   while(1)
   {
    //获取新建连接,在堆上申请
     TcpSocket *conn_sock = new TcpSocket();
     std::string cliip;
     uint16_t cliport;
     bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);
     if(ret < 0)
     {
      continue;
     }
    std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
     //使用新建连接与客户端通信
     new_worker(conn_sock);
      
    }
   
   // 关闭套接字
   lst_sock.Close();
   return 0;
  }

makefile:

all:tcp_srv tcp_cli process_srv thread_srv
thread_srv:thread_srv.cpp
	g++ -std=c++11 $^ -o $@ -lpthread
process_srv:process_srv.cpp
	g++ -std=c++11 $^ -o $@
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@

测试结果和多进程方式一样,存在的问题也相同。

6.改进---多线程方式+字典方式实现多次通信 

cp thread_srv.cpp robot_srv.cpp

robot_srv.cpp:

#include "tcp_socket.hpp"

#include<pthread.h>
#include<unordered_map>

std::unordered_map<std::string,std::string>_dictionaries={
  {"nihao","你好"},
  {"leihou","雷猴"},
  {"hello","hi"}
 };

 std::string get_rsp(const std::string &key)
{ 
  auto it = _dictionaries.find(key);
  if(it!= _dictionaries.end())
  {
    return it->second;
  }
  return "不要乱说话";
}

void* entry(void* arg)
{
 TcpSocket *conn_sock = (TcpSocket*)arg;
   while(1)
  {  
     std::string buf;
     bool ret = conn_sock->Recv(&buf);
     if(ret == false)
     {
      conn_sock->Close();
      break;
     }
    std::string data = get_rsp(buf);  
    ret = conn_sock->Send(data);
      if(ret == false)
      {
       conn_sock->Close();
       break;
      }
    }
  conn_sock->Close();
  delete conn_sock;
  return NULL;
}
bool new_worker(TcpSocket *conn_sock)
{
  pthread_t tid;
  int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);
  if(ret != 0)
  {
      std::cout<<"thread create error\n";
      return false;
  }
  pthread_detach(tid);
  return true;
 }

int main()
{
  TcpSocket lst_sock;

  //创建套接字
   CHECK_RES(lst_sock.Socket());
  //绑定地址信息
   CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
  //开始监听
   CHECK_RES(lst_sock.Listen());
   while(1)
   {
    //获取新建连接,在堆上申请
     TcpSocket *conn_sock = new TcpSocket();
     std::string cliip;
     uint16_t cliport;
     bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);
     if(ret < 0)
     {
      continue;
     }
    std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
     //使用新建连接与客户端通信
     new_worker(conn_sock);
      
    }
   
   // 关闭套接字
   lst_sock.Close();
   return 0;
  }

makefile:

all:tcp_srv tcp_cli process_srv thread_srv robot_srv
robot_srv:robot_srv.cpp
	g++ -std=c++11 $^ -o $@ -lpthread
thread_srv:thread_srv.cpp
	g++ -std=c++11 $^ -o $@ -lpthread
process_srv:process_srv.cpp
	g++ -std=c++11 $^ -o $@
tcp_cli:tcp_cli.cpp
	g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
	g++ -std=c++11 $^ -o $@

测试结果 客户端:

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值