Linux(13)(中)

一.select代码讲解

关于这部分代码,我会重点讲解套接字还有select服务端的内容,其余代码不做讲解!关于下面参数介绍的时候:in(输入参数,我们交给OS)out(输出参数,OS交给我们)in/out(输入/输出函数,我们交给OS,然后OS在交给我们)

二.socket代码

2.1 基础部分

cpp 复制代码
// 定义套接字操作的致命错误码(2 开始,避开系统保留值0表示成功,1表示通用错误)
enum
{
    SocketErr = 2, // 创建套接字失败
    BindErr,       // 绑定地址失败
    ListenErr,     // 监听失败
};

// 监听队列的最大长度(等待连接的客户端数量上限)
const int backlog = 10;

Sock()
    : sockfd_(-1) // 构造函数:初始化文件描述符为无效值
{} 

~Sock()
{ 
    // 析构函数:自动关闭有效套接字,防止资源泄漏

    if (sockfd_ >= 0)//避免无效关闭无效的文件描述符
    {
        close(sockfd_); // 关闭底层文件描述符
        sockfd_ = -1;   // 重置为无效值,避免重复关闭
    }
}

关于这部分知识,重要的是enum这个枚举为什么要从2开始,注释有做解释,至于其他内容则无需讲解,看注释即可!

2.2 socket常见API

2.2.1 Socket

cpp 复制代码
void Socket()
    {   // 创建 TCP 套接字并启用地址/端口复用
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建 IPv4 流式套接字
        if (sockfd_ < 0)
        {
            lg(Fatal, "socket error, %s: %d", strerror(errno), errno);//
            exit(SocketErr); // 创建失败,程序终止
        }
        int opt = 1;
        // 启用 SO_REUSEADDR,允许快速重启服务
        setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    }

要点:

  • 明白socket函数的意思
cpp 复制代码
// 创建一个套接字,用于网络通信
// 返回一个文件描述符,代表该网络端点
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

// 参数:
//   domain   - [in] 协议族(如 AF_INET 表示 IPv4,AF_INET6 表示 IPv6)
//   type     - [in] 套接字类型(如 SOCK_STREAM 表示 TCP,SOCK_DGRAM 表示 UDP)
//   protocol - [in] 指定具体协议(通常设为 0,表示使用默认协议)

// 返回值:
//   成功:返回非负整数(新的套接字文件描述符)
//   失败:返回 -1,并设置 errno
  • 明白setsockopt函数的意思
cpp 复制代码
// 设置 socket 选项(控制底层网络行为)
// 常用于调整缓冲区、超时、重用地址等

#include <sys/socket.h>

int setsockopt(
    int sockfd,
    int level,
    int optname,
    const void *optval,
    socklen_t optlen
);

// 参数说明:
//   sockfd   - socket 文件描述符
//   level    - 选项层级:
//                SOL_SOCKET  → 通用 socket 层
//                IPPROTO_TCP → TCP 层
//                IPPROTO_IP  → IP 层
//   optname  - 具体选项名(如 SO_REUSEADDR)
//   optval   - 指向选项值的指针(通常为 int 或 struct)
//   optlen   - optval 的大小(字节数)

// 返回值:
//   成功:0
//   失败:-1,设置 errno

// 示例:

// 1. 允许端口重用(解决 "Address already in use")
int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

2.2.2 Bind

cpp 复制代码
// 绑定到本地任意 IP 的指定端口
void Bind(uint16_t port)
{                                       
    struct sockaddr_in local;           // 本地地址结构
    memset(&local, 0, sizeof(local));   // 清零结构体内存

    local.sin_family = AF_INET;         // 协议族设为 IPv4
    local.sin_port = htons(port);       // 主机字节序端口转网络字节序
    local.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地网络接口

    if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
        exit(BindErr); // 绑定失败,程序终止
    }
}

要点:

  • 明白Bind函数的意思
cpp 复制代码
// 将套接字绑定到指定的本地地址和端口
// 服务器通常需要显式 bind 客户端一般由系统自动分配
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 参数:
//   sockfd  - [in] 套接字文件描述符(由 socket() 创建)
//   addr    - [in] 指向本地地址结构的指针(如 struct sockaddr_in)
//   addrlen - [in] addr 结构体的字节大小(如 sizeof(struct sockaddr_in))

// 返回值:
//   成功:返回 0
//   失败:返回 -1 并设置 errno
  • 了解sockaddr_in结构体
cpp 复制代码
// IPv4 套接字地址结构体(用于 bind, connect, accept 等)
// 定义在 <netinet/in.h>

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族:AF_INET(必须)
    in_port_t      sin_port;     // 端口号(网络字节序,16 位)
    struct in_addr sin_addr;     // IPv4 地址(网络字节序,32 位)
    char           sin_zero[8];  // 填充字段(全置 0,使结构体与 sockaddr 大小一致)
};

// 成员说明:
//   sin_family - 固定为 AF_INET(表示 IPv4)
//   sin_port   - 用 htons() 转换主机字节序端口到网络字节序
//                例: sin_port = htons(8080);
//   sin_addr   - 用 inet_addr() 或 inet_pton() 设置 IP 地址
//                例: sin_addr.s_addr = inet_addr("192.168.1.1");
//                     或 inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);
//   sin_zero   - 必须清零(通常用 memset 或直接赋值)
  • in_addr也是一个结构体
cpp 复制代码
// 表示 IPv4 地址(32 位无符号整数,网络字节序)
// 定义在 <netinet/in.h>

#include <netinet/in.h>

struct in_addr {
    in_addr_t s_addr;  // 通常为 uint32_t,存储 IPv4 地址
};

// 关键点:
//   • s_addr 的值是 **网络字节序(大端)**
//   • 不能直接赋主机整数值(需用转换函数)

2.2.3 Listen

cpp 复制代码
// 将套接字设为监听状态
void Listen()
{ 
    if (listen(sockfd_, backlog) < 0)
    {
        lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
        exit(ListenErr); // 监听失败,程序终止
    }
}

要点:

  • 明白listen的函数
cpp 复制代码
// 将套接字设为监听状态 准备接收客户端连接请求(仅用于 TCP)
// 通常在 bind 之后调用
#include <sys/socket.h>

int listen(int sockfd, int backlog);

// 参数:
//   sockfd  - [in] 套接字文件描述符(必须是已 bind 的 TCP 套接字)
//   backlog - [in] 连接请求队列的最大长度(内核会根据系统限制调整)

// 返回值:
//   成功:返回 0
//   失败:返回 -1 并设置 errno

2.2.4 Accept

cpp 复制代码
// 接受新连接,并输出客户端地址信息
int Accept(std::string *clientip, uint16_t *clientport)
{      
    // 存储客户端地址                                                         
    struct sockaddr_in peer;                                     

    // 地址结构大小
    socklen_t len = sizeof(peer);                                

    // 阻塞等待连接
    int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len); 
    if (newfd < 0)
    {
        lg(Warning, "accept error, %s: %d", strerror(errno), errno);
        return -1; // 接受失败,返回无效 fd
    }
    char ipstr[64];
    // 将二进制 IPv4 地址安全转换为可读字符串
    inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
    *clientip = ipstr;                  // 输出客户端 IP 字符串
    *clientport = ntohs(peer.sin_port); // 网络字节序端口转主机字节序
    return newfd;                       // 返回新连接的文件描述符
}

要点:

  • 明白accept函数
cpp 复制代码
// 从监听套接字的连接请求队列中取出一个新连接
// 创建一个新的套接字用于与客户端通信(原监听套接字继续监听)
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 参数:
//   sockfd  - [in] 监听套接字的文件描述符(必须已调用 listen())
//   addr    - [out] 指向 sockaddr 结构的指针,用于接收客户端地址(可为 NULL)
//   addrlen - [in/out] 
//               输入:addr 缓冲区的字节大小
//               输出:实际写入的客户端地址结构体长度

// 返回值:
//   成功:返回新的已连接套接字描述符
//   失败:返回 -1 并设置 errno
  • 明白inet_ntop函数
cpp 复制代码
// 将网络字节序的 IPv4 或 IPv6 地址(二进制形式)
// 转换为标准可读字符串(如 "192.168.1.1" 或 "2001:db8::1")
#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

// 参数:
//   af    - [in] 地址族:
//               AF_INET  → src 指向 struct in_addr(IPv4)
//               AF_INET6 → src 指向 struct in6_addr(IPv6)
//   src   - [in] 指向包含网络字节序 IP 地址的结构体的指针
//   dst   - [out] 输出缓冲区,用于存放转换后的字符串
//   size  - [in] dst 缓冲区的大小(字节数)

// 返回值:
//   成功:返回 dst 指针
//   失败:返回 NULL,并设置 errno(如 ENOSPC 表示缓冲区太小)

2.2.5 Connect

cpp 复制代码
// 主动连接到指定服务器
bool Connect(const std::string &ip, const uint16_t &port)
{                                   
    struct sockaddr_in peer;        // 服务器地址结构
    memset(&peer, 0, sizeof(peer)); // 初始化内存

    peer.sin_family = AF_INET;      // IPv4 协议
    peer.sin_port = htons(port);    // 端口转网络字节序

    // 将点分十进制 IP 字符串转换为二进制网络字节序
    inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

    int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer));
    if (n == -1)
    {
        std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
        return false; // 连接失败
    }
    return true; // 连接成功
}

要点:

  • 明白connect函数
cpp 复制代码
// 主动发起连接到指定服务器地址(用于 TCP 客户端)
// 建立与远程主机的连接后即可进行数据收发
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 参数:
//   sockfd  - [in] 套接字文件描述符(由 socket() 创建,通常为 SOCK_STREAM)
//   addr    - [in] 指向目标服务器地址结构的指针(如 struct sockaddr_in)
//   addrlen - [in] addr 结构体的字节大小(如 sizeof(struct sockaddr_in))

// 返回值:
//   成功:返回 0
//   失败:返回 -1 并设置 errno
  • 明白inet_ntop函数
cpp 复制代码
// 将网络字节序的 IPv4 或 IPv6 地址(二进制)转换为标准可读字符串格式
#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

// 参数:
//   af    - [in] 地址族:
//               AF_INET  → src 指向 struct in_addr(IPv4)
//               AF_INET6 → src 指向 struct in6_addr(IPv6)
//   src   - [in] 指向包含 IP 地址(网络字节序)的结构体的指针
//   dst   - [out] 用于存储输出字符串的缓冲区
//   size  - [in] dst 缓冲区的大小(以字节为单位)

// 返回值:
//   成功:返回 dst 指针
//   失败:返回 NULL,并设置 errno(常见错误:ENOSPC --- 缓冲区太小)

三.完整代码

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include "Log.hpp"

// 定义套接字操作的致命错误码(从 2 开始,避开系统保留值0表示成功,1表示通用错误)
enum
{
    SocketErr = 2, // 创建套接字失败
    BindErr,       // 绑定地址失败
    ListenErr,     // 监听失败
};

// 监听队列的最大长度(等待连接的客户端数量上限)
const int backlog = 10;

class Sock
{
public:
    Sock()
        : sockfd_(-1) // 构造函数:初始化文件描述符为无效值
    {} 

    ~Sock()
    { 
        // 析构函数:自动关闭有效套接字,防止资源泄漏

        if (sockfd_ >= 0)//避免无效关闭无效的文件描述符
        {
            close(sockfd_); // 关闭底层文件描述符
            sockfd_ = -1;   // 重置为无效值,避免重复关闭
        }
    }
    
    void Socket()
    {   // 创建 TCP 套接字并启用地址/端口复用
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建 IPv4 流式套接字
        if (sockfd_ < 0)
        {
            lg(Fatal, "socket error, %s: %d", strerror(errno), errno);//
            exit(SocketErr); // 创建失败,程序终止
        }
        int opt = 1;
        // 启用 SO_REUSEADDR,允许快速重启服务
        setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    }

    // 绑定到本地任意 IP 的指定端口
    void Bind(uint16_t port)
    {                                       
        struct sockaddr_in local;           // 本地地址结构
        memset(&local, 0, sizeof(local));   // 清零结构体内存

        local.sin_family = AF_INET;         // 协议族设为 IPv4
        local.sin_port = htons(port);       // 主机字节序端口转网络字节序
        local.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地网络接口

        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(BindErr); // 绑定失败,程序终止
        }
    }

    // 将套接字设为监听状态
    void Listen()
    { 
        if (listen(sockfd_, backlog) < 0)
        {
            lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
            exit(ListenErr); // 监听失败,程序终止
        }
    }

    // 接受新连接,并输出客户端地址信息
    int Accept(std::string *clientip, uint16_t *clientport)
    {                                                               
    struct sockaddr_in peer;                                     // 存储客户端地址
    socklen_t len = sizeof(peer);                                // 地址结构大小
    int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len); // 阻塞等待连接
    if (newfd < 0)
    {
        lg(Warning, "accept error, %s: %d", strerror(errno), errno);
        return -1; // 接受失败,返回无效 fd
    }
    char ipstr[64];
    // 将二进制 IPv4 地址安全转换为可读字符串
    inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
    *clientip = ipstr;                  // 输出客户端 IP 字符串
    *clientport = ntohs(peer.sin_port); // 网络字节序端口转主机字节序
    return newfd;                       // 返回新连接的文件描述符
    }

    // 主动连接到指定服务器
    bool Connect(const std::string &ip, const uint16_t &port)
    {                                   
        struct sockaddr_in peer;        // 服务器地址结构
        memset(&peer, 0, sizeof(peer)); // 初始化内存

        peer.sin_family = AF_INET;      // IPv4 协议
        peer.sin_port = htons(port);    // 端口转网络字节序

        // 将点分十进制 IP 字符串转换为二进制网络字节序
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer));
        if (n == -1)
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false; // 连接失败
        }
        return true; // 连接成功
    }

    // 显式关闭套接字(通常由析构函数自动完成)
    void Close()
    { 
        if (sockfd_ >= 0)
        {
            close(sockfd_); // 释放系统资源
            sockfd_ = -1;   // 标记为无效
        }
    }

     // 获取底层文件描述符(只读接口)
    int Fd() const
    {
        return sockfd_;
    }

private:
    int sockfd_; // TCP 套接字文件描述符,-1 表示无效
};

关于今天的内容就是讲解select中的socket如何写,希望大家能好好学习,学习这个也是复习我们之前讲的套接字知识!

相关推荐
来可电子CAN青年8 小时前
CAN总线远距离传输老断网?Fx灯不闪别慌,这几招让你的通信“稳如泰山”!
网络
独行soc8 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
云小逸8 小时前
【nmap源码解析】Nmap OS识别核心模块深度解析:osscan2.cc源码剖析(1)
开发语言·网络·学习·nmap
自不量力的A同学9 小时前
Solon AI v3.9 正式发布:全能 Skill 爆发
java·网络·人工智能
威迪斯特9 小时前
CentOS图形化操作界面:理论解析与实践指南
linux·运维·centos·组件·图形化·桌面·xserver
一方热衷.9 小时前
在线安装对应版本NVIDIA驱动
linux·运维·服务器
独自归家的兔9 小时前
ubuntu系统安装dbswitch教程 - 备份本地数据到远程服务器
linux·运维·ubuntu
ONE_SIX_MIX9 小时前
ubuntu 24.04 用rdp连接,桌面黑屏问题,解决
linux·运维·ubuntu
龙飞059 小时前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl