开发框架-网络通讯客户端与服务端

// socket通讯的服务端类
class ctcpserver
{
private:
    int m_socklen;                                // 结构体struct sockaddr_in的大小。
    struct sockaddr_in m_clientaddr;   // 客户端的地址信息。
    struct sockaddr_in m_servaddr;     // 服务端的地址信息。
    int  m_listenfd;                               // 服务端用于监听的socket。
    int  m_connfd;                                // 客户端连接上来的socket。
public:
    ctcpserver():m_listenfd(-1),m_connfd(-1) {}  // 构造函数。

    // 服务端初始化。
    // port:指定服务端用于监听的端口。
    // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
    bool initserver(const unsigned int port,const int backlog=5); 

    // 从已连接队列中获取一个客户端连接,如果已连接队列为空,将阻塞等待。
    // 返回值:true-成功的获取了一个客户端连接,false-失败,如果accept失败,可以重新accept。
    bool accept();

    // 获取客户端的ip地址。
    // 返回值:客户端的ip地址,如"192.168.1.100"。
    char *getip();

    // 接收对端发送过来的数据。
    // buffer:存放接收数据的缓冲区。
    // ibuflen: 打算接收数据的大小。
    // itimeout:等待数据的超时时间(秒):-1-不等待;0-无限等待;>0-等待的秒数。
    // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
    bool read(string &buffer,const int itimeout=0);                           // 接收文本数据。
    bool read(void *buffer,const int ibuflen,const int itimeout=0);   // 接收二进制数据。

    // 向对端发送数据。
    // buffer:待发送数据缓冲区。
    // ibuflen:待发送数据的大小。
    // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
    bool write(const string &buffer);                          // 发送文本数据。
    bool write(const void *buffer,const int ibuflen);   // 发送二进制数据。

    // 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
    void closelisten();

    // 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
    void closeclient();

    ~ctcpserver();  // 析构函数自动关闭socket,释放资源。
};

关于read函数的细节

bool ctcpserver::read(string &buffer,const int itimeout)  // 接收文本数据。
{
    if (m_connfd==-1) return false;

    return(tcpread(m_connfd,buffer,itimeout));
}

tcpread函数

bool tcpread(const int sockfd,string &buffer,const int itimeout)    // 接收文本数据。
{
    if (sockfd==-1) return false;

    // 如果itimeout>0,表示等待itimeout秒,如果itimeout秒后接收缓冲区中还没有数据,返回false。
    if (itimeout>0)
    {
        struct pollfd fds;
        fds.fd=sockfd;
        fds.events=POLLIN;
        if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
    }

    // 如果itimeout==-1,表示不等待,立即判断socket的接收缓冲区中是否有数据,如果没有,返回false。
    if (itimeout==-1)
    {
        struct pollfd fds;
        fds.fd=sockfd;
        fds.events=POLLIN;
        if ( poll(&fds,1,0) <= 0 ) return false;
    }

    int buflen=0;

    // 先读取报文长度,4个字节。
    if (readn(sockfd,(char*)&buflen,4) == false) return false;

    buffer.resize(buflen);   // 设置buffer的大小。

    // 再读取报文内容。
    if (readn(sockfd,&buffer[0],buflen) == false) return false;

    return true;
}

为什么这里先读取报文内容:

主要为了解决粘包和分包问题

TCP是面向流的协议,发送的数据在接收端并没有明确的边界。多个报文可能会被合并成一个大的数据块(粘包),或者一个报文可能被拆分成多个数据块。读取报文长度后,可以根据长度信息来判断何时一个完整的报文已经接收完毕。

将 &buflen 强制类型转换为 char* 类型,可以解决类型不匹配的问题。这样,recv 函数就会将接收到的字节正确地写入到 buflen 变量所指向的内存位置。

报文长度告诉接收方整个报文的实际大小,这样接收方可以准确地分配合适大小的内存空间来存储数据,避免数据溢出或者不完整的情况

明确报文长度可以帮助接收方更高效地处理数据。它可以避免不必要的等待和重试,因为接收方知道什么时候应该开始和结束读取数据。

poll函数

poll 函数是一个系统调用,用于在多个文件描述符上等待事件的发生。它允许程序在等待时不阻塞整个进程,而是允许程序继续执行其他任务或进行轮询,直到所关心的文件描述符上有指定的事件发生为止。

函数原型

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明

  1. fds

    • 类型struct pollfd *
    • 描述:指向一个 pollfd 结构数组的指针。每个 pollfd 结构描述了一个待检查的文件描述符及其关心的事件。
  2. nfds

    • 类型nfds_t
    • 描述:表示 fds 数组中元素的数量。
  3. timeout

    • 类型int
    • 描述:表示超时时间,单位是毫秒。指定为 -1 表示 poll 调用将无限期阻塞,直到有事件发生;指定为 0 表示 poll 调用立即返回,不阻塞;指定大于 0 的值表示在超时时间内等待事件发生。

返回值

  • 如果 poll 调用成功,返回值为发生事件的文件描述符数量。
  • 如果超时时间到达而没有任何文件描述符上的事件发生,返回值为 0
  • 如果发生错误,返回值为 -1,并设置 errno 指示具体错误。

struct pollfd 结构体

pollfd 结构体用于描述一个文件描述符及其关心的事件:

struct pollfd {
    int fd;         // 文件描述符
    short events;   // 等待的事件(如 POLLIN、POLLOUT 等)
    short revents;  // 实际发生的事件(由系统填充)
};

readn函数

bool readn(const int sockfd,char *buffer,const size_t n)
{
    int nleft=n;    // 剩余需要读取的字节数。
    int idx=0;       // 已成功读取的字节数。
    int nread;       // 每次调用recv()函数读到的字节数。

    while(nleft > 0)
    {
        if ( (nread=recv(sockfd,buffer+idx,nleft,0)) <= 0) return false;

        idx=idx+nread;
        nleft=nleft-nread;
    }

    return true;
}

recv函数:

recv(int sockfd, void *buf, size_t len, int flags)

参数解释:

  1. sockfd:这是一个已经通过 socket 函数创建的套接字描述符(socket descriptor)。recv 函数通过这个描述符来识别和操作特定的网络连接。

  2. bufbuf 是一个指向存放接收数据的缓冲区的指针。接收到的数据将会被写入到这个缓冲区中。它的类型是 void*,即可以指向任意类型的数据。通常在使用时会进行类型转换,比如 (char*)buf

  3. lenlen 是 buf 所指向的缓冲区的长度,即能够接收的最大字节数。这个参数告诉 recv 函数缓冲区的大小,避免写入超出缓冲区界限的数据。

  4. flagsflags 是一个用于控制接收行为的参数,可以设置为0或者其他特定的标志。常见的选项有:

    • 可能还有其他平台特定的标志,用于控制接收的行为。
    • MSG_DONTWAIT:非阻塞操作,立即返回,如果没有数据可接收则返回错误。
    • MSG_WAITALL:阻塞操作,直到接收到指定长度的数据。

recv 函数返回值

  • 如果成功接收数据,recv 函数返回接收到的字节数(可以小于请求的字节数)。
  • 如果连接已关闭(对端关闭),recv 返回 0 表示连接结束。
  • 如果发生错误,recv 返回一个负数,表示特定的错误类型(如 -1 表示错误,可以通过 errno 获取具体错误码)

write中有writen函数

使用write你函数取代send函数因为:

send函数共能是把待发送数据拷贝到发送缓冲区,如果缓冲区内存不足,返回本次已拷贝的字节数;为保证发送成功,应该循环调用send函数;在wirten中循环调用了send函数

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值