项目-SERVER模块-Socket模块


一、Socket模块是什么?

Socket模块是对套接字操作封装的⼀个模块,主要实现的socket的各项操作。
在这里插入图片描述

二、代码实现

1.成员变量

这行代码表示在类(class)的私有(private)部分声明了一个整型变量 _sockfd,用于存储某个类的套接字文件描述符。在类的私有部分声明的成员只能在该类的成员函数内部访问,外部无法直接访问。

private:
    int _sockfd;

2.构造、析构函数

这段代码展示了一个名为Socket的类的构造函数和析构函数的定义:

  1. Socket() : _sockfd(-1) {}: 这是一个无参数的构造函数,用于初始化Socket类的对象。在这个构造函数中,通过初始化列表将_sockfd成员变量设置为-1,表示套接字文件描述符的初始值为-1。

  2. Socket(int sockfd) : _sockfd(sockfd) {}: 这是一个带有整型参数的构造函数,用于初始化Socket类的对象并指定套接字文件描述符的值。在这个构造函数中,通过初始化列表将_sockfd成员变量设置为传入的参数sockfd的值。

  3. ~Socket() { Close(); }: 这是Socket类的析构函数,用于释放资源和清理工作。在析构函数中调用了Close()函数,该函数应该是Socket类的一个成员函数,用于关闭套接字。通过在析构函数中调用Close()函数,确保在对象被销毁时及时关闭相关资源,避免资源泄漏。

public:
    Socket() : _sockfd(-1) {}
    Socket(int sockfd) : _sockfd(sockfd) {}
    ~Socket() { Close(); }

3.获取套接字文件描述符

这段代码定义了一个名为get_fd()的成员函数,用于获取类中私有成员变量_sockfd的值(套接字文件描述符)。该函数返回整型值,表示获取到的套接字文件描述符。

通过定义这样的成员函数,可以在类外部获取Socket类对象的套接字文件描述符,同时保持_sockfd作为私有成员的封装性。这样的设计方式遵循了面向对象编程的封装原则,将类的数据隐藏起来,只允许通过成员函数来进行访问和操作,从而提高了代码的安全性和可维护性。

    // 获取套接字文件描述符
    int get_fd() { return _sockfd; }

4.创建套接字

    // 创建套接字
    bool Create()
    {
        // 调用socket函数创建套接字
        // int socket(int domain, int type, int protocol)
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        // 检查套接字创建是否成功
        if (_sockfd < 0)
        {
            // 套接字创建失败时输出日志信息
            INF_LOG("Socket creation failed");
            return false;
        }

        // 套接字创建成功
        return true;
    }

5.绑定地址信息

// 绑定地址和端口
bool Bind(const std::string &ip, uint16_t port)
{
    // 创建一个 sockaddr_in 结构体并设置相关参数
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;  // 设置地址族为IPv4

    // 将端口号转换为网络字节顺序
    addr.sin_port = htons(port);

    // 将IP地址转换为网络字节顺序并填入结构体中
    addr.sin_addr.s_addr = inet_addr(ip.c_str());

    // 计算地址结构体的长度
    socklen_t len = sizeof(struct sockaddr_in);

    // 调用bind函数将套接字和地址绑定
    int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
    if (ret < 0)
    {
        // 绑定失败时输出错误日志信息
        ERR_LOG("BIND ADDRESS FAILED!");
        return false;
    }

    // 绑定成功
    return true;
}

6.开始监听连接请求

// 开始监听连接请求
bool Listen(int backlog = MAX_LISTEN)
{
    // 调用listen函数开始监听连接请求
    int ret = listen(_sockfd, backlog);
    if (ret < 0)
    {
        // 监听失败时输出错误日志信息
        ERR_LOG("SOCKET LISTEN FAILED!");
        return false;
    }

    // 监听成功
    return true;
}

7.向服务器发起连接

// 向服务器发起连接
bool Connection(const std::string &ip, uint16_t port)
{
    // 创建一个 sockaddr_in 结构体并设置相关参数
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;  // 设置地址族为IPv4

    // 将端口号转换为网络字节顺序
    addr.sin_port = htons(port);

    // 将IP地址转换为网络字节顺序并填入结构体中
    addr.sin_addr.s_addr = inet_addr(ip.c_str());

    // 计算地址结构体的长度
    socklen_t len = sizeof(struct sockaddr_in);

    // 调用connect函数发起连接请求
    int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
    if (ret < 0)
    {
        // 连接失败时输出错误日志信息
        ERR_LOG("CONNECT SERVER FAILED!");
        return false;
    }

    // 连接成功
    return true;
}

8.获取新连接

// 获取新连接
int Accept()
{
    // 调用accept函数接受新的连接
    // int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
    int newfd = accept(_sockfd, NULL, NULL);
    if (newfd < 0)
    {
        // 接受连接失败时输出错误日志信息
        ERR_LOG("SOCKET ACCEPT FAILED!");
        return -1;
    }

    // 返回新的文件描述符
    return newfd;
}

9.接收数据

// 接收数据
ssize_t Recv(void *buf, size_t len, int flag = 0)
{
    // 调用recv函数接收数据
    ssize_t s = recv(_sockfd, buf, len, flag);
    if (s <= 0)
    {
        if (errno == EINTR || errno == EAGAIN)
        {
            // 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0
            INF_LOG("Recv has not ready!");
            return 0;
        }
        // 其他接收失败的情况,记录错误日志信息并返回-1
        ERR_LOG("read failed!");
        return -1;
    }

    // 返回实际接收的数据长度
    return s;
}

10.非阻塞接收数据

// 非阻塞接收数据
ssize_t NonBlockRecv(void *buf, size_t len)
{
    // 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收
    return Recv(buf, len, MSG_DONTWAIT);
}

11.发送数据

// 发送数据
ssize_t Send(const void *buf, size_t len, int flag = 0)
{
    // 调用send函数发送数据
    // ssize_t send(int sockfd, void *data, size_t len, int flag);
    ssize_t ret = send(_sockfd, buf, len, flag);
    if (ret < 0)
    {
        // 发送失败时记录错误日志信息并返回-1
        ERR_LOG("SOCKET SEND FAILED!!");
        return -1;
    }

    // 返回实际发送的数据长度
    return ret;
}

12.非阻塞发送数据

// 非阻塞发送数据
ssize_t NonBlockSend(void *buf, size_t len)
{
    // 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送
    return Send(buf, len, MSG_DONTWAIT);
}

13.关闭套接字

// 关闭套接字
void Close()
{
    // 检查套接字是否有效,如果有效则关闭套接字
    if (_sockfd != -1)
        close(_sockfd);
    
    // 将套接字文件描述符设置为无效值
    _sockfd = -1;
}

14.创建一个服务端连接

// 创建一个服务端连接
bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
{
    // 1. 创建套接字 
    // 2. 绑定地址 
    // 3. 开始监听 
    // 4. 设置非阻塞 
    // 5. 启动地址重用

    // 如果创建套接字失败,则返回false
    if (Create() == false) return false;

    // 如果需要设置为非阻塞模式,则调用NonBlock函数
    if (block_flag) NonBlock();

    // 绑定地址,如果绑定失败则返回false
    if (Bind(ip, port) == false) return false;

    // 开始监听,如果监听失败则返回false
    if (Listen() == false) return false;

    // 启动地址重用
    ReuseAddress();

    return true;
}

15.创建一个客户端连接

// 创建一个客户端连接
bool CreateClient(uint16_t port, const std::string &ip)
{
    // 1. 创建套接字 
    // 2. 指向连接服务器

    // 如果创建套接字失败,则返回false
    if (Create() == false)  
        return false;

    // 连接服务器,如果连接失败则返回false
    if (Connection(ip, port) == false)
        return false;

    return true;
}

16.设置套接字选项——开启地址端口重用

// 设置套接字选项——开启地址端口重用
void ReuseAddress()
{
    // 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用
    int val = 1;
    setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));
    val = 1;
    setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));
}

17. 设置套接字阻塞属性——设置为非阻塞

// 设置套接字阻塞属性——设置为非阻塞
void NonBlock()
{
    // 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式
    //int fcntl(int fd,int cmd,.../* arg */);
    int flag = fcntl(_sockfd, F_GETFL, 0);
    fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
}

18.测试代码

这段代码是一个简单的基于Socket的服务器端程序,它创建一个服务器Socket并监听指定端口(8500)。然后在一个无限循环中接受客户端连接,接收客户端发送的数据,并将数据原样发送回客户端,最后关闭与客户端的连接。注意:需要打开两个终端分别运行服务器端和客户端

//服务器端
#include"../source/server.hpp"
int main(){
    Socket lst_sock;
    lst_sock.CreateServer(8500);
    while(1){
       int newfd =lst_sock.Accept();
       if(newfd <0){
           continue;
       }  
       Socket cli_sock(newfd);
       char buf[1024]={0};
       int ret= cli_sock.Recv(buf, 1023);
       if(ret < 0){
           cli_sock.Close();
           continue;
       }
       cli_sock.Send(buf, ret);
       cli_sock.Close();
    }
    lst_sock.Close();
    return 0;
}
//客户端口
#include"../source/server.hpp"
int main()
{
    Socket cli_sock;
    cli_sock.CreateClient(8500,"127.0.0.1");
    std::string str="hello have a good day~";
    cli_sock.Send(str.c_str(),str.size());
    char buf[1024]={0};
    cli_sock.Recv(buf,1023);
    DBG_LOG("%s",buf);
    return 0;
}

测试结果:
在这里插入图片描述

19.整体源代码

// Socket //
#define MAX_LISTEN 1024
#define DEFAULT_IP "0.0.0.0"
class Socket
{
private:
    int _sockfd;
public:
    Socket() : _sockfd(-1) {}
    Socket(int sockfd) : _sockfd(sockfd) {}
    ~Socket() { Close(); }
    // 获取套接字文件描述符
    int get_fd() { return _sockfd; }

    // 创建套接字
    bool Create()
    {
        // 调用socket函数创建套接字
        // int socket(int domain, int type, int protocol)
        _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        // 检查套接字创建是否成功
        if (_sockfd < 0)
        {
            // 套接字创建失败时输出日志信息
            INF_LOG("Socket creation failed");
            return false;
        }

        // 套接字创建成功
        return true;
    }

    // 绑定地址和端口
    bool Bind(const std::string &ip, uint16_t port)
    {
        // 创建一个 sockaddr_in 结构体并设置相关参数
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;  // 设置地址族为IPv4

        // 将端口号转换为网络字节顺序
        addr.sin_port = htons(port);

        // 将IP地址转换为网络字节顺序并填入结构体中
        addr.sin_addr.s_addr = inet_addr(ip.c_str());

        // 计算地址结构体的长度
        socklen_t len = sizeof(struct sockaddr_in);

        // 调用bind函数将套接字和地址绑定
        int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
        if (ret < 0)
        {
            // 绑定失败时输出错误日志信息
            ERR_LOG("BIND ADDRESS FAILED!");
            return false;
        }

        // 绑定成功
        return true;
    }

    // 开始监听连接请求
    bool Listen(int backlog = MAX_LISTEN)
    {
        // 调用listen函数开始监听连接请求
        int ret = listen(_sockfd, backlog);
        if (ret < 0)
        {
            // 监听失败时输出错误日志信息
            ERR_LOG("SOCKET LISTEN FAILED!");
            return false;
        }

        // 监听成功
        return true;
    }


    // 向服务器发起连接
    bool Connection(const std::string &ip, uint16_t port)
    {
        // 创建一个 sockaddr_in 结构体并设置相关参数
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;  // 设置地址族为IPv4

        // 将端口号转换为网络字节顺序
        addr.sin_port = htons(port);

        // 将IP地址转换为网络字节顺序并填入结构体中
        addr.sin_addr.s_addr = inet_addr(ip.c_str());

        // 计算地址结构体的长度
        socklen_t len = sizeof(struct sockaddr_in);

        // 调用connect函数发起连接请求
        int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
        if (ret < 0)
        {
            // 连接失败时输出错误日志信息
            ERR_LOG("CONNECT SERVER FAILED!");
            return false;
        }

        // 连接成功
        return true;
    }

   
    // 获取新连接
    int Accept()
    {
        // 调用accept函数接受新的连接
        // int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
        int newfd = accept(_sockfd, NULL, NULL);
        if (newfd < 0)
        {
            // 接受连接失败时输出错误日志信息
            ERR_LOG("SOCKET ACCEPT FAILED!");
            return -1;
        }

        // 返回新的文件描述符
        return newfd;
    }


    // 接收数据
    ssize_t Recv(void *buf, size_t len, int flag = 0)
    {
        // 调用recv函数接收数据
        ssize_t s = recv(_sockfd, buf, len, flag);
        if (s <= 0)
        {
            if (errno == EINTR || errno == EAGAIN)
            {
                // 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0
                INF_LOG("Recv has not ready!");
                return 0;
            }
            // 其他接收失败的情况,记录错误日志信息并返回-1
            ERR_LOG("read failed!");
            return -1;
        }

        // 返回实际接收的数据长度
        return s;
    }

   
    // 非阻塞接收数据
    ssize_t NonBlockRecv(void *buf, size_t len)
    {
        // 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收
        return Recv(buf, len, MSG_DONTWAIT);
    }


    // 发送数据
    ssize_t Send(const void *buf, size_t len, int flag = 0)
    {
        // 调用send函数发送数据
        // ssize_t send(int sockfd, void *data, size_t len, int flag);
        ssize_t ret = send(_sockfd, buf, len, flag);
        if (ret < 0)
        {
            // 发送失败时记录错误日志信息并返回-1
            ERR_LOG("SOCKET SEND FAILED!!");
            return -1;
        }

        // 返回实际发送的数据长度
        return ret;
    }

   
    // 非阻塞发送数据
    ssize_t NonBlockSend(void *buf, size_t len)
    {
        // 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送
        return Send(buf, len, MSG_DONTWAIT);
    }


    // 关闭套接字
    void Close()
    {
        // 检查套接字是否有效,如果有效则关闭套接字
        if (_sockfd != -1)
            close(_sockfd);
        
        // 将套接字文件描述符设置为无效值
        _sockfd = -1;
    }

   
    // 创建一个服务端连接
    bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
    {
        // 1. 创建套接字 
        // 2. 绑定地址 
        // 3. 开始监听 
        // 4. 设置非阻塞 
        // 5. 启动地址重用

        // 如果创建套接字失败,则返回false
        if (Create() == false) return false;

        // 如果需要设置为非阻塞模式,则调用NonBlock函数
        if (block_flag) NonBlock();

        // 绑定地址,如果绑定失败则返回false
        if (Bind(ip, port) == false) return false;

        // 开始监听,如果监听失败则返回false
        if (Listen() == false) return false;

        // 启动地址重用
        ReuseAddress();

        return true;
    }

    // 创建一个客户端连接
    bool CreateClient(uint16_t port, const std::string &ip)
    {
        // 1. 创建套接字 
        // 2. 指向连接服务器

        // 如果创建套接字失败,则返回false
        if (Create() == false)  
            return false;

        // 连接服务器,如果连接失败则返回false
        if (Connection(ip, port) == false)
            return false;

        return true;
    }
   
    // 设置套接字选项——开启地址端口重用
    void ReuseAddress()
    {
        // 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用
        int val = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));
        val = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));
    }

   
    // 设置套接字阻塞属性——设置为非阻塞
    void NonBlock()
    {
        // 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式
        //int fcntl(int fd,int cmd,.../* arg */);
        int flag = fcntl(_sockfd, F_GETFL, 0);
        fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
    }
};
  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值