• 首页
  • 搜索
  • 工具
  • 分类
  • 日志
  • 友链
  • 图片

It's Geek KingYoungy

KEEP CHALLENGE
网络编程

如何利用xinetd深度优化服务性能:从原理到高阶实践

2025-03-29 浏览量 124 暂无评论


如何利用xinetd深度优化服务性能:从原理到高阶实践


一、问题背景:为什么需要按需启停服务?

1.1 常驻进程的资源瓶颈

在传统服务模型中,每个服务独立运行并持续监听端口(例如通过socket()+bind()+listen()系统调用)。这种方式在高频访问场景下表现良好,但对于低频服务(如每天仅几百次请求的API)存在显著问题:

• 内存浪费:一个简单的HTTP服务进程可能占用10MB内存。若有20个低频服务,常驻模式下需占用200MB内存,而实际利用率可能不足1%。
• CPU调度开销:内核需要为每个常驻进程维护调度队列项,进程数过多时上下文切换成本上升(可通过vmstat的cs字段观察)。
• 端口管理复杂度:每个服务需独立管理防火墙规则、端口分配等。

1.2 xinetd的核心价值

xinetd作为"超级守护进程",通过单一线程监听多个端口,仅在请求到达时启动对应服务进程。其优势体现在:

• 进程数线性降低:若有N个低频服务,xinetd仅需1个主进程+N个短暂子进程(按需创建)。
• 统一管理入口:所有服务的端口、访问控制、日志均可通过xinetd集中配置。
• 资源硬限制:可对每个服务设置CPU、内存、并发数等限制,避免单个服务耗尽资源。


二、xinetd架构解析:从请求到进程启动的完整流程

2.1 核心工作流程
  1. 端口监听:xinetd主进程通过select()/poll()系统调用监听所有配置的端口。
  2. 连接接入:当新连接到达时,xinetd根据端口号查找对应的服务配置。
  3. 预安全检查:根据only_from、no_access等规则过滤非法IP。
  4. 进程启动:若配置为wait = no(并发模式),立即fork()+exec()启动服务程序;若wait = yes(单线程模式),则在当前进程处理。
  5. 数据重定向:将socket连接的文件描述符重定向到服务进程的stdin和stdout,服务只需读写标准输入输出即可与客户端通信。
  6. 资源回收:服务进程退出后,xinetd回收子进程状态(通过waitpid())。
2.2 性能关键点:进程启动耗时

对于解释型语言(如Python),启动解释器可能需要50-100ms。这意味着:

# Python服务示例
$ time python3 -c 'print("hello")' 
hello
real    0m0.052s  # 52ms的冷启动开销

优化建议:
• 对延迟敏感的服务应使用编译型语言(C++、Rust、Go)。
• 使用prefork模式预启动进程池(需结合libprefork等库)。


三、实战配置:手把手构建xinetd代理服务

3.1 安装与基础配置
# Ubuntu安装
sudo apt update
sudo apt install xinetd -y

# 验证安装
systemctl status xinetd
3.2 开发被代理服务(C++实现)

服务功能:接收客户端字符串,返回处理后的字符串(模拟业务逻辑)。

// demo_service.cpp
#include <iostream>
#include <string>
#include <unistd.h>  // for getpid()

using namespace std;

int main() {
    // 读取请求(xinetd将socket数据重定向到stdin)
    string request;
    getline(cin, request);

    // 模拟业务处理(如数据库访问、计算等)
    sleep(1); // 添加1秒延迟模拟业务耗时

    // 构造响应
    string response = "[PID:" + to_string(getpid()) + "] Processed: " + request;

    // 输出响应(通过stdout发送回客户端)
    cout << response << endl;
    
    return 0;
}

编译与部署:

g++ demo_service.cpp -o /usr/local/bin/demo_service -O2 -static  # 静态编译避免库依赖
chmod +x /usr/local/bin/demo_service
3.3 xinetd服务配置详解

创建配置文件/etc/xinetd.d/demo_service:

# 定义服务名为demo_service(需与/etc/services中的名称对应)
service demo_service
{
        disable         = no         # 启用服务
        flags           = REUSE      # 允许端口复用
        socket_type     = stream     # TCP协议
        protocol        = tcp        # 明确协议类型
        port            = 9090       # 监听端口
        wait            = no         # 并发模式:为每个连接fork新进程
        user            = nobody     # 降权运行(安全加固)
        server          = /usr/local/bin/demo_service  # 服务程序路径
        server_args     = -l debug    # 传递给服务的参数(示例)
        instances       = 100        # 最大并发进程数
        per_source      = 10         # 单个IP最大连接数
        log_on_success  = HOST PID    # 记录成功连接的客户端IP和进程ID
        log_on_failure  = HOST        # 记录失败尝试的客户端IP
        log_type        = FILE /var/log/xinetd_demo.log  # 独立日志文件
        cps             = 50 10       # 限速:每秒50连接,超过则等待10秒
        rlimit_cpu      = 30          # 单个进程最多使用30秒CPU时间
        rlimit_as       = 100M        # 虚拟内存限制为100MB
        env             = LD_LIBRARY_PATH=/opt/mylibs  # 设置环境变量
}

重要参数解析:
• wait = no:每个连接独立进程,适用于短连接。
• instances:防止DDoS攻击,避免进程数耗尽。
• rlimit_*:基于Linux cgroups的资源限制,防止恶意请求导致系统崩溃。

3.4 端口注册(可选)

在/etc/services中添加服务名与端口映射:

echo "demo_service 9090/tcp  # My xinetd demo service" >> /etc/services
3.5 重载配置
# Systemd方式
sudo systemctl reload xinetd

# 传统SIGHUP方式
kill -HUP $(pgrep xinetd)

四、高级调试与监控技巧

4.1 实时日志观察
tail -f /var/log/xinetd_demo.log
# 示例日志输出
2024-03-01T14:23:18 START: demo_service pid=12345 from=192.168.1.100
2024-03-01T14:23:19 EXIT: demo_service pid=12345 status=0 duration=1s
4.2 连接过程追踪

使用strace观察xinetd处理流程:

sudo strace -p $(pgrep xinetd) -f -e trace=network,process,file
4.3 资源限制验证

触发CPU限制测试:

// 修改服务代码,添加无限循环
while(1) {}  // 模拟CPU占用

观察结果:
• 进程将在30秒后(由rlimit_cpu指定)被SIGKILL终止。
• 日志中出现status=15(SIGTERM)或status=9(SIGKILL)。


五、性能优化陷阱与解决方案

5.1 冷启动延迟问题

场景:服务程序启动耗时高(如JVM、Python解释器)。

解决方案:
• Prefork模式:预启动进程池。

server = /usr/sbin/tcpd
server_args = /path/to/prefork_wrapper /usr/local/bin/demo_service

• 使用轻量运行时:如Golang(无VM开销)、C++。

5.2 保持连接(Keep-Alive)处理

问题:客户端使用HTTP Keep-Alive时,xinetd无法感知连接复用。

解决方案:

flags = KEEPALIVE  # 启用TCP层keepalive
5.3 日志性能优化

避免日志IO成为瓶颈:

log_type = SYSLOG daemon  # 使用syslog异步写入
log_on_success =         # 禁用成功日志

六、安全加固指南

6.1 权限最小化

• 降权运行:user = nobody
• chroot隔离:

id = root
groups = yes
server_chroot = /var/empty  # 空目录作为根
6.2 访问控制
# 仅允许内网访问
only_from = 192.168.0.0/16 10.0.0.0/8

# 阻止恶意IP
no_access = 58.211.2.34 172.10.*
6.3 SELinux策略

创建自定义SELinux策略模块:

# 生成策略模块
audit2allow -a -M xinetd_demo < /var/log/audit/audit.log
semodule -i xinetd_demo.pp

七、扩展应用场景

7.1 替代Cron的按需任务

通过HTTP API触发批处理任务:

service batch-task
{
        port            = 8080
        server          = /usr/bin/curl
        server_args     = -s http://internal:9999/trigger-task
        only_from       = 127.0.0.1  # 仅限本机访问
}
7.2 UDP服务代理
service udp-demo
{
        socket_type     = dgram
        protocol        = udp
        wait            = yes        # UDP必须为yes
        server          = /usr/local/bin/udp_service
}

八、现代替代方案对比

8.1 systemd Socket Activation
# /etc/systemd/system/demo.socket
[Socket]
ListenStream=0.0.0.0:9090
Accept=yes

[Install]
WantedBy=sockets.target

# /etc/systemd/system/demo@.service
[Service]
ExecStart=/usr/local/bin/demo_service
StandardInput=socket
8.2 xinetd vs Kubernetes Sidecar
维度xinetdK8s Sidecar
资源隔离进程级容器级
扩展性单机集群
配置复杂度低(文本文件)高(YAML+CRD)
适用场景传统服务器云原生环境

九、性能压测与数据对比

9.1 测试工具(wrk)
# 启动100并发,持续30秒
wrk -t4 -c100 -d30s http://localhost:9090
9.2 结果对比(模拟业务)
指标常驻进程模式xinetd模式
内存占用50MB(常驻)0MB(无请求时)
最大QPS1200800
99%延迟1.1s1.3s
进程数峰值100100

结论:xinetd在低频场景(QPS<100)下资源节约效果显著,高频场景建议仍使用常驻进程。


通过深度解析xinetd的机制与实战技巧,开发者可根据业务特点灵活选择服务模型。在物联网、企业内部系统等低频访问场景中,xinetd仍是最轻量高效的解决方案之一。

- 阅读全文 -
系统编程基础

Linux Daemon进程开发指南:从创建到日志管理

2025-03-28 浏览量 144 暂无评论

Linux Daemon进程开发指南:从创建到日志管理

一、Daemon进程创建流程

1. 核心步骤解析

Linux守护进程需遵循UNIX规范脱离终端控制,以下是标准创建流程:

1.1 fork() - 进程复制

功能:创建子进程,父进程退出实现后台化

#include <unistd.h>
pid_t fork(void);

参数:无
返回值:
• 成功:父进程返回子进程PID,子进程返回0
• 失败:返回-1,设置errno

1.2 setsid() - 会话控制

功能:创建新会话并脱离终端

#include <unistd.h>
pid_t setsid(void);

返回值:
• 成功:返回新会话ID
• 失败:返回-1,设置errno

1.3 二次fork() - 非会话组长

必要性:防止重新获取控制终端(部分系统要求)

1.4 文件操作

umask(0); // 重置文件权限掩码
chdir("/"); // 切换工作目录到根节点
close(STDIN_FILENO); // 关闭标准IO
dup2(open("/dev/null", O_RDWR), STDIN_FILENO); // 重定向到空设备

二、SIGHUP信号处理机制

1. 信号处理器设置

1.1 sigaction() - 高级信号处理

功能:注册信号处理函数

#include <signal.h>
int sigaction(int signum, 
             const struct sigaction *act,
             struct sigaction *oldact);

参数解析:
• signum:信号编号(如SIGHUP=1)
• act:新动作描述结构体

struct sigaction {
  void     (*sa_handler)(int); // 处理函数指针
  sigset_t sa_mask;    // 执行期间阻塞的信号集
  int      sa_flags;   // 标志位:SA_RESTART|SA_NOCLDSTOP等
  void     (*sa_sigaction)(int, siginfo_t *, void *); // 替代handler
};

• oldact:原动作存储指针

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

2. SIGHUP典型应用场景

• 配置文件热加载(如nginx -s reload)
• 日志文件轮转(配合logrotate)
• 服务状态刷新


三、Syslog日志系统API详解

syslog本身是一个daemon进程,本地的进程可以通过syslog()系统调用与其建立Unix Domain Socket进行通信,远程的可以使用Internet Domain Socket与其通信。它会将收到的内容根据设施(facility)与重要性等级(level)组合成的优先级(priotity)来选择相应的行为来处理日志。

1. 核心函数组

1.1 openlog() - 初始化日志连接

#include <syslog.h>
void openlog(const char *ident, int option, int facility);

参数说明:
• ident:程序标识字符串(常为程序名)
• option:控制标志组合(位或):

LOG_CONS    // 无法连接时写入控制台
LOG_NDELAY  // 立即打开连接
LOG_PID     // 记录进程ID
LOG_PERROR  // 同时输出到stderr

• facility:日志分类标识:

LOG_AUTH     // 安全/认证消息
LOG_DAEMON   // 系统守护进程
LOG_LOCAL0~7 // 自定义分类

1.2 syslog() - 日志消息生成

#include <syslog.h>
void syslog(int priority, const char *format, ...);

参数解析:
• priority:级别宏与facility的组合
级别常量:

LOG_EMERG   // 系统不可用
LOG_ALERT   // 立即处理
LOG_CRIT    // 严重错误
LOG_ERR     // 一般错误
LOG_WARNING // 警告
LOG_NOTICE  // 正常但重要
LOG_INFO    // 常规信息
LOG_DEBUG   // 调试信息

• format:类似printf的格式化字符串
• ...:可变参数列表

1.3 closelog() - 关闭日志连接

#include <syslog.h>
void closelog(void);

四、Daemon开发实践建议

  1. 资源管理:需显式关闭非必要文件描述符,防止句柄泄漏
  2. 错误处理:所有系统调用需检查返回值,结合errno输出诊断信息
  3. 信号屏蔽:正确处理SIGTERM等终止信号,实现优雅退出
  4. 日志轮转:通过SIGHUP实现日志文件重打开(示例):
void log_rotate(int sig) {
    fclose(logfile);
    logfile = fopen(path, "a+"); // 重新打开日志文件
    syslog(LOG_NOTICE, "Log rotated by signal %d", sig);
}
  1. 权限控制:建议以非root用户运行,通过setuid()降权

五、Syslog高级配置

1. 远程日志配置(/etc/rsyslog.conf)

# 启用UDP监听
module(load="imudp")
input(type="imudp" port="514")

# 启用TCP+TLS
module(load="imtcp")
input(type="imtcp" port="6514" StreamDriver.Name="gtls")

2. 日志过滤规则

# 仅记录ssh登录失败
if $programname == 'sshd' and $msg contains 'Failed' then /var/log/ssh_fail.log

通过合理运用daemon创建规范、信号处理机制和syslog系统,开发者可以构建出稳定可靠的后台服务。建议结合systemd等现代初始化系统进行服务管理,并定期审计日志安全配置。

- 阅读全文 -
网络编程

DNS(IP)与服务名(端口)解析

2025-03-28 浏览量 123 暂无评论

DNS(IP)与服务名(端口)解析

1. getaddrinfo()

功能

将主机名和服务名转换为套接字地址结构列表,完成协议无关的地址解析。支持IPv4/IPv6双栈处理,自动过滤不支持的协议。

函数声明

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *host, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

参数解析

  1. host:主机名或IP地址字符串

    • 若为域名(如 "ex­am­ple.com"):触发 DNS 查询。域名字符串长度不小于netdb.h中定义的NI_MAXHOST(需启用_BSD_SOURCE)。
    • 若为IPv4/​IPv6 地址(如 "192.168.1.1" 或 "::1"):直接解析为地址结构。IP地址字符串长度不小于<netinet/in.h>中定义的INET_ADDRSTRLEN或INET6_ADDRSTRLEN。
    • 若为NULL:若在 hint 中指定 AI_­PAS­SIVE,表明服务端程序需要绑定到本地所有可用网络接口如(0.0.0.0 或 ::);若未指定,仅将服务名(如 "http")转换为端口号(如 80),不涉及主机名解析。
  2. service:服务名或端口号字符串

    • 可接受服务名(如"http")或十进制端口字符串(如"80")。服务字符串不小于netdb.h中定义的NI_MAXSERV(需启用_BSD_SOURCE)。十进制端口字符串无长度要求。
    • NULL表示不指定端口
  3. hints:过滤结果的模板结构体

    struct addrinfo {
        int ai_flags;           // 控制选项
        int ai_family;          // 地址族
        int ai_socktype;        // 套接字类型
        int ai_protocol;        // 协议类型
        socklen_t ai_addrlen;   // 地址结构长度
        struct sockaddr *ai_addr; // 套接字地址
        char *ai_canonname;     // 规范主机名
        struct addrinfo *ai_next; // 链表指针
    };
    • ai_flags 组合值:

      AI_PASSIVE     
      /* 不设置,就代表你是客户端,期望获取指定域名的服务器的套接字地址进行套接字连接。
            若host为空而service非空,则表明客户端希望getaddrinfo返回给其本地回环ip以通过bind绑定;
            若host非空而service为空,则表明客户端希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 
            闲临时端口以供发送
         若设置,就代表你是服务器,期望获取能够被客户端访问到的套接字地址,
            若host为空而service非空,则表明服务器希望getaddrinfo返回给其通配ip以监听所有网卡的service请求;
            若host非空而service为空,则表明服务器希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 
            闲临时端口以供接收*/
      AI_CANONNAME   // 返回规范主机名
      AI_NUMERICHOST // 禁止域名解析成IP地址,以减少函数不必要的检查(即我们必须传入一个IP地址字符串而非域名或NULL)
      AI_NUMERICSERV // 禁止服务解析成端口,以减少函数不必要的检查(即我们必须传入一个端口号而非服务名)
      AI_V4MAPPED    // IPv6返回IPv4映射地址
      AI_ALL         // 同时返回IPv4和IPv6映射

注意:某些系统(如 Linux)可能不允许 AI_CANONNAME 和 AI_PASSIVE 同时使用,因为 AI_PASSIVE 通常用于服务器监听,而 AI_CANONNAME 主要用于客户端查询主机名。

    • ai_family 取值:

      AF_UNSPEC  // 允许任意地址族
      AF_INET    // IPv4
      AF_INET6   // IPv6
    • ai_socktype 取值:

      SOCK_STREAM  // 流式套接字
      SOCK_DGRAM   // 数据报套接字
      0            // 任意类型
    1. res:返回的地址链表头指针

    返回值

    • 0:成功
    • 非零错误码:失败,需用gai_strerror()转换

      EAI_BADFLAGS   // 无效的ai_flags
      EAI_NONAME     // 无法解析名称
      EAI_AGAIN      // 临时故障
      EAI_FAIL       // 不可恢复错误
      EAI_MEMORY     // 内存分配失败
      EAI_SYSTEM     // 系统错误(检查errno)

    2. freeaddrinfo()

    功能

    释放getaddrinfo()动态分配的地址链表,避免内存泄漏。

    函数声明

    #include <netdb.h>
    
    void freeaddrinfo(struct addrinfo *res);

    参数解析

    • res:getaddrinfo返回的地址链表头指针

    返回值

    无返回值


    3. gai_strerror()

    功能

    将getaddrinfo()/getnameinfo()的错误码转换为可读字符串。

    函数声明

    #include <netdb.h>
    
    const char *gai_strerror(int errcode);

    参数解析

    • errcode:getaddrinfo()或getnameinfo()返回的错误码

    返回值

    • 对应错误描述字符串(静态存储区,无需释放)

    4. getnameinfo()

    功能

    将套接字地址转换为对应的主机名和服务名,实现逆向解析。

    函数声明

    #include <sys/socket.h>
    #include <netdb.h>
    
    int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                    char *host, socklen_t hostlen,
                    char *serv, socklen_t servlen,
                    int flags);

    参数解析

    1. sa:输入套接字地址结构指针

      • 支持struct sockaddr_in/sockaddr_in6等类型
    2. salen:地址结构实际长度

      • sizeof(struct sockaddr_in) 等
    3. host:输出主机名缓冲区

      • 长度应至少为NI_MAXHOST(1025)
    4. hostlen:主机名缓冲区长度

      • 使用宏定义:NI_MAXHOST
    5. serv:输出服务名缓冲区

      • 长度应至少为NI_MAXSERV(32)
    6. servlen:服务名缓冲区长度

      • 使用宏定义:NI_MAXSERV
    7. flags:控制标志组合

      NI_NOFQDN        // 仅返回主机名部分
      NI_NUMERICHOST   // 返回数字地址
      NI_NAMEREQD      // 无法解析时报错
      NI_NUMERICSERV   // 返回端口号而非服务名
      NI_DGRAM         // 指定数据报服务(用于端口映射)

    返回值

    • 0:成功
    • 非零错误码:同getaddrinfo()

    宏定义总结

    // 名称大小
    #define NI_MAXHOST 1025 //域名
    #define NI_MAXSERV 32 //服务名
    
    // IP地址presentation字符串最大长度
    #define INET_ADDRSTRLEN 16//IPv4
    #define INET6_ADDRSTRLEN 46 //IPv6
    
    // 地址族
    #define AF_UNSPEC 0 
    #define AF_INET   2
    #define AF_INET6  10
    
    // 套接字类型
    #define SOCK_STREAM 1
    #define SOCK_DGRAM  2

    以上函数配合使用可实现健壮的跨平台网络编程,正确处理了内存管理和错误处理,是构建现代网络应用的基础设施。

    - 阅读全文 -
    网络编程

    Internet Domain Socket 编程:字节流与数据报套接字详解

    2025-03-27 浏览量 148 暂无评论

    Internet Domain Socket 编程完全指南

    :&(蛆音娘_不愧是我)

    1. 套接字基础与核心函数

    1.1 socket() - 创建套接字

    创建一个通信端点并返回一个文件描述符。

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

    参数说明:

    • domain: 指定通信域(网络层)

      • AF_INET: IPv4 Internet 协议
      • AF_INET6: IPv6 Internet 协议
      • AF_UNIX: 本地通信
    • type: 指定通信语义(传输层)

      • SOCK_STREAM: 提供有序、可靠、双向、基于连接的字节流(TCP)
      • SOCK_DGRAM: 支持数据报(无连接、不可靠、固定最大长度)(UDP)
      • SOCK_RAW: 提供原始网络协议访问
      • SOCK_SEQPACKET: 提供有序、可靠、双向、基于连接的数据报传输路径
    • protocol: 通常为0,表示自动选择与type对应的默认协议

    返回值:

    • 成功: 返回新套接字的文件描述符
    • 失败: 返回-1,并设置errno

    1.2 bind() - 绑定地址到套接字

    将地址(主机名+端口号)分配给套接字。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    参数说明:

    • sockfd: socket()返回的文件描述符
    • addr: 指向要绑定的地址结构体的指针
    • addrlen: 地址结构体的长度

    地址结构体(一般不直接用struct sockaddr,而是使用后面带in或in6的结构体):

    • IPv4 (struct sockaddr_in):

      struct sockaddr_in {
          sa_family_t    sin_family;  // AF_INET
          in_port_t      sin_port;    // 端口号(网络字节序)
          struct in_addr sin_addr;    // IPv4地址
          char           sin_zero[8]; // 填充
      };
      
      struct in_addr {
          uint32_t s_addr;  // IPv4地址(网络字节序)
      };
    • IPv6 (struct sockaddr_in6):

      struct sockaddr_in6 {
          sa_family_t     sin6_family;   // AF_INET6
          in_port_t       sin6_port;     // 端口号(网络字节序)
          uint32_t       sin6_flowinfo; // IPv6流信息
          struct in6_addr sin6_addr;     // IPv6地址
          uint32_t       sin6_scope_id; // 作用域ID
      };
      
      struct in6_addr {
          unsigned char s6_addr[16];  // IPv6地址
      };

    地址结构体在使用前一定要做的事:
    调用memset将其内存清零。即

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr)); // 清0

    原因:防止结构体字段的内存忘记添加终止符\0,也防止一些系统需要将因内存对齐而填充的字节设置为0的要求
    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    1.3 listen() - 监听连接

    将套接字标记为被动套接字(监听套接字),用于接受连接请求并诞生连接套接字。
    一个监听套接字可以调用accept()诞生无穷个连接套接字,除非超过系统资源限制。
    但内核中为该监听套接字保留的已连接但未被监听套接字调用accept的未决连接数量最大值为backlog。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int listen(int sockfd, int backlog);

    参数说明:

    • sockfd: socket()返回的文件描述符
    • backlog: 挂起连接队列的最大长度

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    1.4 accept() - 接受连接

    从监听套接字的挂起连接队列中接受一个连接。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    参数说明:

    • sockfd: 监听套接字的文件描述符
    • addr: 用于存储对端地址的缓冲区(可为NULL)
    • addrlen: 输入时为addr缓冲区大小,输出时为实际地址长度(可为NULL)

    返回值:

    • 成功: 返回新套接字的文件描述符
    • 失败: 返回-1,并设置errno

    1.5 connect() - 发起连接

    发起与监听套接字的连接。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    参数说明:

    • sockfd: socket()返回的文件描述符
    • addr: 目标地址结构体指针
    • addrlen: 地址结构体的长度

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    1.6 close() - 关闭套接字

    关闭文件描述符。注意在对底层IO的深度总结中介绍,他只是断开该文件描述与内核的打开文件句柄之间的指向关系并删除该文件描述符。当内核的套接字文件打开句柄没有任何进程的文件描述符指向它时,内核向对端发送FIN。shutdown系统调用的关闭写端功能直接通知内核发送FIN。(FIN用于通知对方自己不会再发送有实质内容的TCP报文且等待对方先回传ACK再回传FIN)

    #include <unistd.h>
    
    int close(int fd);

    参数说明:

    • fd: 要关闭的文件描述符

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    1.7 shutdown() - 关闭套接字的部分功能

    关闭全双工连接的一部分。与close不同之处在于其功能是若关闭了写端(或读写两端一起关闭)立即触发内核发送FIN,并不对文件描述符、文件句柄做任何操作。注意关闭读端只是内核不让read,但是内核并不发送FIN。

    #include <sys/socket.h>
    
    int shutdown(int sockfd, int how);

    参数说明:

    • sockfd: 套接字文件描述符
    • how: 关闭方式

      • SHUT_RD: 关闭读端
      • SHUT_WR: 关闭写端
      • SHUT_RDWR: 关闭读写两端

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    2. 数据传输函数

    2.1 send() - 发送数据

    通过已连接的套接字发送数据。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    参数说明:

    • sockfd: 已连接的套接字文件描述符
    • buf: 要发送的数据缓冲区
    • len: 要发送的数据长度
    • flags: 标志位(可位或组合)

      • MSG_CONFIRM: 确认链路有效
      • MSG_DONTROUTE: 不通过网关发送
      • MSG_DONTWAIT: 非阻塞操作
      • MSG_EOR: 结束记录
      • MSG_MORE: 与下次调用封装在一个TCP报文中
      • MSG_NOSIGNAL: 不产生SIGPIPE信号
      • MSG_OOB: 发送带外数据

    返回值:

    • 成功: 返回发送的字节数(正文)
    • 失败: 返回-1,并设置errno

    2.2 recv() - 接收数据

    从已连接的套接字接收数据。特别注意,网络中的数据(包括管道、FIFO等任何IPC)不单单为C语言服务。即不会遵循字符串最后以0字符结尾。传给printf打印要么自己手动加上'\0' ,要么使用格式化输出方式"%.*s"来动态指定字符串长度。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    参数说明:

    • sockfd: 已连接的套接字文件描述符
    • buf: 接收数据的缓冲区
    • len: 缓冲区长度
    • flags: 标志位(可位或组合)

      • MSG_CMSG_CLOEXEC: 设置接收的文件描述符为close-on-exec
      • MSG_DONTWAIT: 非阻塞操作
      • MSG_ERRQUEUE: 接收错误信息
      • MSG_OOB: 接收带外数据
      • MSG_PEEK: 查看数据但不从缓冲区移除
      • MSG_TRUNC: 即使数据包被截断也返回实际长度
      • MSG_WAITALL: 等待所有请求数据到达

    返回值:

    • 成功: 返回接收的字节数(正文)(0表示对端关闭连接)
    • 失败: 返回-1,并设置errno

    2.3 sendto() - 发送数据报

    通过无连接套接字发送数据报。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);

    参数说明:

    • sockfd: 套接字文件描述符
    • buf: 要发送的数据缓冲区
    • len: 要发送的数据长度
    • flags: 同send()
    • dest_addr: 目标地址结构体指针(可为NULL)
    • addrlen: 目标地址结构体长度(可为0)

    返回值:

    • 成功: 返回发送的字节数(正文)
    • 失败: 返回-1,并设置errno

    2.4 recvfrom() - 接收数据报

    从无连接套接字接收数据报。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                     struct sockaddr *src_addr, socklen_t *addrlen);

    参数说明:

    • sockfd: 套接字文件描述符
    • buf: 接收数据的缓冲区
    • len: 缓冲区长度
    • flags: 同recv()
    • src_addr: 用于存储源地址的缓冲区(可为NULL)
    • addrlen: 输入时为src_addr缓冲区大小,输出时为实际地址长度(可为NULL)

    返回值:

    • 成功: 返回接收的字节数(正文)(0表示对端关闭连接)
    • 失败: 返回-1,并设置errno

    2.5 sendmsg() - 发送消息

    通过套接字发送消息(支持分散/聚集I/O和控制消息)。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

    参数说明:

    • sockfd: 套接字文件描述符
    • msg: 消息结构体指针
    • flags: 同send()

    msghdr结构体:

    struct msghdr {
        void         *msg_name;       // 可选地址
        socklen_t     msg_namelen;    // 地址长度
        struct iovec *msg_iov;        // 分散/聚集数组
        int           msg_iovlen;     // iov元素个数
        void         *msg_control;    // 辅助数据
        socklen_t     msg_controllen; // 辅助数据长度
        int           msg_flags;      // 接收消息的标志
    };
    
    struct iovec {
        void  *iov_base;  // 缓冲区起始地址
        size_t iov_len;   // 缓冲区长度
    };

    返回值:

    • 成功: 返回发送的字节数
    • 失败: 返回-1,并设置errno

    2.6 recvmsg() - 接收消息

    通过套接字接收消息(支持分散/聚集I/O和控制消息)。

    #include <sys/types.h>
    #include <sys/socket.h>
    
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

    参数说明:

    • sockfd: 套接字文件描述符
    • msg: 消息结构体指针
    • flags: 同recv()

    返回值:

    • 成功: 返回接收的字节数(0表示对端关闭连接)
    • 失败: 返回-1,并设置errno

    3. IP地址转换函数

    3.1 inet_addr() - 将IPv4点分十进制字符串转换为网络字节序整数

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    in_addr_t inet_addr(const char *cp);

    参数说明:

    • cp: IPv4点分十进制地址字符串(如"192.168.1.1")

    返回值:

    • 成功: 返回网络字节序的32位IPv4地址
    • 失败: 返回INADDR_NONE(通常为-1)

    3.2 inet_aton() - 将IPv4点分十进制字符串转换为网络字节序结构体

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int inet_aton(const char *cp, struct in_addr *inp);

    参数说明:

    • cp: IPv4点分十进制地址字符串
    • inp: 存储转换结果的in_addr结构体指针

    返回值:

    • 成功: 返回非零
    • 失败: 返回0

    3.3 inet_ntoa() - 将网络字节序IPv4地址转换为点分十进制字符串

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    char *inet_ntoa(struct in_addr in);

    参数说明:

    • in: 网络字节序的in_addr结构体

    返回值:

    • 返回指向静态缓冲区的字符串指针(非线程安全)

    3.4 inet_pton() - 将可打印地址字符串转换为网络格式(支持IPv4/IPv6)

    #include <arpa/inet.h>
    
    int inet_pton(int af, const char *src, void *dst);

    参数说明:

    • af: 地址族

      • AF_INET: IPv4
      • AF_INET6: IPv6
    • src: 源地址字符串

      • INET_ADDRSTRLEN: IPv4presentation字符串最大长度
      • INET6_ADDRSTRLEN: IPv6presentation字符串最大长度
    • dst: 存储转换结果的缓冲区(in_addr或in6_addr结构体的地址)

    返回值:

    • 成功: 返回1
    • 无效输入: 返回0
    • 失败: 返回-1

    3.5 inet_ntop() - 将网络格式地址转换为可打印字符串(支持IPv4/IPv6)

    #include <arpa/inet.h>
    
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

    参数说明:

    • af: 地址族(AF_INET或AF_INET6)
    • src: 网络格式的地址指针(in_addr或in6_addr结构体的地址)
    • dst: 目标缓冲区(长度至少是上述两个宏定义)
    • size: 目标缓冲区大小

    缓冲区大小建议:

    • IPv4: 至少INET_ADDRSTRLEN(16)字节
    • IPv6: 至少INET6_ADDRSTRLEN(46)字节

    返回值:

    • 成功: 返回dst
    • 失败: 返回NULL

    4. 套接字选项控制

    4.1 getsockopt() - 获取套接字选项

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

    参数说明:

    • sockfd: 套接字文件描述符
    • level: 选项级别

      • SOL_SOCKET: 通用套接字选项
      • IPPROTO_IP: IPv4选项
      • IPPROTO_IPV6: IPv6选项
      • IPPROTO_TCP: TCP选项
    • optname: 选项名称

      • SOL_SOCKET级别:

        • SO_ACCEPTCONN: 套接字是否在监听
        • SO_BROADCAST: 是否允许发送广播
        • SO_DEBUG: 是否启用调试
        • SO_DONTROUTE: 是否绕过标准路由
        • SO_ERROR: 获取并清除错误状态
        • SO_KEEPALIVE: 是否启用keepalive
        • SO_LINGER: 延迟关闭时间
        • SO_OOBINLINE: 是否将带外数据放入普通数据流
        • SO_RCVBUF: 接收缓冲区大小
        • SO_REUSEADDR: 是否允许重用本地地址,分为两个层面。第一是使得连接套接字关闭后监听套接字可以不等待2MSL的时间调用accept而不发生EADDRINUSE错误;第二是使得多个监听套接字可以绑定同一个端口。
        • SO_SNDBUF: 发送缓冲区大小
        • SO_TYPE: 获取套接字类型
      • IPPROTO_IP级别:

        • IP_TOS: 服务类型字段
        • IP_TTL: 生存时间字段
      • IPPROTO_IPV6级别:

        • IPV6_V6ONLY: 是否仅限IPv6
      • IPPROTO_TCP级别:

        • TCP_NODELAY: 禁用Nagle算法
    • optval: 存储选项值的缓冲区
    • optlen: 输入时为optval缓冲区大小,输出时为实际选项长度

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    4.2 setsockopt() - 设置套接字选项

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

    参数说明:

    • 同getsockopt()

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    5. 主机名与地址解析

    5.1 gethostbyname() - 通过主机名获取主机信息(IPv4)

    #include <netdb.h>
    
    struct hostent *gethostbyname(const char *name);

    hostent结构体:

    struct hostent {
        char  *h_name;       // 主机正式名
        char **h_aliases;    // 别名列表
        int    h_addrtype;   // 地址类型(AF_INET)
        int    h_length;     // 地址长度
        char **h_addr_list;  // 地址列表(网络字节序)
    };
    #define h_addr h_addr_list[0]  // 向后兼容

    返回值:

    • 成功: 返回hostent结构体指针
    • 失败: 返回NULL,并设置h_errno

    5.2 gethostbyaddr() - 通过地址获取主机信息(IPv4)

    #include <netdb.h>
    
    struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

    参数说明:

    • addr: 指向网络字节序地址的指针
    • len: 地址长度(IPv4为4)
    • type: 地址类型(AF_INET)

    返回值:

    • 同gethostbyname()

    5.3 getaddrinfo() - 地址无关的主机名解析(支持IPv4/IPv6)

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    
    int getaddrinfo(const char *host, const char *service,
                    const struct addrinfo *hints,
                    struct addrinfo **res);

    详情见于解析DNS与服务名地址,IP地址与端口号

    5.4 freeaddrinfo() - 释放getaddrinfo()分配的内存

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    
    void freeaddrinfo(struct addrinfo *res);

    详情见于解析DNS与服务名地址,IP地址与端口号

    5.5 gai_strerror() - 获取getaddrinfo()错误描述

    #include <netdb.h>
    
    const char *gai_strerror(int errcode);

    详情见于解析DNS与服务名地址,IP地址与端口号

    5.6 getnameinfo() - 地址无关的地址到名称转换(支持IPv4/IPv6)

    #include <sys/socket.h>
    #include <netdb.h>
    
    int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
                    char *host, socklen_t hostlen,
                    char *serv, socklen_t servlen, int flags);

    详情见于解析DNS与服务名地址,IP地址与端口号

    6. (端口、正文中的整数)字节序转换函数

    字节序转换函数用于诸如信息里的整数、端口号等多字节信息单元的传递。当每个信息仅占一个字节时,如字符串,内存低位一定是字符串的前几个字符。而对于整数,大端存储——先存高位,即内存低地址存储整数高位;小端存储——先存低位,即内存低地址存储整数低位。网络字节序为大端,而本地主机由于硬件架构的不同,有的是大端而有的是小端。

    6.1 htons() - 主机字节序转网络字节序(16位)

    #include <arpa/inet.h>
    
    uint16_t htons(uint16_t hostshort);

    参数说明:

    • hostshort: 主机字节序的16位整数

    返回值:

    • 返回网络字节序的16位整数

    6.2 htonl() - 主机字节序转网络字节序(32位)

    #include <arpa/inet.h>
    
    uint32_t htonl(uint32_t hostlong);

    参数说明:

    • hostlong: 主机字节序的32位整数

    返回值:

    • 返回网络字节序的32位整数

    6.3 ntohs() - 网络字节序转主机字节序(16位)

    #include <arpa/inet.h>
    
    uint16_t ntohs(uint16_t netshort);

    参数说明:

    • netshort: 网络字节序的16位整数

    返回值:

    • 返回主机字节序的16位整数

    6.4 ntohl() - 网络字节序转主机字节序(32位)

    #include <arpa/inet.h>
    
    uint32_t ntohl(uint32_t netlong);

    参数说明:

    • netlong: 网络字节序的32位整数

    返回值:

    • 返回主机字节序的32位整数

    7. 其他辅助函数

    7.1 getsockname() - 获取套接字本地地址

    #include <sys/socket.h>
    
    int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    参数说明:

    • sockfd: 套接字文件描述符
    • addr: 存储本地地址的缓冲区
    • addrlen: 输入时为addr缓冲区大小,输出时为实际地址长度

    返回值:

    • 成功: 返回0
    • 失败: 返回-1,并设置errno

    7.2 getpeername() - 获取对端地址

    #include <sys/socket.h>
    
    int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    参数说明:

    • 同getsockname()

    返回值:

    • 同getsockname()

    7.3 sockatmark() - 检查是否处于带外标记

    #include <sys/socket.h>
    
    int sockatmark(int sockfd);

    参数说明:

    • sockfd: 套接字文件描述符

    返回值:

    • 处于带外标记: 返回1
    • 不处于带外标记: 返回0
    • 失败: 返回-1,并设置errno

    - 阅读全文 -
    1. 1
    2. ...
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. ...
    9. 10
    • 站点概览
    author

    38 日志
    7 分类
    Creative Commons
    • 热门文章
    • 热评文章
    • 随机文章
    • 在 Debian 服务器上部署 FileBrowser 并集成到现有博客路径
    • 高等数学重要定义整理
    • 高等数学重要定理总结
    • C语言原子量的使用
    • 库、链接与执行
    • 欢迎使用 Typecho
    • 对底层IO的深度总结
    • 数据结构——树
    • 库、链接与执行
    • shell作业控制的两个问题:组长叛变与SIGHUP信号
    • 文件锁技术详解:flock与fcntl系统调用
    • 深入理解pthread互斥量与条件变量的使用
    • 使用GDB调试程序的完整指南
    • C++ STL中的栈与队列:核心原理与实战应用指南
    • 在 Debian 服务器上部署 FileBrowser 并集成到现有博客路径

    浏览量 : 5435

    © 2025 It's Geek KingYoungy. Power By Typecho . Theme by Shiyi

    浙ICP备2025160639号  |  浙公网安备33020502001222号

    This is just a placeholder img.