计算机网络 —— 网络编程(TCP)

打印 上一主题 下一主题

主题 1047|帖子 1047|积分 3141

我们之前了解过了UDP的网络编程接口,本日我们要来了解一下TCP网络接口。
TCP和UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的传输层协议,它们用于在网络中传输数据。只管它们都是基于IP(互联网协议)之上的传输层协议,但两者在筹划目的、功能特性以及应用场景上有着明显的区别。
TCP (Transmission Control Protocol)

   

  • 连接导向:TCP 是面向连接的协议,在数据传输前必要建立连接(三次握手),确保通讯两边都准备好接收数据。
  • 可靠性:TCP 提供可靠的数据传输服务,通过确认机制(ACK)、重传机制和流量控制来包管数据包按序无误地到达接收端。
  • 有序交付:TCP 会按照发送序次将数据包通报给应用层,纵然某些数据包后到也会被正确排序。
  • 流控与拥塞控制:TCP 实现了复杂的流量控制和拥塞控制算法,如慢启动、拥塞避免等,以优化网络资源利用并防止网络拥塞。
  • 高开销:由于提供了多种保障机制,TCP 的头部较大,处理过程也更复杂,因此相对UDP来说具有更高的CPU和带宽开销。
  • 适用于场景:适合对数据完整性要求高的应用,例如文件传输(FTP)、电子邮件(SMTP)、网页浏览(HTTP/HTTPS)等。
  UDP (User Datagram Protocol)

   

  • 无连接:UDP 是无连接的协议,不必要在发送数据之前建立连接,可以直接发送数据报文。
  • 不可靠性:UDP 不提供可靠性包管,它不会重传丢失的数据包,也不包管数据包的序次。
  • 无序交付:UDP 按照接收到的序次将数据交给应用层,大概会出现乱序征象。
  • 低开销:相比TCP,UDP 头部较小,没有复杂的握手过程和确认机制,所以它的处理速率更快,开销更低。
  • 适用于场景:适合对实时性要求较高或对少量数据丢失不敏感的应用,如视频流媒体、在线游戏、DNS查询等。
  我们要关注的是TCP在发送数据之前要三次握手建立连接,所以TCP和UDP的网络接口重要差别就是在这个建立连接上,大家如果想详细了解一下TCP和UDP之间的区别可以看看这几篇文章:
   https://blog.csdn.net/qq_67693066/article/details/139620649
    https://blog.csdn.net/qq_67693066/article/details/139623241
    https://blog.csdn.net/qq_67693066/article/details/139626168
  前期准备

TCP前期的准备跟UDP是差不多的,所以我们这边按照套路写一下就行了:
  1. #pragma once
  2. #include<iostream>
  3. #include<cstring>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. const static uint16_t defaultport = 8888;
  8. class TcpServer
  9. {
  10.     public:
  11.     TcpServer(const uint16_t port = defaultport)
  12.         :_port(port)
  13.     {
  14.     }
  15.     void Init()
  16.     {
  17.         //创建套接字
  18.         _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);
  19.         if(_listen_socketfd < 0)
  20.         {
  21.             std::cout << "Create listensocket fail!" << std::endl;
  22.         }
  23.         //初始化本地服务器信息
  24.         struct sockaddr_in local;
  25.         local.sin_family = AF_INET; //IPV4
  26.         local.sin_port = htons(_port);
  27.         local.sin_addr.s_addr = INADDR_ANY;
  28.         //绑定
  29.         if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
  30.         {
  31.             std::cout << "Bind fail!" << std::endl;
  32.             exit(1);
  33.         }
  34.         std::cout << "Bind successfully and the listensocketfd is " <<
  35.         _listen_socketfd << std::endl;
  36.     }
  37.     void Start()
  38.     {
  39.         while(true)
  40.         {
  41.             
  42.         }
  43.     }
  44.     ~TcpServer()
  45.     {
  46.     }
  47.     private:
  48.     //端口号
  49.     uint16_t _port;
  50.     //监听套接字
  51.     int _listen_socketfd = -1;
  52. };
复制代码
  1. #include<iostream>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<unistd.h>
  6. #include<cerrno>
  7. #include<stdio.h>
  8. #include<cstring>
  9. void Usage()
  10. {
  11.     std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
  12. }
  13. int main(int argc,char* argv[])
  14. {
  15.     if(argc != 3)
  16.     {
  17.         Usage();
  18.         return 1;
  19.     }
  20.     //创建客户端的套接字
  21.     int socketfd = socket(AF_INET,SOCK_STREAM,0);
  22.     if(socketfd < 0)
  23.     {
  24.         std::cout << "Create socketfd fail" << std::endl;
  25.         exit(1);
  26.     }
  27.     std::cout << "Create socketfd successfully and the socketfd is " <<
  28.     socketfd << std::endl;
  29.     //填充服务器端的信息
  30.     uint16_t serverport = std::stoi(argv[2]);
  31.     std::string serverip = argv[1];
  32.     struct sockaddr_in server;
  33.     memset(&server,0,sizeof(server));
  34.     server.sin_family = AF_INET;
  35.     server.sin_port = htons(serverport);
  36.     if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
  37.     {
  38.         std::cout << "IPV4 converstion is successful!" << std::endl;
  39.     }
  40.     else
  41.     {
  42.         perror("Invalid IPv4 address");
  43.     }
  44. }
复制代码
  1. #include"TcpServer.hpp"
  2. #include<memory>
  3. void Usage()
  4. {
  5.     std::cout << "Usage ./TcpServer port" << std::endl;
  6. }
  7. int main(int argc, char* argv[])
  8. {
  9.     if(argc != 2)
  10.     {
  11.         Usage();
  12.         return 1;
  13.     }
  14.     //创建智能指针
  15.     uint16_t serverport = std::stoi(argv[1]);
  16.     std::unique_ptr<TcpServer> usr = std::make_unique<TcpServer>(serverport);
  17.     usr->Init();
  18.     usr->Start();
  19.     return 0;
  20. }
复制代码
接下来我们要写的部分就是和UDP不一样的部分了:
listen (服务端)

TCP建立连接的时候,服务器要进入监听状态,监听客户端是否有链接请求,listen就是完成这部分工作的:
listen() 函数是TCP服务器端编程中的一个重要步调,它用于将套接字转换为监听状态,以便担当来自客户端的连接请求。一旦调用了 listen(),该套接字就会开始列队等待连接请求,并准备好通过 accept() 来处理这些请求。
函数原型

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int listen(int sockfd, int backlog);
复制代码
  

  • sockfd:这是由 socket() 函数创建并已经绑定了地点信息(通过 bind())的套接字描述符。
  • backlog:这是监听队列的最大长度,即在服务器开始拒绝新的连接之前,可以有多少个未完成的连接(半连接)。这个值并不是绝对的,操作系统大概会根据现实情况调解它。通常,设置一个合理的值即可,例如5到10之间。
  返回值

   

  • 如果乐成,listen() 返回 0。
  • 如果发生错误,则返回 -1,并设置 errno 以指示详细的错误原因。
  使用示例

以下是一个完整的C语言代码片段,展示了如何使用 listen() 函数:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #define PORT 8080
  7. #define BACKLOG 10 // 监听队列的最大长度
  8. int main() {
  9.     int server_fd;
  10.     struct sockaddr_in address;
  11.     // 创建套接字
  12.     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
  13.         perror("socket failed");
  14.         exit(EXIT_FAILURE);
  15.     }
  16.     // 设置地址结构体
  17.     address.sin_family = AF_INET;
  18.     address.sin_addr.s_addr = INADDR_ANY;
  19.     address.sin_port = htons(PORT);
  20.     // 绑定套接字到指定地址和端口
  21.     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
  22.         perror("bind failed");
  23.         close(server_fd);
  24.         exit(EXIT_FAILURE);
  25.     }
  26.     // 将套接字设置为监听状态
  27.     if (listen(server_fd, BACKLOG) < 0) {
  28.         perror("listen failed");
  29.         close(server_fd);
  30.         exit(EXIT_FAILURE);
  31.     }
  32.     printf("Server is listening on port %d\n", PORT);
  33.     // 接下来可以调用 accept() 来接收连接
  34.     // ...
  35.     // 关闭套接字
  36.     close(server_fd);
  37.     return 0;
  38. }
复制代码
注意事项

   

  • 绑定后监听:确保在调用 listen() 之前已经乐成调用了 bind() 函数来绑定套接字到特定的地点和端口。
  • 选择符合的 backlog 值:虽然 backlog 参数指定了监听队列的最大长度,但现实的队列长度大概会受到操作系统的限定。一般来说,除非有特别需求,否则不必要设置非常大的 backlog 值。
  • 非阻塞模式:如果你希望 accept() 不会阻塞,可以在调用 listen() 之后将套接字设置为非阻塞模式,但这必要额外的处理逻辑来应对大概的 EAGAIN 或 EWOULDBLOCK 错误。
  通过 listen() 函数,服务器能够准备担当客户端的连接请求,并通过后续的 accept() 调用来建立与客户端的现实连接。这使得服务器可以同时处理多个客户端的连接请求,而不会因为等待某个客户端而导致其他客户端被忽视。
我们补全代码:
  1. #pragma once
  2. #include<iostream>
  3. #include<cstring>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. const static uint16_t defaultport = 8888;
  8. const static int BACKLOG = 5;
  9. class TcpServer
  10. {
  11.     public:
  12.     TcpServer(const uint16_t port = defaultport)
  13.         :_port(port)
  14.     {
  15.     }
  16.     void Init()
  17.     {
  18.         //创建套接字
  19.         _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);
  20.         if(_listen_socketfd < 0)
  21.         {
  22.             std::cout << "Create listensocket fail!" << std::endl;
  23.         }
  24.         //初始化本地服务器信息
  25.         struct sockaddr_in local;
  26.         local.sin_family = AF_INET; //IPV4
  27.         local.sin_port = htons(_port);
  28.         local.sin_addr.s_addr = INADDR_ANY;
  29.         //绑定
  30.         if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
  31.         {
  32.             std::cout << "Bind fail!" << std::endl;
  33.             exit(1);
  34.         }
  35.         std::cout << "Bind successfully and the listensocketfd is " <<
  36.         _listen_socketfd << std::endl;
  37.         //监听
  38.         if(listen(_listen_socketfd,BACKLOG) < 0)
  39.         {
  40.             std::cout << "Listen fail!" << std::endl;
  41.             exit(1);
  42.         }
  43.         std::cout << "Listen successfully! and the listensocketfd is " <<
  44.         _listen_socketfd << std::endl;
  45.     }
  46.     void Start()
  47.     {
  48.         while(true)
  49.         {
  50.         }
  51.     }
  52.     ~TcpServer()
  53.     {
  54.     }
  55.     private:
  56.     //端口号
  57.     uint16_t _port;
  58.     //监听套接字
  59.     int _listen_socketfd = -1;
  60. };
复制代码

accpect (服务端)

accept() 函数用于TCP服务器端编程中,它从已完成连接队列中提取下一个连接请求,并创建一个新的套接字来与客户端通讯。这个新的套接字专用于与特定客户端之间的数据互换,而原始的监听套接字则继续等待其他连接请求。
函数原型

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

  • sockfd:这是由 socket() 创建并已经调用 bind() 和 listen() 设置为监听状态的套接字描述符。
  • addr(可选):这是一个指向 struct sockaddr 结构的指针,用于接收客户端地点信息。如果不必要获取客户端地点信息,可以传入 NULL。
  • addrlen(可选):这是一个指向 socklen_t 范例变量的指针,表示 addr 参数所指向结构的大小。调用 accept() 之前应该初始化这个值为结构体的大小;函数返回时,它会被更新为现实填充的地点结构的大小。如果 addr 是 NULL,那么 addrlen 也应该是 NULL。
  返回值

   

  • 如果乐成,accept() 返回一个新套接字描述符,该描述符用于与客户端举行通讯。
  • 如果发生错误,则返回 -1,并设置 errno 以指示详细的错误原因。
  注意事项

   

  • 阻塞行为:默认情况下,accept() 是一个阻塞调用,即如果没有待处理的连接请求,它将一直等待直到有新的连接到来。如果你希望 accept() 不会阻塞,可以在调用 accept() 之前将套接字设置为非阻塞模式。
   

  • 多线程或多进程处理:为了同时处理多个客户端连接,通常必要在每次担当到新连接后启动一个新的线程或子进程来处理该连接,如许主步伐可以继续调用 accept() 来接收更多的连接。
   

  • 关闭连接:当与客户端的通讯完成后,记得关闭对应的客户端套接字 (new_socket)。监听套接字 (server_fd) 则保持打开状态,继续接收新的连接请求。
  1. #pragma once
  2. #include<iostream>
  3. #include<cstring>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. const static uint16_t defaultport = 8888;
  8. const static int BACKLOG = 5;
  9. class TcpServer
  10. {
  11.     public:
  12.     TcpServer(const uint16_t port = defaultport)
  13.         :_port(port)
  14.     {
  15.     }
  16.     void Init()
  17.     {
  18.         //创建套接字
  19.         _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);
  20.         if(_listen_socketfd < 0)
  21.         {
  22.             std::cout << "Create listensocket fail!" << std::endl;
  23.         }
  24.         //初始化本地服务器信息
  25.         struct sockaddr_in local;
  26.         local.sin_family = AF_INET; //IPV4
  27.         local.sin_port = htons(_port);
  28.         local.sin_addr.s_addr = INADDR_ANY;
  29.         //绑定
  30.         if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
  31.         {
  32.             std::cout << "Bind fail!" << std::endl;
  33.             exit(1);
  34.         }
  35.         std::cout << "Bind successfully and the listensocketfd is " <<
  36.         _listen_socketfd << std::endl;
  37.         //监听
  38.         if(listen(_listen_socketfd,BACKLOG) < 0)
  39.         {
  40.             std::cout << "Listen fail!" << std::endl;
  41.             exit(1);
  42.         }
  43.         std::cout << "Listen successfully! and the listensocketfd is " <<
  44.         _listen_socketfd << std::endl;
  45.    
  46.     }
  47.     void Start()
  48.     {
  49.         while(true)
  50.         {
  51.             //接收(抓取链接)
  52.             struct sockaddr_in temp;
  53.             memset(&temp,0,sizeof(temp));
  54.             socklen_t len = sizeof(temp);
  55.             int new_socketfd = accept(_listen_socketfd,(struct sockaddr*
  56.             )&temp,&len);
  57.             char ip_str[INET_ADDRSTRLEN];
  58.             const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));
  59.             std::string serverip = result ? ip_str : "Invalid address";
  60.             if(new_socketfd < 0)
  61.             {
  62.                 std::cout << "Accpect fail but try again" << std::endl;
  63.                 continue;
  64.             }
  65.             else
  66.             {
  67.                 std::cout << "Accpect successfully and the new socketfd is "<<
  68.                 new_socketfd << std::endl;
  69.             }
  70.     }
  71.     ~TcpServer()
  72.     {
  73.     }
  74.     private:
  75.     //端口号
  76.     uint16_t _port;
  77.     //监听套接字
  78.     int _listen_socketfd = -1;
  79. };
复制代码
通过 accept() 函数,服务器能够有用地管理并发连接,确保每个客户端都能得到及时的服务。
connect (客户端)

connect() 函数用于TCP客户端编程中,它尝试与指定的服务器建立连接。一旦乐成建立了连接,客户端就可以通过这个套接字与服务器举行数据互换。connect() 是一个阻塞调用,意味着它会一直等待直到连接建立乐成或失败。
函数原型

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

  • sockfd:这是由 socket() 创建的套接字描述符,尚未与任何远程地点关联。
  • addr:这是一个指向 struct sockaddr 结构的指针,包含要连接的服务器的地点信息。通常使用 struct sockaddr_in 或 struct sockaddr_in6 来表示IPv4或IPv6地点。
  • addrlen:这是 addr 参数所指向结构体的大小(以字节为单位)。对于 sockaddr_in,这通常是 sizeof(struct sockaddr_in)。
  返回值

   

  • 如果乐成,connect() 返回 0。
  • 如果发生错误,则返回 -1,并设置 errno 以指示详细的错误原因。常见的错误包括:
  • ECONNREFUSED:连接被服务器拒绝。
  • ETIMEDOUT:连接超时。
  • EINPROGRESS:在非阻塞模式下,连接正在尝试建立但尚未完成。
  1. #include<iostream>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<unistd.h>
  6. #include<cerrno>
  7. #include<stdio.h>
  8. #include<cstring>
  9. void Usage()
  10. {
  11.     std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
  12. }
  13. int main(int argc,char* argv[])
  14. {
  15.     if(argc != 3)
  16.     {
  17.         Usage();
  18.         return 1;
  19.     }
  20.     //创建客户端的套接字
  21.     int socketfd = socket(AF_INET,SOCK_STREAM,0);
  22.     if(socketfd < 0)
  23.     {
  24.         std::cout << "Create socketfd fail" << std::endl;
  25.         exit(1);
  26.     }
  27.     std::cout << "Create socketfd successfully and the socketfd is " <<
  28.     socketfd << std::endl;
  29.     //填充服务器端的信息
  30.     uint16_t serverport = std::stoi(argv[2]);
  31.     std::string serverip = argv[1];
  32.     struct sockaddr_in server;
  33.     memset(&server,0,sizeof(server));
  34.     server.sin_family = AF_INET;
  35.     server.sin_port = htons(serverport);
  36.    
  37.     if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
  38.     {
  39.         std::cout << "IPV4 converstion is successful!" << std::endl;
  40.     }
  41.     else
  42.     {
  43.         perror("Invalid IPv4 address");
  44.     }
  45.     //发起连接
  46.     if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0)
  47.     {
  48.         std::cout << "Connect fail!" << std::endl;
  49.         exit(1);
  50.     }
  51.     else
  52.     {
  53.         std::cout << "Connect successfully! and the socketfd is " <<
  54.         socketfd << std::endl;
  55.     }
  56.    
  57.     close(socketfd);
  58. }
复制代码
注意事项

   

  • 非阻塞模式:如果你希望 connect() 不会阻塞,可以在调用 connect() 之前将套接字设置为非阻塞模式。在这种情况下,如果连接尚未完成,connect() 会立即返回 -1 并设置 errno 为 EINPROGRESS。你必要使用 select()、poll() 或其他方法来查抄连接是否已经建立乐成。
   

  • 错误处理:确保对 connect() 的返回值举行得当的错误处理。特别是要注意处理那些大概导致连接失败的情况,如服务器不可达或端口未开放等。
   

  • 超机遇制:为了防止步伐长时间卡在 connect() 上,可以考虑实现超机遇制。这可以通过设置套接字选项(如 SO_RCVTIMEO 和 SO_SNDTIMEO)大概使用 select() 或 poll() 来实现。
   

  • 资源管理:无论连接是否乐成,都应该确保在得当的时候关闭套接字以开释系统资源。
  通过 connect() 函数,客户端能够主动发起与服务器的连接请求,从而开始双向的数据传输过程。这是TCP客户端编程中的关键步调之一。
如果一切顺利就可以看到如许的结果:

send 和 recv

send() 和 recv() 是用于TCP套接字通讯的两个重要函数,分别用于发送和接收数据。它们是BSD套接字API的一部分,在POSIX兼容的操作系统(如Linux、macOS)中广泛使用。
send() 函数

send() 用于通过已建立连接的套接字发送数据。它类似于标准文件I/O中的 write() 函数,但提供了额外的控制选项。
函数原型

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t send(int sockfd, const void *buf, size_t len, int flags);
复制代码
  

  • sockfd:这是由 socket() 创建并已经通过 connect() 或 accept() 建立了连接的套接字描述符。
  • buf:指向要发送的数据缓冲区的指针。
  • len:要发送的数据长度(以字节为单位)。
  • flags:提供对行为的额外控制,常用的标记包括:
  • MSG_DONTWAIT:使操作非阻塞,纵然套接字本身是阻塞模式。
  • MSG_NOSIGNAL:防止SIGPIPE信号在写入已关闭的连接时天生。
  • MSG_MORE:指示有更多数据将被发送,有助于优化Nagle算法的行为。
  返回值

   

  • 如果乐成,send() 返回现实发送的字节数。这个值大概小于请求发送的字节数(例如,当套接字缓冲区满时),因此你大概必要循环调用 send() 直到所有数据都被发送。
  • 如果发生错误,则返回 -1,并设置 errno 以指示详细的错误原因。
  recv() 函数

recv() 用于从已建立连接的套接字中读取数据。它类似于标准文件I/O中的 read() 函数,也提供了额外的控制选项。
函数原型

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t recv(int sockfd, void *buf, size_t len, int flags);
复制代码
  

  • sockfd:这是由 socket() 创建并已经通过 connect() 或 accept() 建立了连接的套接字描述符。
  • buf:指向用来存储接收到的数据的缓冲区的指针。
  • len:要接收的最大数据量(以字节为单位)。
  • flags:提供对行为的额外控制,常用的标记包括:
  • MSG_WAITALL:等待直到接收到请求的所有数据。
  • MSG_DONTWAIT:使操作非阻塞,纵然套接字本身是阻塞模式。
  • MSG_PEEK:预览数据而不现实移除它(即数据仍然生存在接收队列中)。
  返回值

   

  • 如果乐成,recv() 返回现实接收到的字节数。如果返回值为0,则表示对端已经关闭了连接。
  • 如果发生错误,则返回 -1,并设置 errno 以指示详细的错误原因。
  我们将客户端和服务器端的代码补完:
  1. #pragma once
  2. #include<iostream>
  3. #include<cstring>
  4. #include<sys/socket.h>
  5. #include<netinet/in.h>
  6. #include<arpa/inet.h>
  7. const static uint16_t defaultport = 8888;
  8. const static int BACKLOG = 5;
  9. class TcpServer
  10. {
  11.     public:
  12.     TcpServer(const uint16_t port = defaultport)
  13.         :_port(port)
  14.     {
  15.     }
  16.     void Init()
  17.     {
  18.         //创建套接字
  19.         _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);
  20.         if(_listen_socketfd < 0)
  21.         {
  22.             std::cout << "Create listensocket fail!" << std::endl;
  23.         }
  24.         //初始化本地服务器信息
  25.         struct sockaddr_in local;
  26.         local.sin_family = AF_INET; //IPV4
  27.         local.sin_port = htons(_port);
  28.         local.sin_addr.s_addr = INADDR_ANY;
  29.         //绑定
  30.         if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
  31.         {
  32.             std::cout << "Bind fail!" << std::endl;
  33.             exit(1);
  34.         }
  35.         std::cout << "Bind successfully and the listensocketfd is " <<
  36.         _listen_socketfd << std::endl;
  37.         //监听
  38.         if(listen(_listen_socketfd,BACKLOG) < 0)
  39.         {
  40.             std::cout << "Listen fail!" << std::endl;
  41.             exit(1);
  42.         }
  43.         std::cout << "Listen successfully! and the listensocketfd is " <<
  44.         _listen_socketfd << std::endl;
  45.    
  46.     }
  47.     void Start()
  48.     {
  49.         while(true)
  50.         {
  51.             //接收(抓取链接)
  52.             struct sockaddr_in temp;
  53.             memset(&temp,0,sizeof(temp));
  54.             socklen_t len = sizeof(temp);
  55.             int new_socketfd = accept(_listen_socketfd,(struct sockaddr*
  56.             )&temp,&len);
  57.             char ip_str[INET_ADDRSTRLEN];
  58.             const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));
  59.             std::string serverip = result ? ip_str : "Invalid address";
  60.             if(new_socketfd < 0)
  61.             {
  62.                 std::cout << "Accpect fail but try again" << std::endl;
  63.                 continue;
  64.             }
  65.             else
  66.             {
  67.                 std::cout << "Accpect successfully and the new socketfd is "<<
  68.                 new_socketfd << std::endl;
  69.             }
  70.             //开始服务
  71.             while(true)
  72.             {
  73.                 char buffer[1024]; //缓冲区
  74.                 int n = recv(new_socketfd,buffer,sizeof(buffer)-1,0);
  75.                 if(n > 0)
  76.                 {
  77.                     buffer[n] = 0;
  78.                     std::cout << "[" << serverip << "]# " << buffer << std::endl;  
  79.                 }
  80.                 else if(n == 0 || n < 0)
  81.                 {
  82.                     std::cout << "Client quit" << std::endl;
  83.                     break;
  84.                 }
  85.                 else
  86.                 {
  87.                     std::cout << "Read fail" << std::endl;
  88.                     break;
  89.                 }
  90.             }
  91.    
  92.         }
  93.     }
  94.     ~TcpServer()
  95.     {
  96.     }
  97.     private:
  98.     //端口号
  99.     uint16_t _port;
  100.     //监听套接字
  101.     int _listen_socketfd = -1;
  102. };
复制代码
  1. #include<iostream>
  2. #include<sys/socket.h>
  3. #include<netinet/in.h>
  4. #include<arpa/inet.h>
  5. #include<unistd.h>
  6. #include<cerrno>
  7. #include<stdio.h>
  8. #include<cstring>
  9. void Usage()
  10. {
  11.     std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
  12. }
  13. int main(int argc,char* argv[])
  14. {
  15.     if(argc != 3)
  16.     {
  17.         Usage();
  18.         return 1;
  19.     }
  20.     //创建客户端的套接字
  21.     int socketfd = socket(AF_INET,SOCK_STREAM,0);
  22.     if(socketfd < 0)
  23.     {
  24.         std::cout << "Create socketfd fail" << std::endl;
  25.         exit(1);
  26.     }
  27.     std::cout << "Create socketfd successfully and the socketfd is " <<
  28.     socketfd << std::endl;
  29.     //填充服务器端的信息
  30.     uint16_t serverport = std::stoi(argv[2]);
  31.     std::string serverip = argv[1];
  32.     struct sockaddr_in server;
  33.     memset(&server,0,sizeof(server));
  34.     server.sin_family = AF_INET;
  35.     server.sin_port = htons(serverport);
  36.     if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
  37.     {
  38.         std::cout << "IPV4 converstion is successful!" << std::endl;
  39.     }
  40.     else
  41.     {
  42.         perror("Invalid IPv4 address");
  43.     }
  44.     //发起连接
  45.     if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0)
  46.     {
  47.         std::cout << "Connect fail!" << std::endl;
  48.         exit(1);
  49.     }
  50.     else
  51.     {
  52.         std::cout << "Connect successfully! and the socketfd is " <<
  53.         socketfd << std::endl;
  54.     }
  55.    
  56.     while(true)
  57.     {
  58.         std::string inbuffer;
  59.         std::getline(std::cin,inbuffer);
  60.         //向服务端发送信息
  61.         int n = send(socketfd,inbuffer.c_str(),inbuffer.size(),0);
  62.     }
  63.     close(socketfd);
  64. }
复制代码
我们用本地回环测试一下:
我们也可以用windows来测试一下:
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <winsock2.h>
  3. #include <ws2tcpip.h>
  4. #include <iostream>
  5. #include<string>
  6. #include<cstdio>
  7. #include<stdio.h>
  8. #pragma comment(lib, "Ws2_32.lib")
  9. int main() {
  10.     // 初始化Winsock
  11.     WSADATA wsaData;
  12.     int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  13.     if (iResult != 0) {
  14.         std::cerr << "WSAStartup failed: " << iResult << std::endl;
  15.         return 1;
  16.     }
  17.     // 创建套接字
  18.     SOCKET SendSocket = socket(AF_INET, SOCK_STREAM, 0);
  19.     if (SendSocket == INVALID_SOCKET)
  20.     {
  21.         std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
  22.         WSACleanup();
  23.         return 1;
  24.     }
  25.     // 设置服务器地址和端口
  26.     sockaddr_in RecvAddr;
  27.     RecvAddr.sin_family = AF_INET;
  28.     RecvAddr.sin_port = htons(8888); // 假设服务器在12345端口监听
  29.     // 将服务器地址从文本转换为二进制形式
  30.     inet_pton(AF_INET, "43.138.14.12", &RecvAddr.sin_addr); // 换成自己服务器的ip
  31.     // 连接到服务器
  32.     iResult = connect(SendSocket, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));
  33.     if (iResult == SOCKET_ERROR) {
  34.         std::cerr << "connect failed: " << WSAGetLastError() << std::endl;
  35.         closesocket(SendSocket);
  36.         WSACleanup();
  37.         return 1;
  38.     }
  39.     // 发送消息到服务器
  40.     while (true)
  41.     {
  42.         std::string message;
  43.         std::cout << "Please enter# ";
  44.         std::getline(std::cin,message);
  45.         iResult = send(SendSocket, message.c_str(), message.size(), 0);
  46.         if (iResult == SOCKET_ERROR) {
  47.             std::cerr << "send failed: " << WSAGetLastError() << std::endl;
  48.             closesocket(SendSocket);
  49.             WSACleanup();
  50.             return 1;
  51.         }
  52.         else {
  53.             std::cout << "Message sent successfully: " << message << std::endl;
  54.         }
  55.     }
  56.     // 关闭套接字
  57.     closesocket(SendSocket);
  58.     // 清理Winsock
  59.     WSACleanup();
  60.     return 0;
  61. }
复制代码

accpect为啥要返回一个新的文件描述符?

我们之前编写代码时,我们一开始界说的socket是listen的socket,但是我们执行accpect时会返回一个新的套接字描述符,这是为什么呢?
这里重要是为了支持并发连接
   服务器通常必要同时处理多个客户端连接。如果 accept() 不返回新的文件描述符,而是使用原始监听套接字举行通讯,那么每次只能与一个客户端通讯,其他客户端将被阻塞,直到当前通讯完成。通过为每个新连接创建一个新的文件描述符,服务器可以同时与多个客户端保持独立的通讯会话
  listen套接字是负责“揽客”的,只负责抓客户端发来的连接(有点像饭店门口招揽客人),真正提供服务的,是accpect执行后谁人新的套接字(饭店里面的服务员才是真正提供服务)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表