【Linux】Socket网络套接字

news2025/1/20 1:50:40

文章目录

  • 网络套接字
    • 1. 网络编程概念
      • 1.1 IP地址和端口号
      • 1.2 理解网络字节序
      • 1.3 sockaddr结构体
    • 2. 网络编程接口
      • 2.1 通用接口
        • socket()
        • bind()
      • 2.2 UDP接口
        • recvfrom()
        • sendto()
      • 2.3 TCP接口
        • listen()
        • accept()
        • connect()
        • recv()/send()
      • 2.4 IP格式转换接口
      • 2.5 套接字接口封装
    • 3. 网络通信设计
      • 3.1 UDP通信
        • UDP客户端
        • UDP服务端
      • 3.2 TCP通信
        • TCP客户端
        • TCP服务端


网络套接字

1. 网络编程概念

1.1 IP地址和端口号

在网络通信中,数据是由进程产生和接收的。IP地址唯一标识了互联网中的一台主机,而端口号则唯一标识了主机上的一个网络进程。因此,网络通信的本质是跨网络的两台主机之间的进程间通信。

  • IP地址:用于标识互联网中的主机。
  • 端口号:用于标识主机上的网络进程。

源端口号用于确定源主机上的网络进程,目的端口号用于确定目的主机上的网络进程。通过IP地址和端口号,我们可以唯一标识互联网上的一个进程。

概念作用
IPIP地址唯一的标识了互联网中的一台主机。
端口端口号唯一地标识主机上的一个网络进程。

源端口号确定源主机上的网络进程,目的端口号确定目的主机上的网络进程。

IP和端口就能标识互联网上的唯一一台机器上的唯一一个进程。

可以把整个网络看作是一个大操作系统,所有的网络行为就可看作是这个系统内的进程间通信。

进程具有独立性,进程间通信的前提是先让不同的进程看到同一份资源,而网络通信的临界资源就是网络。

1.2 理解网络字节序

当跨主机传输数据时,必须考虑且强制规定字节序。规定:网络中的数据一律都采用大端的形式

一般从低到高地将数据发出,所以接收时也是先收到低地址数据,便于存储。

所以系统提供了库函数,可将数据进行主机和网络字节序的转化,如下:

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);   // 主机 转 网络 长整型
uint16_t htons(uint16_t hostshort);  // 主机 转 网络 短整型
uint32_t ntohl(uint32_t netlong);    // 网络 转 主机 长整型
uint16_t ntohs(uint16_t netshort);   // 网络 转 主机 短整型

1.3 sockaddr结构体

通信方式有很多种,比如TCP/IP属于是AF_INET,域间套接字属于AF_UNIX。各家协议都有自己的套接字结构体。

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

接口设计者提供一个通用结构体叫sockaddr。各种结构体可以强转成sockaddr,接口内部可以通过前16位数据判断具体协议类型。

#include <netinet/in.h>
#include <apra/inet.h>

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family // sa_family_t(unsigned short) sin_family

typedef uint16_t in_port_t;   // 端口类型
typedef uint_32_t in_addr_t;  // IP 类型
struct in_addr
  {
    in_addr_t s_addr;    
  };

/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);   /* Protocol Family.  */
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;	/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

 

2. 网络编程接口

2.1 通用接口

socket()

socket()函数用于创建一个套接字,它本质上是打开一个文件,与网络无关。

#include <sys/types.h>
#include <sys/socket.h> 
int socket(int domain, int type, int protocol);
  • domain:指定通信协议族,如AF_INET(IPv4)。
  • type:指定通信类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。
  • protocol:指定具体的协议,通常为0。
bind()

bind()函数用于将套接字绑定到一个特定的IP地址和端口上。

int bind(int socket, const struct sockaddr *address, socklen_t address_len);
  • socket:套接字文件描述符。CP
  • address:包含IP地址和端口的套接字信息结构体。
  • address_len:结构体长度。
接口解释
socket()创建套接字本质就是打开文件,与网络无关。
bind()本质是将IP端口和套接字文件关联。

2.2 UDP接口

recvfrom()

recvfrom()函数用于接收UDP数据包,并获取发送方的地址信息。

ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
  • socket:套接字文件描述符。
  • buffer:接收数据的缓冲区。
  • length:缓冲区长度。
  • flags:接收标志,通常为0。
  • address:用于存储发送方地址的结构体。
  • address_len:地址结构体长度。
sendto()

sendto()函数用于发送UDP数据包到指定的地址。

ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
  • socket:套接字文件描述符。
  • message:要发送的数据。
  • length:数据长度。
  • flags:发送标志,通常为0。
  • dest_addr:目标地址结构体。
  • dest_len:目标地址结构体长度。

2.3 TCP接口

listen()

listen()函数用于将套接字设置为监听状态,准备接受连接请求。

int listen(int socket, int backlog);
  • socket:套接字文件描述符。
  • backlog:等待连接队列的最大长度。

TCP是面向连接的通信协议,所以在通信前需先建立连接。

listen的本质是设置套接字为listen监听状态,允许用户进行连接。

accept()

accept()函数用于接受客户端的连接请求,并创建一个新的套接字用于通信。

int accept(int socket, struct sockaddr *address, socklen_t *address_len);
  • socket:监听套接字文件描述符。
  • address:用于存储客户端地址的结构体。
  • address_len:地址结构体长度。

**accept表示正式建立与客户端的连接。**本质阻塞式的等待三次握手完成,并将连接提取到应用层。

socket返回的sock_fd用来监听连接。accept返回的sock_fd是用来通信的fd。

connect()

connect()函数用于客户端向服务器发起连接请求。

int connect(int socket, const struct sockaddr *address, socklen_t address_len);
  • socket:套接字文件描述符。
  • address:服务器地址结构体。
  • address_len:地址结构体长度。

**connect的作用是主动向服务端发起连接。**本质是向服务器端发起TCP的三次握手,并等待握手完成。

recv()/send()

recv()send()函数用于TCP套接字的数据接收和发送。

ssize_t recv(int socket, void *buffer, size_t length, int flags); 
ssize_t send(int socket, const void *buffer, size_t length, int flags);
  • socket:套接字文件描述符。
  • buffer:接收或发送数据的缓冲区。
  • length:数据长度。
  • flags:接收或发送标志,通常为0。

网络数据收发接口,本质是读写文件。

TCP协议是流式套接字,TCP收发接口和文件读写非常像,仅多了方式参数flags

2.4 IP格式转换接口

// 字符串IP 转 网络整数IP
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
int inet_pton(int af, const char *src, void *dst);

// 网络整数IP 转 字符串IP
char* inet_ntoa(struct in_addr in); // 非线程安全
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

2.5 套接字接口封装

namespace inet {
// 定义网络编程API的结构体
struct api {
   // 定义网络协议类型枚举
   enum {
       udp = SOCK_DGRAM,  // UDP协议
       tcp = SOCK_STREAM  // TCP协议
   };

   // 创建套接字函数
   static int Socket(int proto) {
       int fd = socket(AF_INET, proto, 0);  // 创建套接字
       if (fd < 0) throw std::runtime_error("socket failed");  // 如果创建失败,抛出异常
       return fd;  // 返回套接字描述符
   }

   // 绑定套接字到本地地址函数
   static void Bind(int sock, const std::string& ip, uint16_t port) {
       struct sockaddr_in local;  // 定义本地地址结构体
       memset(&local, 0, sizeof(local));  // 清空结构体

       local.sin_family = AF_INET;  // 设置地址族为IPv4
       local.sin_addr.s_addr = inet_addr(ip.c_str());  // 设置IP地址
       local.sin_port = htons(port);  // 设置端口号

       if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
           throw std::runtime_error("bind error");  // 如果绑定失败,抛出异常
   }

   // 绑定套接字到任意本地地址函数
   static void Bind(int sock, uint16_t port) {
       struct sockaddr_in local;  // 定义本地地址结构体
       memset(&local, 0, sizeof(local));  // 清空结构体
       local.sin_family = AF_INET;  // 设置地址族为IPv4
       local.sin_addr.s_addr = INADDR_ANY;  // 设置IP地址为任意本地地址
       local.sin_port = htons(port);  // 设置端口号
       if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
           throw std::runtime_error("bind error");  // 如果绑定失败,抛出异常
   }

   // 监听套接字函数
   static void Listen(int sock, int backlog) {
       if (listen(sock, backlog) < 0)
           throw std::runtime_error("listen error");  // 如果监听失败,抛出异常
   }

   // 连接套接字到远程地址函数
   static void Connect(int sock, const std::string& ip, uint16_t port, int trytime = 1) {
       struct sockaddr_in peer;  // 定义远程地址结构体
       memset(&peer, 0, sizeof(peer));  // 清空结构体
       peer.sin_family = AF_INET;  // 设置地址族为IPv4
       peer.sin_addr.s_addr = inet_addr(ip.c_str());  // 设置IP地址
       peer.sin_port = htons(port);  // 设置端口号
       while (trytime-- > 0 && connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0) {
           if (trytime == 0) throw std::runtime_error("connect failed");  // 如果连接失败,抛出异常
           sleep(1);  // 等待1秒后重试
       }
   }

   // 接受连接函数
   static int Accept(int sock, std::string* ip = nullptr, uint16_t* port = nullptr) {
       struct sockaddr_in peer;  // 定义远程地址结构体
       socklen_t len = sizeof(peer);  // 初始化地址长度
       memset(&peer, 0, len);  // 清空结构体
       int fd = accept(sock, (struct sockaddr*)&peer, &len);  // 接受连接
       if (ip)   *ip = inet_ntoa(peer.sin_addr);  // 如果指定了IP地址,则获取IP地址
       if (port) *port = ntohs(peer.sin_port);  // 如果指定了端口号,则获取端口号
       return fd;  // 返回新的套接字描述符
   }

   // 接收数据函数
   static int Recv(int sock, std::string* msg, size_t len) {
       msg->clear();  // 清空消息字符串
       std::unique_ptr<char[]> buf(new char[len]{0});  // 创建缓冲区
       ssize_t s = recv(sock, buf.get(), len, 0);  // 接收数据
       if (s > 0) {
           buf[s] = 0;  // 在数据末尾添加字符串结束符
           *msg = buf.get();  // 将接收到的数据复制到消息字符串
       }
       return s;  // 返回接收到的数据长度
   }

   // 发送数据函数
   static int Send(int sock, const std::string& msg) {
       return send(sock, msg.c_str(), msg.size(), 0);  // 发送数据
   }

   // 接收数据并获取发送方地址函数
   static int Recvfrom(int sock, std::string* msg, size_t len, 
                       std::string* ip = nullptr, uint16_t* port = nullptr) {
       msg->clear();  // 清空消息字符串
       std::unique_ptr<char[]> buf(new char[len]{0});  // 创建缓冲区
       struct sockaddr_in peer;  // 定义远程地址结构体
       socklen_t sklen = sizeof(peer);  // 初始化地址长度
       memset(&peer, 0, sklen);  // 清空结构体
       ssize_t s = recvfrom(sock, buf.get(), len, 0, (struct sockaddr*)&peer, &sklen);  // 接收数据
       if (s > 0) {
           buf[s] = 0;  // 在数据末尾添加字符串结束符
           *msg = buf.get();  // 将接收到的数据复制到消息字符串
       }
       if (ip)   *ip = inet_ntoa(peer.sin_addr);  // 如果指定了IP地址,则获取IP地址
       if (port) *port = ntohs(peer.sin_port);  // 如果指定了端口号,则获取端口号
       return s;  // 返回接收到的数据长度
   }

   // 发送数据到指定地址函数
   static int Sendto(int sock, const std::string& msg, 
                     const std::string& ip, uint16_t port) {
       struct sockaddr_in peer;  // 定义远程地址结构体
       memset(&peer, 0, sizeof(peer));  // 清空结构体
       peer.sin_family = AF_INET;  // 设置地址族为IPv4
       peer.sin_addr.s_addr = inet_addr(ip.c_str());  // 设置IP地址
       peer.sin_port = htons(port);  // 设置端口号
       ssize_t s = 
           sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));  // 发送数据
       return s;  // 返回发送的数据长度
   }
};

// TCP协议相关的命名空间
namespace tcp {

// TCP服务器类
class server {
public:
   // 构造函数,指定端口和监听队列长度
   server(uint16_t port, int backlog = 12) : _sock(0), _port(port), _backlog(backlog) {
       init();  // 初始化服务器
   }

   // 构造函数,指定IP地址、端口和监听队列长度
   server(const std::string& ip, uint16_t port, int backlog = 12)
       : _sock(0), _ip(ip), _port(port), _backlog(backlog) {
       init();  // 初始化服务器
   }

   // 接受客户端连接函数
   int accept(std::string* cip = nullptr, uint16_t* cport = nullptr) {
       return inet::api::Accept(_sock, cip, cport);  // 接受连接
   }

   // 接收数据函数
   int recv(int sock, std::string* msg, size_t len) { 
       return inet::api::Recv(sock, msg, len);  // 接收数据
   }

   // 发送数据函数
   int send(int sock, const std::string& msg) { 
       return inet::api::Send(sock, msg);  // 发送数据
   }

   // 析构函数,关闭套接字
   ~server() { close(_sock); }

private:
   // 初始化服务器函数
   void init() {
       _sock = inet::api::Socket(inet::api::tcp);  // 创建TCP套接字
       if (_ip.empty()) inet::api::Bind(_sock, _port);  // 如果未指定IP地址,则绑定到任意本地地址
       else inet::api::Bind(_sock, _ip, _port);  // 否则绑定到指定IP地址和端口
       inet::api::Listen(_sock, _backlog);  // 开始监听
   }

protected:
   int _sock;  // 套接字描述符
   std::string _ip;  // IP地址
   uint16_t _port;  // 端口号
   int _backlog;  // 监听队列长度
};

// TCP客户端类
class client
{
public:
   // 构造函数,指定服务器IP地址、端口和重试次数
   client(const std::string& svr_ip, uint16_t svr_port, int trytime = 1)
       : _sock(0), _sip(svr_ip), _sport(svr_port), _trytime(trytime) {
       _sock = inet::api::Socket(inet::api::tcp);  // 创建TCP套接字
       inet::api::Connect(_sock, _sip, _sport, _trytime);  // 连接服务器
   }

   // 发送数据函数
   int send(const std::string& msg) { return inet::api::Send(_sock, msg); }
   int send(int sock, const std::string& msg) { return inet::api::Send(sock, msg); }

   // 接收数据函数
   int recv(std::string* msg, size_t len) { return inet::api::Recv(_sock, msg, len); }
   int recv(int sock, std::string* msg, size_t len) 
   { return inet::api::Recv(sock, msg, len); }

   // 析构函数,关闭套接字
   ~client() { close(_sock); }

protected:
   int _sock;  // 套接字描述符
   std::string _sip;  // 服务器IP地址
   uint16_t _sport;  // 服务器端口号
   int _trytime;  // 重试次数
};
}

// UDP协议相关的命名空间
namespace udp {

// UDP服务器类
class server {
public:
   // 构造函数,指定端口
   server(uint16_t port) : _sock(0), _port(port) {
       init();  // 初始化服务器
   }

   // 构造函数,指定IP地址和端口
   server(const std::string& ip, uint16_t port) : _sock(0), _ip(ip), _port(port) {
       init();  // 初始化服务器
   }

   // 发送数据到指定地址函数
   int sendto(const std::string& msg, const std::string& cip, uint16_t cport) {
       return inet::api::Sendto(_sock, msg, cip, cport);  // 发送数据
   }

   // 接收数据并获取发送方地址函数
   int recvfrom(std::string* msg, size_t len, 
                std::string* cip = nullptr, uint16_t* cport = nullptr) {
       return inet::api::Recvfrom(_sock, msg, len, cip, cport);  // 接收数据
   }

   // 析构函数,关闭套接字
   ~server() { close(_sock); }

private:
   // 初始化服务器函数
   void init() {
       _sock = inet::api::Socket(inet::api::udp);  // 创建UDP套接字
       if (_ip.empty()) inet::api::Bind(_sock, _port);  // 如果未指定IP地址,则绑定到任意本地地址
       else inet::api::Bind(_sock, _ip, _port);  // 否则绑定到指定IP地址和端口
   }

protected:
   int _sock;  // 套接字描述符
   std::string _ip;  // IP地址
   uint16_t _port;  // 端口号
};

// UDP客户端类
class client {
public:
   // 构造函数,指定服务器IP地址和端口
   client(const std::string& svr_ip, uint16_t svr_port)
       : _sock(0), _sip(svr_ip), _sport(svr_port) {
       _sock = inet::api::Socket(inet::api::udp);  // 创建UDP套接字
   }

   // 发送数据到服务器函数
   int sendto(const std::string& msg) 
   { return inet::api::Sendto(_sock, msg, _sip, _sport); }

   // 接收数据函数
   int recvfrom(std::string* msg, size_t len) 
   { return inet::api::Recvfrom(_sock, msg, len); }

   // 析构函数,关闭套接字
   ~client() { close(_sock); }

protected:
   int _sock;  // 套接字描述符
   std::string _sip;  // 服务器IP地址
   uint16_t _sport;  // 服务器端口号
};
}
}

 

3. 网络通信设计

3.1 UDP通信

UDP客户端

UDP客户端的设计采用了多线程的方式,一个线程负责发送消息,另一个线程负责接收消息。这种设计允许客户端在发送消息的同时,能够实时接收服务器的响应,从而实现双向通信。

class udp_client
{
public:
   // 构造函数,接收服务器IP地址和端口号
   udp_client(std::string sip, uint16_t sport) : _sock(0), _sip(sip), _sport(sport)
   {}

   // 析构函数,关闭套接字并等待发送和接收线程结束
   ~udp_client() {
       close(_sock);
       _sender.join();
       _recver.join();
   }

   // 初始化函数,创建UDP套接字,并启动发送和接收线程
   void init() {
       _sock = socket(AF_INET, SOCK_DGRAM, 0);
       if (_sock < 0) exit(SOCKET_ERR);
       _sender = std::thread(&udp_client::send, this);
       _recver = std::thread(&udp_client::recv, this);
   }

   // 发送消息函数,运行在独立的线程中
   void send()
   {
       struct sockaddr_in peer; // 定义服务器地址结构体
       peer.sin_family = AF_INET; // 设置地址族为IPv4
       peer.sin_addr.s_addr = inet_addr(_sip.c_str()); // 设置服务器IP地址
       peer.sin_port = htons(_sport); // 设置服务器端口号

       // 无限循环,等待用户输入消息并发送到服务器
       while (true)
       {
           std::string msg;
           std::cout << "please input:> ";
           getline(std::cin, msg);
           sendto(_sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
       }
   }

   // 接收消息函数,运行在独立的线程中
   void recv()
   {
       while (true)
       {
           char buf[1024] = {0}; // 接收缓冲区
           struct sockaddr_in tmp; // 临时地址结构体,用于接收发送方的地址信息
           socklen_t len = sizeof(tmp); // 地址结构体长度
           ssize_t s = recvfrom(_sock, buf, sizeof(buf), 0, (struct sockaddr*)&tmp, &len);
           if (s > 0) buf[s] = 0; // 如果接收到数据,在数据末尾添加字符串结束符
           else continue; // 如果接收失败,继续下一次接收
           std::cout << "server return# " << buf << std::endl; // 打印接收到的消息
       }
   }

private:
   int _sock; // UDP套接字描述符
   std::string _sip; // 服务器IP地址
   uint16_t _sport; // 服务器端口号

   std::thread _sender; // 发送消息的线程
   std::thread _recver; // 接收消息的线程
};
UDP服务端

UDP服务端的设计相对简单,它只需要创建一个套接字,绑定到指定的端口,然后无限循环接收客户端的消息并回复。

class udp_server
{
public:
   // 构造函数,接收端口号
   udp_server(uint16_t port) : _sock(0), _port(port)
   {}

   // 析构函数,关闭套接字
   ~udp_server()
   {
       close(_sock);
   }

   // 初始化函数,创建UDP套接字并绑定到指定端口
   void init()
   {
       _sock = socket(AF_INET, SOCK_DGRAM, 0);
       if (_sock < 0) exit(SOCKET_ERR);

       struct sockaddr_in local; // 定义本地地址结构体
       local.sin_family = AF_INET; // 设置地址族为IPv4
       local.sin_addr.s_addr = INADDR_ANY; // 设置IP地址为任意本地地址
       local.sin_port = htons(_port); // 设置端口号

       if (bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
           exit(BIND_ERR); // 如果绑定失败,退出程序
   }

   // 启动服务器函数,用于接收客户端消息并回复
   void start()
   {
       char buf[1024] = {0}; // 接收缓冲区
       while (true)
       {
           struct sockaddr_in peer; // 定义客户端地址结构体
           socklen_t len = sizeof(peer); // 地址结构体长度

           ssize_t s = recvfrom(_sock, buf, sizeof(buf), 0, (struct sockaddr*)&peer, &len);
           if (s > 0) buf[s] = 0; // 如果接收到数据,在数据末尾添加字符串结束符
           else continue; // 如果接收失败,继续下一次接收

           std::string cip = inet_ntoa(peer.sin_addr); // 获取客户端IP地址
           uint16_t cport = ntohs(peer.sin_port); // 获取客户端端口号
           std::cout << '[' << cip << ':' << cport << "] " << buf << std::endl; // 打印接收到的消息

           std::string rsp = buf; // 回复消息与接收到的消息相同
           sendto(_sock, rsp.c_str(), rsp.size(), 0, (struct sockaddr*)&peer, sizeof(peer)); // 发送回复消息
       }
   }

private:
   int _sock; // UDP套接字描述符
   uint16_t _port; // 端口号
};

3.2 TCP通信

TCP客户端

TCP客户端的设计包括创建套接字、连接服务器、发送和接收消息。

class tcp_client
{
public:
   // 构造函数,接收服务器IP地址和端口号
   tcp_client(std::string sip, uint16_t sport) : _sock(0), _sip(sip), _sport(sport)
   {}

   // 析构函数,关闭套接字
   ~tcp_client()
   {
       close(_sock);
   }

   // 初始化函数,创建TCP套接字并尝试连接到服务器
   void init()
   {
       // 1. 创建套接字
       _sock = socket(AF_INET, SOCK_STREAM, 0);
       if (_sock < 0)
       {
           std::cerr << "socket error" << std::endl;
           exit(SOCKET_ERR);
       }
       std::cout << "socket success " << _sock << std::endl;

       // 2. 连接服务器
       struct sockaddr_in peer; // 定义服务器地址结构体
       memset(&peer, 0, sizeof(peer)); // 清空结构体
       peer.sin_family = AF_INET; // 设置地址族为IPv4
       peer.sin_port = htons(_sport); // 设置服务器端口号
       inet_aton(_sip.c_str(), &peer.sin_addr); // 设置服务器IP地址

       int cnt = 5; // 设置重试次数
       while (connect(_sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
       {
           std::cerr << "connect failed, remaining try " << cnt-- << std::endl;
           if (cnt == 0)
           {
               std::cerr << "connect error" << std::endl;
               exit(CONNECT_ERR);
           }
           sleep(1); // 等待1秒后重试
       }
       std::cout << "connect success" << std::endl;
   }

   // 启动客户端函数,用于发送消息给服务器并接收回复
   void start()
   {
       while (true)
       {
           std::string msg;
           std::cout << "please input:> ";
           getline(std::cin, msg); // 读取用户输入

           send(_sock, msg.c_str(), msg.size(), 0); // 发送消息给服务器

           char buf[1024] = {0}; // 接收缓冲区
           ssize_t s = recv(_sock, buf, sizeof(buf), 0); // 接收服务器回复
           if (s > 0)
           {
               buf[s] = 0; // 在数据末尾添加字符串结束符
               std::cout << "server return " << buf << std::endl; // 打印服务器回复
           }
           else if (s == 0)
           {
               close(_sock); // 如果服务器关闭连接,关闭套接字
               std::cout << "server quit" << std::endl;
               break; // 退出循环
           }
           else
           {
               close(_sock); // 如果接收失败,关闭套接字
               std::cerr << "recv error " << strerror(errno) << std::endl; // 打印错误信息
               break; // 退出循环
           }
       }
   }

private:
   int _sock; // TCP套接字描述符
   std::string _sip; // 服务器IP地址
   uint16_t _sport; // 服务器端口号
};
TCP服务端

TCP服务端的设计需要处理监听、接受连接、发送和接收消息。

class tcp_client
{
public:
    // 构造函数,接收服务器的IP地址和端口号作为参数
    tcp_client(std::string sip, uint16_t sport) : _sock(0), _sip(sip), _sport(sport)
    {}

    // 析构函数,在对象销毁时关闭套接字
    ~tcp_client()
    {
        close(_sock);
    }

    // 初始化函数,用于创建TCP套接字并尝试连接到服务器
    void init()
    {
        // 1. 创建TCP套接字
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            // 如果套接字创建失败,输出错误信息并退出程序
            std::cerr << "socket error" << std::endl;
            exit(SOCKET_ERR);
        }
        std::cout << "socket success " << _sock << std::endl;

        // 2. 连接服务器
        struct sockaddr_in peer; // 定义服务器地址结构体
        memset(&peer, 0, sizeof(peer)); // 清空结构体
        peer.sin_family = AF_INET; // 设置地址族为IPv4
        peer.sin_port = htons(_sport); // 设置服务器端口号
        inet_aton(_sip.c_str(), &peer.sin_addr); // 设置服务器IP地址

        int cnt = 5; // 设置重试次数
        while (connect(_sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
        {
            // 如果连接失败,输出剩余尝试次数并重试
            std::cerr << "connect failed, remaining try " << cnt-- << std::endl;
            if (cnt == 0)
            {
                // 如果尝试次数用完,输出错误信息并退出程序
                std::cerr << "connect error" << std::endl;
                exit(CONNECT_ERR);
            }
            sleep(1); // 等待1秒后重试
        }
        std::cout << "connect success" << std::endl;
    }

    // 启动客户端函数,用于发送消息给服务器并接收回复
    void start()
    {
        while (true)
        {
            std::string msg;
            std::cout << "please input:> ";
            getline(std::cin, msg); // 读取用户输入

            send(_sock, msg.c_str(), msg.size(), 0); // 发送消息给服务器

            char buf[1024] = {0}; // 接收缓冲区
            ssize_t s = recv(_sock, buf, sizeof(buf), 0); // 接收服务器回复
            if (s > 0)
            {
                buf[s] = 0; // 在数据末尾添加字符串结束符
                std::cout << "server return " << buf << std::endl; // 打印服务器回复
            }
            else if (s == 0)
            {
                close(_sock); // 如果服务器关闭连接,关闭套接字
                std::cout << "server quit" << std::endl;
                break; // 退出循环
            }
            else
            {
                close(_sock); // 如果接收失败,关闭套接字
                std::cerr << "recv error " << strerror(errno) << std::endl; // 打印错误信息
                break; // 退出循环
            }
        }
    }

private:
    int _sock; // TCP套接字描述符
    std::string _sip; // 服务器IP地址
    uint16_t _sport; // 服务器端口号
};

网络套接字编程的本质是利用系统调用从零编写应用层,不是使用应用层。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1674181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EasyNmon服务器性能监控工具环境搭建

一、安装jdk环境 1、看我这篇博客 https://blog.csdn.net/weixin_54542209/article/details/138704468 二、下载最新easyNmon包 1、下载地址 https://github.com/mzky/easyNmon/releases wget https://github.com/mzky/easyNmon/releases/download/v1.9/easyNmon_AMD64.tar.…

ansible利用playbook 部署lamp架构

搭建参考&#xff1a;ansible批量运维管理-CSDN博客 定义ansible主机清单 [rootansible-server ~]# vim /etc/hosts 192.168.200.129 host01 192.168.200.130 host02 [rootansible-server ~]# vim /etc/ansible/hosts [webserver] host01 host02 在ansible端编写index.html…

引用和实体完整性

本文将详细讲解在数据库服务器级具有引用和实体完整性的好处&#xff0c;包括如何在字段中设置默认值、检查约束和引用约束&#xff0c;以及在何时发生约束检查。通过遵循这些指导原则&#xff0c;可以确保数据的一致性和准确性。 1.完整性 完整性指数据库中数据的准确性或正确…

安科瑞为蔚来充换电站数字化运维提供解决方案

2023年12月23日蔚来宣布了一项重要的计划&#xff0c;表示蔚来将在2024年新建1000座换电站&#xff0c;这将使蔚来的换电站总数达到3310座&#xff0c;2025年蔚来将建成覆盖“九纵九横十九大城市群”的高速换电网络&#xff0c;并且全球换电站数量将超过4000座。此外&#xff0…

国内使用 CloudFlare 避坑指南

最近明月收到了不少新手使用 CloudFlare 的求助,发现很多首次使用 CloudFlare 的甚至包括已经在使用 CloudFlare 的站长们对 CloudFlare 的使用有很多的误区,再加上国内简中互联网上有关 CloudFlare 的教程良莠不齐,更是加深了新手使用 CloudFlare 入坑的概率,让一些别有用…

第二证券投资策略|国产人形机器人进展飞速 数据要素万亿蓝海市场启动在即

昨日&#xff0c;A股震荡回调&#xff0c;沪指盘中一度翻红&#xff0c;尾盘再度回落&#xff1b;深证成指、创业板指弱势下探。截至收盘&#xff0c;沪指跌0.21%报3148.02点&#xff0c;深证成指跌0.6%报9673.32点&#xff0c;创业板指跌0.95%报1860.37点&#xff0c;北证50指…

FloodFill算法---BFS

目录 一、前言 二、算法模板套路 2.1 创建所需的全局变量&#xff1a; 2.2 BFS模板&#xff1a; 2.3 细节处理&#xff1a; 三、例题练习 3.1 例题1&#xff1a;图像渲染 3.2 例题2&#xff1a;岛屿数量 3.3 例题3&#xff1a;岛屿的最大面积 3.4 例题4&#xff1a;被…

易康001:易康多尺度分割结果异常

前言 易康是一种在遥感领域常用的数据处理软件&#xff0c;它主要是用于面向对象的分类&#xff0c;涵盖了分割、模糊分类、监督分类等流程。但是在进行多尺度分割时&#xff0c;往往会遇到一些问题&#xff0c;例如下面图片所示&#xff1a; 1 多尺度分割问题 这种问题一般是…

长图拼接技巧大揭秘:轻松实现横向拼接,一键批量处理方法

在数字化时代&#xff0c;我们经常会遇到需要将多张图片拼接成一张长图的情况&#xff0c;无论是用于制作海报、展示报告&#xff0c;还是制作社交媒体上的长图故事&#xff0c;掌握长图拼接的技巧都显得尤为重要。本文将为大家揭秘长图拼接的实用技巧&#xff0c;并介绍办公提…

计算机存储器分级

从需求上讲&#xff0c;我们希望存储器速度快、体积小、能耗低、散热好、断电数据不丢失。但在现实中&#xff0c;我们往往无法把所有需求都实现。 首先来了解一下RAM和ROM的区别&#xff1a; RAM&#xff08;Random Access Memory&#xff09;也叫随机存取存储器&#xff0c;R…

2024年6款用于搭建AI知识库的最佳SaaS软件

现如今&#xff0c;越来越多的企业开始意识到搭建高效、智能的AI知识库是非常重要的&#xff0c;并在不断了解和搭建AI知识库。如何高效搭建出有效的AI知识库是很多企业存在的问题。在2024年&#xff0c;有哪些SaaS软件是搭建AI知识库的最佳软件呢&#xff1f;本文将推荐6个用于…

Comau柯马机器人维修故障分类

在柯马机器人的使用过程中&#xff0c;常见的是Comau机械手减速器故障。那么&#xff0c;我们一起来探讨一下柯马机械臂维修减速机故障的问题。Comau工业机械手减速器故障分类 1. 异响 机器人在工作过程中发出异常声响&#xff0c;可能是柯马机械臂减速器内部磨损或零件松动引起…

安科瑞智慧用电解决方案 九小场所、人员密集场所电气火灾预警系统

安科瑞Acrelcloud-6000安全用电管理平台是针对我国当前电气火灾事故频发而创新的一套电气火灾预警和预防管理系统.系统通过物联网技术对电气引发火灾的主要因素&#xff08;导线温度、电流、电压和漏电流&#xff09;进行不间断的数据跟踪与统计分析&#xff0c;实时发现电气线…

【数据结构】线性表----链表详解

数据结构—-链表详解 目录 文章目录 链表的定义链表的构成链表的分类双向和单向带头和不带头循环和不循环 链表的命名基本操作的实现初始化打印取值查找插入指定位置插入删除删除销毁 部分其他链表的代码实现循环链表双向链表 优点/缺点&#xff08;对比顺序表&#xff09;优点…

冰川秘境:全球冰川可视化大屏带你穿越冰原

在浩瀚无垠的宇宙中&#xff0c;地球以其独特的蓝色光环吸引着人们的目光。而在这颗蓝色星球上&#xff0c;冰川这一大自然的杰作&#xff0c;更是以其壮美与神秘&#xff0c;让人们心驰神往。 从阿尔卑斯山脉的冰川到南极洲的冰盖&#xff0c;从格陵兰岛的冰山到喜马拉雅山脉的…

美国多IP服务器为企业的数据分析提供了强大的技术支持

美国多IP服务器为企业的数据分析提供了强大的技术支持 在当今数字化时代&#xff0c;数据分析已经成为企业决策和战略规划的核心。而美国多IP服务器则为企业提供了强大的技术支持&#xff0c;帮助它们有效地进行数据分析&#xff0c;从而更好地理解市场、优化运营&#xff0c;…

【源码】购物返利源码每日分红 服务器打包完整版淘宝/京东/亚马逊等刷单平台源码

购物返利源码每日分红 服务器打包完整版淘宝/京东/亚马逊等刷单平台源码 好友分享的购物返利系统带分红&#xff0c;功能很强大的&#xff0c;类似矿机那种源码&#xff01;请勿违法用途&#xff01;源码和数据库都不缺。简单看了下搭建还是非常简单的&#xff01; 东西如下图&…

Android XML的使用详解

一、布局文件&#xff1a; 在layout目录下&#xff0c;使用比较广泛&#xff1b;我们可以为应用定义两套或多套布局&#xff0c;例如&#xff1a;可以新建目录layout_land(代表手机横屏布局)&#xff0c;layout_port(代表手机竖屏布局)&#xff0c;系统会根据不同情况自动找到…

顺序表的实现(迈入数据结构的大门)(1)

上一节我们认识到了什么是数据结构 这一节我们就来实现第一个数据结构的实现 思考一个问题&#xff1a; 假定一个数组&#xff0c;空间为10&#xff0c;已经使用了5个&#xff0c;向其中插入数据的步骤&#xff1a; 1.插入数据&#xff0c;我们先要求数组长度&#xff0c;其…

23、Flink 的 Savepoints 详解

Savepoints 1.什么是 Savepoints Savepoint 是依据 Flink checkpointing 机制所创建的流作业执行状态的镜像&#xff0c;可以使用 Savepoint 进行 Flink 作业的停止、重启或更新。 Savepoint 由两部分组成&#xff1a;稳定存储&#xff08;例如 HDFS&#xff0c;S3&#xff…