当前位置:   article > 正文

网络编程 -- 简易TCP网络程序

网络编程 -- 简易TCP网络程序

字符串回响

1.1 核心功能

  字符串回响程序类似于 echo 指令,客户端向服务器发送消息,服务器在收到消息后会将消息发送给客户端,该程序实现起来比较简单,同时能很好的体现 socket 套接字编程的流程。

  

1.2 程序结构

  这个程序我们已经基于 UDP 协议实现过了,换成 TCP 协议实现时,程序的结构是没有变化的,同样需要 server.hppserver.ccclient.hppclient.cc 这几个文件。

server.hpp 头文件

  1. #pragma once
  2. #include<iostream>
  3. #include<sys/types.h>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. #include "err.hpp"
  8. #include<cstring>
  9. namespace My_server{
  10. const uint16_t default_port = 8888; // 默认端口号
  11. class server{
  12. public:
  13. server(const uint16_t port = default_port)
  14. :_port(port)
  15. {}
  16. ~server()
  17. {}
  18. // 初始化服务器
  19. void InitServer(){
  20. }
  21. // 启动服务器
  22. void StartServer()
  23. {}
  24. private:
  25. int _sock; // 套接字(存疑)
  26. uint16_t _port; // 端口号
  27. };
  28. }

注意: 这里的 _sock 套接字成员后面需要修改

server.cc 头文件

  1. //智能指针头文件
  2. #include<memory>
  3. #include"server.hpp"
  4. using namespace My_server;
  5. int main(){
  6. std::unique_ptr<server> usvr(new server());
  7. usvr->InitServer();
  8. usvr->StartServer();
  9. return 0;
  10. }

创建 client.hpp 客户端头文件

  1. #pragma once
  2. #include<iostream>
  3. #include<string>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include"err.hpp"
  9. namespace My_client{
  10. class client{
  11. public:
  12. client(const std::string& ip,const uint16_t port)
  13. :server_ip(ip)
  14. ,server_port(port)
  15. {}
  16. ~client(){}
  17. //初始化客户端
  18. void InitClient(){}
  19. // 启动客户端
  20. void StartClient(){}
  21. private:
  22. int _sock;// 套接字
  23. std::string server_ip;//服务器ip
  24. uint16_t server_port; //服务器端口号
  25. };
  26. }

创建client.cc文件
 

  1. #include"client.hpp"
  2. #include<memory>
  3. using namespace My_client;
  4. void Usage(char* program){
  5. std::cout<<"Usage : "<<std::endl;
  6. std::cout<<"\t "<<program<<" ServerIP ServerPort"<<std::endl;
  7. }
  8. int main(int argc,char *argv[]){
  9. if(argc!=3){
  10. Usage(argv[0]);
  11. return USAGE_ERR;
  12. }
  13. //获取服务器IP地址和端口号
  14. std::string ip(argv[1]);
  15. uint16_t port=std::stoi(argv[2]);
  16. std::unique_ptr<client> usvr(new client(ip,port));
  17. usvr->InitClient();
  18. usvr->StartClient();
  19. return 0;
  20. }

同时需要Makefile文件

  1. .PHONY:all
  2. all:server client
  3. server:server.cc
  4. g++ -o $@ $^ -std=c++14
  5. client:client.cc
  6. g++ -o $@ $^ -std=c++14
  7. .PHONY:clean
  8. clean:
  9. rm -rf server client

和 err.hpp 头文件

  1. #pragma once
  2. // 错误码
  3. enum
  4. {
  5. USAGE_ERR=1 ,
  6. SOCKET_ERR,
  7. BIND_ERR
  8. };

1.3 服务端

1.3.1 初始化服务端  

  基于 TCP 协议实现的网络程序也需要 创建套接字、绑定 IP 和端口号

在使用 socket 函数创建套接字时,UDP 协议需要指定参数2为 SOCK_DGRAMTCP 协议则是指定参数2为 SOCK_STREAM

InitServer() 初始化服务器函数 — 位于 server.hpp 服务器头文件中的 server

  1. #pragma once
  2. #include<iostream>
  3. #include<sys/types.h>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. #include "err.hpp"
  8. #include<cstring>
  9. namespace My_server{
  10. const uint16_t default_port = 8888; // 默认端口号
  11. class server{
  12. public:
  13. server(const uint16_t port = default_port)
  14. :_port(port)
  15. {}
  16. ~server()
  17. {}
  18. // 初始化服务器
  19. void InitServer(){
  20. //1 创建套接字
  21. _sock = socket(AF_INET,SOCK_STREAM,0);
  22. if(_sock==-1){
  23. //绑定失败
  24. std::cerr<<"Create Socket Fail!"<<strerror(errno)<<std::endl;
  25. exit(SOCKET_ERR);
  26. }
  27. std::cout<<"Create Socket Success!"<<_sock<<std::endl;
  28. //2 绑定端口号和IP地址
  29. struct sockaddr_in lockal;
  30. bzero(&lockal,sizeof lockal);
  31. lockal.sin_family = AF_INET;
  32. lockal.sin_addr.s_addr = INADDR_ANY;
  33. lockal.sin_port = htons(_port);
  34. if(bind(_sock,(const sockaddr*)&lockal,sizeof(lockal))){
  35. std::cout<<"Bind IP&&Port Fali:"<<strerror(errno)<<std::endl;
  36. exit(BIND_ERR);
  37. }
  38. }
  39. // 启动服务器
  40. void StartServer()
  41. {}
  42. private:
  43. int _sock; // 套接字(存疑)
  44. uint16_t _port; // 端口号
  45. };
  46. }

注意: 在绑定端口号时,一定需要把主机序列转换为网络序列

  为什么在绑定端口号阶段需要手动转换为网络序列,而在发送信息阶段则不需要? 这是因为在发送信息阶段,recvfrom / sendto 等函数会自动将需要发送的信息转换为网络序列,接收信息时同样会将其转换为主机序列,所以不需要手动转换

  如果使用的 UDP 协议,那么初始化服务器到此就结束了,但我们本文中使用的是 TCP 协议,这是一个 面向连接 的传输层协议,意味着在初始化服务器时,需要设置服务器为 监听 状态

  使用到的函数是 listen 函数

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int listen(int sockfd, int backlog);

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

  listen函数 使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

  这里的参数2需要设置一个整数,通常为 16、32、64...,表示 全连接队列 的最大长度,关于 全连接队列 的详细知识放到后续博客中讲解,这里只需要直接使用。

server.hpp 服务器头文件

  我们改变一下网络文件的名字

  1. #pragma once
  2. #include<iostream>
  3. #include<cerrno>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include "err.hpp"
  9. #include<cstring>
  10. namespace My_server{
  11. const uint16_t default_port = 8888; // 默认端口号
  12. const int backlog=32; //全连接队列的最大长度
  13. class server{
  14. public:
  15. server(const uint16_t port = default_port)
  16. :_port(port)
  17. {}
  18. ~server()
  19. {}
  20. // 初始化服务器
  21. void InitServer(){
  22. //1 创建套接字
  23. _listensock = socket(AF_INET,SOCK_STREAM,0);
  24. if(_listensock==-1){
  25. //绑定失败
  26. std::cerr<<"Create Socket Fail!"<<strerror(errno)<<std::endl;
  27. exit(SOCKET_ERR);
  28. }
  29. std::cout<<"Create Socket Success!"<<_listensock<<std::endl;
  30. //2 绑定端口号和IP地址
  31. struct sockaddr_in local;
  32. bzero(&local,sizeof(local));
  33. local.sin_family = AF_INET;
  34. local.sin_addr.s_addr = INADDR_ANY;//绑定任意可用IP地址
  35. local.sin_port = htons(_port);
  36. if(bind(_listensock,(const sockaddr*)&local,sizeof(local))){
  37. std::cout<<"Bind IP&&Port Fali:"<<strerror(errno)<<std::endl;
  38. exit(BIND_ERR);
  39. }
  40. //3. 监听
  41. if(listen(_listensock,backlog) == -1){
  42. std::cerr << "Listen Fail!" << strerror(errno) << std::endl;
  43. //新增一个报错
  44. exit(LISTEN_ERR);
  45. }
  46. std::cout<<"Listen Success!"<<std::endl;
  47. }
  48. // 启动服务器
  49. void StartServer()
  50. {}
  51. private:
  52. int _listensock; // 套接字(存疑)
  53. uint16_t _port; // 端口号
  54. };
  55. }

  至此基于  TCP 协议 实现的初始化服务器函数就填充完成了,编译并运行服务器,显示初始化服务器成功。

// 示例代码中端口号为8088

1.3.2 启动服务器

1.3.2.1 处理连接请求

  TCP 是面向连接,当有客户端发起连接请求时,TCP 服务器需要正确识别并尝试进行连接,当连接成功时,与其进行通信,可使用 accept 函数进行连接。

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数解读:

  • sockfd 服务器用于处理连接请求的 socket 套接字
  • addr 客户端的 sockaddr 结构体信息
  • addrlen 客户端的 sockaddr 结构体大写

  其中 addraddrlen 是一个 输入输出型 参数,类似于 recvfrom 中的参数。

  返回值:连接成功返回一个用于通信的 socket 套接字(文件描述符),失败返回 -1。

  这也就意味着之前我们在 TcpServer 中创建的类内成员 sock_ 并非是用于通信,而是专注于处理连接请求,在 TCP 服务器中,这种套接字称为 监听套接字

使用 accept 函数处理连接请求

server.hpp 服务器头文件

  1. #pragma once
  2. #include<iostream>
  3. #include<cerrno>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include "err.hpp"
  9. #include<cstring>
  10. namespace My_server{
  11. const uint16_t default_port = 8888; // 默认端口号
  12. const int backlog=32; //全连接队列的最大长度
  13. class server{
  14. public:
  15. server(const uint16_t port = default_port)
  16. :_port(port)
  17. {}
  18. ~server()
  19. {}
  20. // 初始化服务器
  21. void InitServer(){
  22. //1 创建套接字
  23. _listensock = socket(AF_INET,SOCK_STREAM,0);
  24. if(_listensock==-1){
  25. //绑定失败
  26. std::cerr<<"Create Socket Fail!"<<strerror(errno)<<std::endl;
  27. exit(SOCKET_ERR);
  28. }
  29. std::cout<<"Create Socket Success!"<<_listensock<<std::endl;
  30. //2 绑定端口号和IP地址
  31. struct sockaddr_in local;
  32. bzero(&local,sizeof(local));
  33. local.sin_family = AF_INET;
  34. local.sin_addr.s_addr = INADDR_ANY;//绑定任意可用IP地址
  35. local.sin_port = htons(_port);
  36. if(bind(_listensock,(const sockaddr*)&local,sizeof(local))){
  37. std::cout<<"Bind IP&&Port Fali:"<<strerror(errno)<<std::endl;
  38. exit(BIND_ERR);
  39. }
  40. //3. 监听
  41. if(listen(_listensock,backlog) == -1){
  42. std::cerr << "Listen Fail!" << strerror(errno) << std::endl;
  43. //新增一个报错
  44. exit(LISTEN_ERR);
  45. }
  46. std::cout<<"Listen Success!"<<std::endl;
  47. }
  48. // 启动服务器
  49. void StartServer(){
  50. while(!_quit){
  51. //1 处理连接请求
  52. struct sockaddr_in client;
  53. socklen_t len=sizeof(client);
  54. int sock=accept(_listensock,(struct sockaddr*)&client,&len);
  55. //2 如果连接失败 继续尝试连接
  56. if(sock==-1){
  57. std::cerr<<"Accept Fail!"<<strerror(errno)<<std::endl;
  58. continue;
  59. }
  60. //连接成功,获取客户端信息
  61. std::string clientip=inet_ntoa(client.sin_addr);
  62. uint16_t clientport= ntohs(client.sin_port);
  63. std::cout<<"Server accept"<<clientip + "-"<<clientport<<sock<<" from "<<_listensock<<" success!"<<std::endl;
  64. //3 这里因为是字节流传递,一般而言我们会自己写一个函数
  65. Service(sock,clientip,clientport);
  66. }
  67. }
  68. private:
  69. int _listensock; // 套接字(存疑)
  70. uint16_t _port; // 端口号
  71. bool _quit; // 判断服务器是否结束运行
  72. };
  73. }
1.3.2.2 业务处理

对于 TCP 服务器来说,它是面向字节流传输的,我们之前使用的文件相关操作也是面向字节流,凑巧的是在 Linux 中网络是以挂接在文件系统的方式实现的,种种迹象表明:可以通过文件相关接口进行通信

  • read 从文件中读取信息(接收消息)
  • write 向文件中写入信息(发送消息)

这两个系统调用的核心参数是 fd文件描述符),即服务器与客户端在连接成功后,获取到的 socket 套接字,所以接下来可以按文件操作的套路,完成业务处理

Service() 业务处理函数 — 位于 server.hpp 服务器头文件中的 server

  1. void Service(int sock,const std::string& clientip,const uint16_t& clientport){
  2. char buff[1024];
  3. std::string who=clientip + "-" + std::to_string(clientport);
  4. while(true){
  5. //以C语言格式读取,预留'\0'的位置
  6. ssize_t n = read(sock,buff,sizeof(buff)-1);
  7. if(n>0){
  8. //读取成功
  9. std::cout<<"Server get: "<<buff<<" from "<<who<<std::endl;
  10. //实际处理可以交给上层逻辑指定
  11. std::string respond = _func(buff);
  12. //发送给服务器
  13. write(sock,buff,strlen(buff));
  14. }
  15. else if(n==0){
  16. //表示当前读到了文件末尾,结束读取
  17. std::cout<<"Client "<<who<<" "<<sock<<" quit!"<<std::endl;
  18. close(sock);
  19. break;
  20. }
  21. else{
  22. // 读取出问题(暂时)
  23. std::cerr << "Read Fail!" << strerror(errno) << std::endl;
  24. close(sock); // 关闭文件描述符
  25. break;
  26. }
  27. }
  28. }
1.3.2.2.1 回调函数

为了更好的实现功能解耦,这里将真正的业务处理函数交给上层处理,编写完成后传给 TcpServer 对象即可,当然,在 TcpServer 类中需要添加对应的类型

这里设置回调函数的返回值为 string,参数同样为 string

  using func_t = std::function<std::string(std::string)>;

server.hpp 服务器头文件

  1. #pragma once
  2. #include<iostream>
  3. #include<cerrno>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include "err.hpp"
  9. #include<cstring>
  10. #include <unistd.h>
  11. #include<functional>
  12. namespace My_server{
  13. const uint16_t default_port = 8888; // 默认端口号
  14. const int backlog=32; //全连接队列的最大长度
  15. using func_t = std::function<std::string(std::string)>;
  16. class server{
  17. public:
  18. server(const func_t &func,const uint16_t port = default_port)
  19. :_func(func)
  20. ,_port(port)
  21. ,_quit(false)
  22. {}
  23. ~server()
  24. {}
  25. // 初始化服务器
  26. void InitServer(){
  27. //1 创建套接字
  28. _listensock = socket(AF_INET,SOCK_STREAM,0);
  29. if(_listensock==-1){
  30. //绑定失败
  31. std::cerr<<"Create Socket Fail!"<<strerror(errno)<<std::endl;
  32. exit(SOCKET_ERR);
  33. }
  34. std::cout<<"Create Socket Success!"<<_listensock<<std::endl;
  35. //2 绑定端口号和IP地址
  36. struct sockaddr_in local;
  37. bzero(&local,sizeof(local));
  38. local.sin_family = AF_INET;
  39. local.sin_addr.s_addr = INADDR_ANY;//绑定任意可用IP地址
  40. local.sin_port = htons(_port);
  41. if(bind(_listensock,(const sockaddr*)&local,sizeof(local))){
  42. std::cout<<"Bind IP&&Port Fali:"<<strerror(errno)<<std::endl;
  43. exit(BIND_ERR);
  44. }
  45. //3. 监听
  46. if(listen(_listensock,backlog) == -1){
  47. std::cerr << "Listen Fail!" << strerror(errno) << std::endl;
  48. //新增一个报错
  49. exit(LISTEN_ERR);
  50. }
  51. std::cout<<"Listen Success!"<<std::endl;
  52. }
  53. // 启动服务器
  54. void StartServer(){
  55. while(!_quit){
  56. //1 处理连接请求
  57. struct sockaddr_in client;
  58. socklen_t len=sizeof(client);
  59. int sock=accept(_listensock,(struct sockaddr*)&client,&len);
  60. //2 如果连接失败 继续尝试连接
  61. if(sock==-1){
  62. std::cerr<<"Accept Fail!"<<strerror(errno)<<std::endl;
  63. continue;
  64. }
  65. //连接成功,获取客户端信息
  66. std::string clientip=inet_ntoa(client.sin_addr);
  67. uint16_t clientport= ntohs(client.sin_port);
  68. std::cout<<"Server accept"<<clientip + "-"<<clientport<<sock<<" from "<<_listensock<<" success!"<<std::endl;
  69. //3 这里因为是字节流传递,一般而言我们会自己写一个函数
  70. Service(sock,clientip,clientport);
  71. }
  72. }
  73. void Service(int sock,const std::string& clientip,const uint16_t& clientport){
  74. char buff[1024];
  75. std::string who=clientip + "-" + std::to_string(clientport);
  76. while(true){
  77. //以C语言格式读取,预留'\0'的位置
  78. ssize_t n = read(sock,buff,sizeof(buff)-1);
  79. if(n>0){
  80. //读取成功
  81. std::cout<<"Server get: "<<buff<<" from "<<who<<std::endl;
  82. //实际处理可以交给上层逻辑指定
  83. std::string respond = _func(buff);
  84. //发送给服务器
  85. write(sock,buff,strlen(buff));
  86. }
  87. else if(n==0){
  88. //表示当前读到了文件末尾,结束读取
  89. std::cout<<"Client "<<who<<" "<<sock<<" quit!"<<std::endl;
  90. close(sock);
  91. break;
  92. }
  93. else{
  94. // 读取出问题(暂时)
  95. std::cerr << "Read Fail!" << strerror(errno) << std::endl;
  96. close(sock); // 关闭文件描述符
  97. break;
  98. }
  99. }
  100. }
  101. private:
  102. int _listensock; // 套接字(存疑)
  103. uint16_t _port; // 端口号
  104. bool _quit; // 判断服务器是否结束运行
  105. func_t _func;// 回调函数
  106. };
  107. }
1.3.2.2.2 server.cc文件

对于当前的 TCP 网络程序(字符串回响)来说,业务处理函数逻辑非常简单,无非就是直接将客户端发送过来的消息,重新转发给客户端

server.cc 服务器源文件

  1. //智能指针头文件
  2. #include<memory>
  3. #include"server.hpp"
  4. #include<string>
  5. using namespace My_server;
  6. // 业务处理回调函数(字符串回响)
  7. std::string echo(std::string request){
  8. return request;
  9. }
  10. int main(){
  11. std::unique_ptr<server> usvr(new server(echo));
  12. usvr->InitServer();
  13. usvr->StartServer();
  14. return 0;
  15. }

  尝试编译并运行服务器,可以看到当前 bash 已经被我们的服务器程序占用了,重新打开一个终端,并通过 netstat 命令查看网络使用情况(基于 TCP 协议)

1.4 客户端

1.4.1 初始化客户端

 对于客户端来说,服务器的 IP 地址与端口号是两个不可或缺的元素,因此在客户端类中,  server_ipserver_port 这两个成员是少不了的,当然得有 socket 套接字

初始化客户端只需要干一件事:创建套接字,客户端是主动发起连接请求的一方,也就意味着它不需要使用 listen 函数设置为监听状态

注意: 客户端也是需要 bind 绑定的,但不需要自己手动绑定,由操作系统帮我们自动完成

client.hpp 客户端头文件

  1. #pragma once
  2. #include<iostream>
  3. #include<string>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include"err.hpp"
  9. #include<cerrno>
  10. #include<cstring>
  11. namespace My_client{
  12. class client{
  13. public:
  14. client(const std::string& ip,const uint16_t port)
  15. :server_ip(ip)
  16. ,server_port(port)
  17. {}
  18. ~client(){}
  19. //初始化客户端
  20. void InitClient(){
  21. // 1. 创建套接字
  22. _sock = socket(AF_INET, SOCK_STREAM, 0);
  23. if (_sock == -1)
  24. {
  25. std::cerr << "Create Socket Fail!" << strerror(errno) << std::endl;
  26. exit(SOCKET_ERR);
  27. }
  28. std::cout << "Create Sock Succeess! " << _sock << std::endl;
  29. }
  30. // 启动客户端
  31. void StartClient(){}
  32. private:
  33. int _sock;// 套接字
  34. std::string server_ip;//服务器ip
  35. uint16_t server_port; //服务器端口号
  36. };
  37. }

1.4.2 启动客户端

1.4.2.1 尝试进行连接

  因为 TCP 协议是面向连接的,服务器已经处于处理连接请求的状态了,客户端现在需要做的就是尝试进行连接,使用 connect 函数进行连接。

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数解读:

  • sockfd 需要进行连接的套接字
  • addr 服务器的 sockaddr 结构体信息
  • addrlen 服务器的 sockaddr 结构体大小

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

  在连接过程中,可能遇到很多问题,比如 网络传输失败、服务器未启动 等,这些问题的最终结果都是客户端连接失败,如果按照之前的逻辑(失败就退出),那么客户端的体验感会非常不好,因此在面对连接失败这种常见问题时,客户端应该尝试重连,如果重连数次后仍然失败,才考虑终止进程

注意: 在进行重连时,可以使用 sleep() 等函数使程序睡眠一会,给网络恢复留出时间

StartClient() 启动客户端函数 — 位于 client.hpp 中的client 类.

  1. #pragma once
  2. #include<iostream>
  3. #include<string>
  4. #include<sys/types.h>
  5. #include<sys/socket.h>
  6. #include<netinet/in.h>
  7. #include<arpa/inet.h>
  8. #include"err.hpp"
  9. #include<cerrno>
  10. #include<cstring>
  11. #include<unistd.h>
  12. namespace My_client{
  13. class client{
  14. public:
  15. client(const std::string& ip,const uint16_t port)
  16. :server_ip(ip)
  17. ,server_port(port)
  18. {}
  19. ~client(){}
  20. //初始化客户端
  21. void InitClient(){
  22. // 1. 创建套接字
  23. _sock = socket(AF_INET, SOCK_STREAM, 0);
  24. if (_sock == -1)
  25. {
  26. std::cerr << "Create Socket Fail!" << strerror(errno) << std::endl;
  27. exit(SOCKET_ERR);
  28. }
  29. std::cout << "Create Sock Succeess! " << _sock << std::endl;
  30. }
  31. // 启动客户端
  32. void StartClient(){
  33. //填充服务器的sockaddr_int 结构体信息
  34. struct sockaddr_in server;
  35. socklen_t len=sizeof(server);
  36. bzero(&server,len);
  37. server.sin_family = AF_INET;
  38. inet_aton(server_ip.c_str(), &server.sin_addr); // 将点分十进制转化为二进制IP地址的另一种方法
  39. server.sin_port = htons(server_port);
  40. //尝试重连五次
  41. int n=5;
  42. while(n){
  43. int ret = connect(_sock,(const struct sockaddr*)&server,len);
  44. if(ret==0){
  45. // 连接成功,可以跳出循环
  46. break;
  47. }
  48. // 尝试进行重连
  49. std::cerr << "网络异常,正在进行重连... 剩余连接次数: " << --n << std::endl;
  50. sleep(1);
  51. }
  52. // 如果剩余重连次数为 0,证明连接失败
  53. if(n == 0)
  54. {
  55. std::cerr << "连接失败! " << strerror(errno) << std::endl;
  56. close(_sock);
  57. exit(CONNECT_ERR);//新加错误标识符
  58. }
  59. // 连接成功
  60. std::cout << "连接成功!" << std::endl;
  61. // 进行业务处理
  62. // Service();
  63. }
  64. private:
  65. int _sock;// 套接字
  66. std::string server_ip;//服务器ip
  67. uint16_t server_port; //服务器端口号
  68. };
  69. }
1.4.2.2 业务处理

客户端在进行业务处理时,同样可以使用 readwrite 进行网络通信

Service() 业务处理函数 — 位于 client.hpp 客户端头文件中的 TcpClient

  1. // 业务处理
  2. void Service()
  3. {
  4. char buff[1024];
  5. std::string who = server_ip + "-" + std::to_string(server_port);
  6. while(true)
  7. {
  8. // 由用户输入信息
  9. std::string msg;
  10. std::cout << "Please Enter >> ";
  11. std::getline(std::cin, msg);
  12. // 发送信息给服务器
  13. write(_sock, msg.c_str(), msg.size());
  14. // 接收来自服务器的信息
  15. ssize_t n = read(_sock, buff, sizeof(buff) - 1);
  16. if(n > 0)
  17. {
  18. // 正常通信
  19. buff[n] = '\0';
  20. std::cout << "Client get: " << buff << " from " << who << std::endl;
  21. }
  22. else if(n == 0)
  23. {
  24. // 读取到文件末尾(服务器关闭了)
  25. std::cout << "Server " << who << " quit!" << std::endl;
  26. close(_sock); // 关闭文件描述符
  27. break;
  28. }
  29. else
  30. {
  31. // 读取异常
  32. std::cerr << "Read Fail!" << strerror(errno) << std::endl;
  33. close(_sock); // 关闭文件描述符
  34. break;
  35. }
  36. }
  37. }

代码示例: 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/464988
推荐阅读
相关标签
  

闽ICP备14008679号