• 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:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define MAX_LISTEN 5
    9. #define CHECK_RES(q) if((q)==false) { return -1;}
    10. class TcpSocket{
    11. private:
    12. int _sockfd;
    13. public:
    14. TcpSocket():_sockfd(-1){}
    15. //创建套接字
    16. bool Socket()
    17. {
    18. _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    19. if(_sockfd < 0)
    20. {
    21. perror("socket error");
    22. return false;
    23. }
    24. return true;
    25. }
    26. //绑定地址信息
    27. bool Bind(const std::string &ip,uint16_t port)
    28. {
    29. struct sockaddr_in addr;//先定义一个ipv4的地址结构
    30. addr.sin_family = AF_INET;
    31. addr.sin_port = htons(port);
    32. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    33. socklen_t len = sizeof(struct sockaddr_in);
    34. int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
    35. if(ret<0)
    36. {
    37. perror("bind error");
    38. return false;
    39. }
    40. return true;
    41. }
    42. //向服务器发起连接
    43. bool Connect(const std::string &ip,uint16_t port)
    44. {
    45. struct sockaddr_in addr;
    46. addr.sin_family = AF_INET;
    47. addr.sin_port = htons(port);
    48. addr.sin_addr.s_addr = inet_addr(ip.c_str());
    49. socklen_t len = sizeof(struct sockaddr_in);
    50. int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
    51. if(ret < 0)
    52. {
    53. perror("connect error");
    54. return false;
    55. }
    56. return true;
    57. }
    58. //服务器开始监听
    59. bool Listen(int backlog = MAX_LISTEN)
    60. {
    61. int ret = listen(_sockfd,backlog);
    62. if(ret < 0)
    63. {
    64. perror("listen error");
    65. return false;
    66. }
    67. return true;
    68. }
    69. //获取新建连接
    70. bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
    71. {
    72. struct sockaddr_in addr;
    73. socklen_t len = sizeof(struct sockaddr_in);
    74. int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
    75. if(newfd<0)
    76. {
    77. perror("accept error");
    78. return false;
    79. }
    80. sock->_sockfd = newfd;
    81. if(ip != NULL)
    82. {
    83. *ip = inet_ntoa(addr.sin_addr);
    84. }
    85. if(port != NULL)
    86. {
    87. *port = ntohs(addr.sin_port);
    88. }
    89. return true;
    90. }
    91. //接受数据
    92. bool Recv(std::string *body)
    93. {
    94. char tmp[4096] = {0};
    95. int ret = recv(_sockfd,tmp,4096,0);
    96. if(ret < 0)
    97. {
    98. perror("recv error");
    99. return false;
    100. }
    101. else if(ret == 0)
    102. {
    103. std::cout<<"peer shutdown!\n";
    104. return false;
    105. }
    106. body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
    107. return true;
    108. }
    109. //发送数据
    110. bool Send(const std::string &body)
    111. {
    112. int ret;
    113. ret = send(_sockfd,body.c_str(),body.size(),0);
    114. if(ret < 0)
    115. {
    116. perror("send error");
    117. return false;
    118. }
    119. return true;
    120. }
    121. //关闭套接字
    122. bool Close()
    123. {
    124. if(_sockfd!= -1)
    125. {
    126. close(_sockfd);
    127. }
    128. return true;
    129. }
    130. };

    tcp_srv.cpp:

    1. #include "tcp_socket.hpp"
    2. int main()
    3. {
    4. TcpSocket lst_sock;
    5. //创建套接字
    6. CHECK_RES(lst_sock.Socket());
    7. //绑定地址信息
    8. CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
    9. //开始监听
    10. CHECK_RES(lst_sock.Listen());
    11. while(1)
    12. {
    13. //获取新建连接
    14. TcpSocket conn_sock;
    15. std::string cliip;
    16. uint16_t cliport;
    17. bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
    18. if(ret < 0)
    19. {
    20. continue;
    21. }
    22. std::cout<<"new connect:"<":"<
    23. //使用新建连接与客户端通信
    24. std::string buf;
    25. ret = conn_sock.Recv(&buf);
    26. if(ret == false)
    27. {
    28. conn_sock.Close();
    29. continue;
    30. }
    31. std::cout<<"client say:"<
    32. std::cout<<"server say:";
    33. fflush(stdout);
    34. std::cin>>buf;
    35. ret = conn_sock.Send(buf);
    36. if(ret == false)
    37. {
    38. conn_sock.Close();
    39. continue;
    40. }
    41. }
    42. // 关闭套接字
    43. lst_sock.Close();
    44. return 0;
    45. }

    tcp_cli.cpp:

    1. #include "tcp_socket.hpp"
    2. int main(int argc,char* argv[])
    3. {
    4. if(argc!= 3)
    5. {
    6. std::cout<<"please input server address!\n";
    7. std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
    8. return -1;
    9. }
    10. std::string srv_ip = argv[1];
    11. uint16_t srv_port = std::stoi(argv[2]);
    12. TcpSocket cli_sock;
    13. //创建套接字
    14. CHECK_RES(cli_sock.Socket());
    15. //绑定地址信息(客户端不推荐)
    16. //向服务器发起连接
    17. CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
    18. while(1)
    19. {
    20. //与服务器通信
    21. std::string buf;
    22. std::cout<<"client say:";
    23. fflush(stdout);
    24. std::cin>>buf;
    25. bool ret = cli_sock.Send(buf);
    26. if(ret == false)
    27. {
    28. cli_sock.Close();
    29. return -1;
    30. }
    31. buf.clear();
    32. ret = cli_sock.Recv(&buf);
    33. if(ret == false)
    34. {
    35. cli_sock.Close();
    36. return -1;
    37. }
    38. std::cout<<"server say:"<
    39. }
    40. //关闭套接字
    41. cli_sock.Close();
    42. return 0;
    43. }

    makefile:

    1. all:tcp_srv tcp_cli
    2. tcp_cli:tcp_cli.cpp
    3. g++ -std=c++11 $^ -o $@
    4. tcp_srv:tcp_srv.cpp
    5. g++ -std=c++11 $^ -o $@

    测试结果如下:

    先运行服务器端程序:

     再运行客户端程序:

     客户端发送数据:

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

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

     

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

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

    process_srv.cpp代码如下:

    1. #include "tcp_socket.hpp"
    2. #include
    3. int new_worker(TcpSocket &conn_sock)
    4. {
    5. pid_t pid = fork();
    6. if(pid < 0)
    7. {
    8. perror("fork error");
    9. return -1;
    10. }
    11. else if(pid == 0)
    12. {
    13. while(1)
    14. {
    15. std::string buf;
    16. bool ret = conn_sock.Recv(&buf);
    17. if(ret == false)
    18. {
    19. conn_sock.Close();
    20. break;
    21. }
    22. std::cout<<"client say:"<
    23. std::cout<<"server say:";
    24. fflush(stdout);
    25. std::cin>>buf;
    26. ret = conn_sock.Send(buf);
    27. if(ret == false)
    28. {
    29. conn_sock.Close();
    30. break;
    31. }
    32. }
    33. conn_sock.Close();
    34. exit(-1);
    35. }
    36. return 0;
    37. }
    38. int main()
    39. {
    40. //显示忽略子进程退出信号,相当于告诉系统,子进程退出直接释放资源
    41. signal(SIGCHLD,SIG_IGN);
    42. TcpSocket lst_sock;
    43. //创建套接字
    44. CHECK_RES(lst_sock.Socket());
    45. //绑定地址信息
    46. CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
    47. //开始监听
    48. CHECK_RES(lst_sock.Listen());
    49. while(1)
    50. {
    51. //获取新建连接
    52. TcpSocket conn_sock;
    53. std::string cliip;
    54. uint16_t cliport;
    55. bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
    56. if(ret < 0)
    57. {
    58. continue;
    59. }
    60. std::cout<<"new connect:"<":"<
    61. //使用新建连接与客户端通信
    62. new_worker(conn_sock);
    63. //父进程必须关闭套接字,否则连接的客户端越多,创建的套接字越多
    64. //但父进程本质并不与客户端通信,所以最后会资源耗尽
    65. //因此必须关闭,而父子进程数据独有,因此父进程的关闭并不影响子进程
    66. conn_sock.Close();
    67. }
    68. // 关闭套接字
    69. lst_sock.Close();
    70. return 0;
    71. }

    makefile改动:

    1. all:tcp_srv tcp_cli process_srv
    2. process_srv:process_srv.cpp
    3. g++ -std=c++11 $^ -o $@
    4. tcp_cli:tcp_cli.cpp
    5. g++ -std=c++11 $^ -o $@
    6. tcp_srv:tcp_srv.cpp
    7. 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:

    1. #include "tcp_socket.hpp"
    2. #include
    3. void* entry(void* arg)
    4. {
    5. TcpSocket *conn_sock = (TcpSocket*)arg;
    6. while(1)
    7. {
    8. std::string buf;
    9. bool ret = conn_sock->Recv(&buf);
    10. if(ret == false)
    11. {
    12. conn_sock->Close();
    13. break;
    14. }
    15. std::cout<<"client say:"<
    16. std::cout<<"server say:";
    17. fflush(stdout);
    18. std::cin>>buf;
    19. ret = conn_sock->Send(buf);
    20. if(ret == false)
    21. {
    22. conn_sock->Close();
    23. break;
    24. }
    25. }
    26. conn_sock->Close();
    27. delete conn_sock;
    28. return NULL;
    29. }
    30. bool new_worker(TcpSocket *conn_sock)
    31. {
    32. pthread_t tid;
    33. int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);
    34. if(ret != 0)
    35. {
    36. std::cout<<"thread create error\n";
    37. return false;
    38. }
    39. pthread_detach(tid);
    40. return true;
    41. }
    42. int main()
    43. {
    44. TcpSocket lst_sock;
    45. //创建套接字
    46. CHECK_RES(lst_sock.Socket());
    47. //绑定地址信息
    48. CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
    49. //开始监听
    50. CHECK_RES(lst_sock.Listen());
    51. while(1)
    52. {
    53. //获取新建连接,在堆上申请
    54. TcpSocket *conn_sock = new TcpSocket();
    55. std::string cliip;
    56. uint16_t cliport;
    57. bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);
    58. if(ret < 0)
    59. {
    60. continue;
    61. }
    62. std::cout<<"new connect:"<":"<
    63. //使用新建连接与客户端通信
    64. new_worker(conn_sock);
    65. }
    66. // 关闭套接字
    67. lst_sock.Close();
    68. return 0;
    69. }

    makefile:

    1. all:tcp_srv tcp_cli process_srv thread_srv
    2. thread_srv:thread_srv.cpp
    3. g++ -std=c++11 $^ -o $@ -lpthread
    4. process_srv:process_srv.cpp
    5. g++ -std=c++11 $^ -o $@
    6. tcp_cli:tcp_cli.cpp
    7. g++ -std=c++11 $^ -o $@
    8. tcp_srv:tcp_srv.cpp
    9. g++ -std=c++11 $^ -o $@

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

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

    cp thread_srv.cpp robot_srv.cpp

    robot_srv.cpp:

    1. #include "tcp_socket.hpp"
    2. #include
    3. #include
    4. std::unordered_map_dictionaries={
    5. {"nihao","你好"},
    6. {"leihou","雷猴"},
    7. {"hello","hi"}
    8. };
    9. std::string get_rsp(const std::string &key)
    10. {
    11. auto it = _dictionaries.find(key);
    12. if(it!= _dictionaries.end())
    13. {
    14. return it->second;
    15. }
    16. return "不要乱说话";
    17. }
    18. void* entry(void* arg)
    19. {
    20. TcpSocket *conn_sock = (TcpSocket*)arg;
    21. while(1)
    22. {
    23. std::string buf;
    24. bool ret = conn_sock->Recv(&buf);
    25. if(ret == false)
    26. {
    27. conn_sock->Close();
    28. break;
    29. }
    30. std::string data = get_rsp(buf);
    31. ret = conn_sock->Send(data);
    32. if(ret == false)
    33. {
    34. conn_sock->Close();
    35. break;
    36. }
    37. }
    38. conn_sock->Close();
    39. delete conn_sock;
    40. return NULL;
    41. }
    42. bool new_worker(TcpSocket *conn_sock)
    43. {
    44. pthread_t tid;
    45. int ret = pthread_create(&tid,NULL,entry,(void*)conn_sock);
    46. if(ret != 0)
    47. {
    48. std::cout<<"thread create error\n";
    49. return false;
    50. }
    51. pthread_detach(tid);
    52. return true;
    53. }
    54. int main()
    55. {
    56. TcpSocket lst_sock;
    57. //创建套接字
    58. CHECK_RES(lst_sock.Socket());
    59. //绑定地址信息
    60. CHECK_RES(lst_sock.Bind("192.168.164.128",20000));
    61. //开始监听
    62. CHECK_RES(lst_sock.Listen());
    63. while(1)
    64. {
    65. //获取新建连接,在堆上申请
    66. TcpSocket *conn_sock = new TcpSocket();
    67. std::string cliip;
    68. uint16_t cliport;
    69. bool ret = lst_sock.Accept(conn_sock,&cliip,&cliport);
    70. if(ret < 0)
    71. {
    72. continue;
    73. }
    74. std::cout<<"new connect:"<":"<
    75. //使用新建连接与客户端通信
    76. new_worker(conn_sock);
    77. }
    78. // 关闭套接字
    79. lst_sock.Close();
    80. return 0;
    81. }

    makefile:

    1. all:tcp_srv tcp_cli process_srv thread_srv robot_srv
    2. robot_srv:robot_srv.cpp
    3. g++ -std=c++11 $^ -o $@ -lpthread
    4. thread_srv:thread_srv.cpp
    5. g++ -std=c++11 $^ -o $@ -lpthread
    6. process_srv:process_srv.cpp
    7. g++ -std=c++11 $^ -o $@
    8. tcp_cli:tcp_cli.cpp
    9. g++ -std=c++11 $^ -o $@
    10. tcp_srv:tcp_srv.cpp
    11. g++ -std=c++11 $^ -o $@

    测试结果 客户端:

     

  • 相关阅读:
    Java的CompletableFuture,Java的多线程开发
    Qt 框架 6.6版本添加响应式布局,并兼容AArch64 架构
    Elasticsearch高级检索之使用单个字母数字进行分词N-gram tokenizer(不区分大小写)【实战篇】
    一览8个 NFT 分析平台
    MySQL8.0.26安装配置教程(windows 64位)
    ROS-API函数介绍
    第八篇 基于JSP 技术的网上购书系统——商品信息查看、我的购物车、结算中心功能实现(网上商城、仿淘宝、当当、亚马逊)
    y137.第八章 Servless和Knative从入门到精通 -- Serverless概念和基础(一)
    工具: MarkDown学习
    harbor 搭建和部署
  • 原文地址:https://blog.csdn.net/weixin_46153828/article/details/126475480