当前位置: 首页 > news >正文

Socket函数详解:完整指南

Socket函数详解:完整指南

1. 函数概述

socket()函数是网络编程中创建通信端点的基础API,它在不同的操作系统上都有实现,是跨平台网络编程的核心。

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

成功时返回非负整数(socket文件描述符),失败时返回-1并设置errno。

2. 参数详解

2.1 domain(协议族/地址族)

决定了socket如何解释地址以及它可以通信的网络范围。

值(宏)描述典型用途地址结构
AF_UNIX/AF_LOCAL本地通信,基于文件路径同一主机上的进程间通信(IPC)struct sockaddr_un
AF_INETIPv4网络协议基于IPv4的网络通信struct sockaddr_in
AF_INET6IPv6网络协议基于IPv6的网络通信struct sockaddr_in6
AF_PACKET底层数据包接口,直接访问链路层网络嗅探、自定义协议开发struct sockaddr_ll
AF_NETLINK内核与用户空间通信路由表配置、防火墙规则管理struct sockaddr_nl
AF_BLUETOOTH蓝牙通信蓝牙设备间数据传输struct sockaddr_bt

注意:在大多数实现中,AF_*(Address Family)和PF_*(Protocol Family)宏的值相同。历史上曾打算让它们具有不同含义,但这种区分在实践中并未实现。

2.2 type(Socket类型)

定义socket的通信特性,如数据传输方式、可靠性和消息边界处理。

值(宏)描述特性典型应用场景
SOCK_STREAM面向连接的可靠字节流可靠性、有序性、流式传输HTTP、FTP、SSH、数据库连接
SOCK_DGRAM无连接的消息传递(数据报)快速、低开销、可能丢失DNS查询、实时音视频、游戏
SOCK_RAW原始socket,可直接访问底层协议自定义协议头、更多控制权网络监控、协议开发、安全工具
SOCK_SEQPACKET有序、可靠、保留消息边界的数据包结合STREAM和DGRAM的优点SCTP协议、特定IPC场景

可与以下标志组合使用(按位或|操作)

  • SOCK_NONBLOCK:创建非阻塞模式的socket
  • SOCK_CLOEXEC:设置close-on-exec标志,防止socket描述符在执行exec()时被继承

2.3 protocol(具体协议)

通常设为 0,表示根据 domaintype 自动选择默认协议。也可显式指定:

协议值描述适用domain和type组合
IPPROTO_TCP传输控制协议AF_INET/AF_INET6 + SOCK_STREAM
IPPROTO_UDP用户数据报协议AF_INET/AF_INET6 + SOCK_DGRAM
IPPROTO_SCTP流控制传输协议AF_INET/AF_INET6 + SOCK_SEQPACKET
IPPROTO_ICMPInternet控制消息协议AF_INET + SOCK_RAW
IPPROTO_RAW原始IP包AF_INET/AF_INET6 + SOCK_RAW

3. 常见用例示例

3.1 创建TCP Socket (IPv4)

int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_sock == -1) {
    perror("socket creation failed");
    exit(EXIT_FAILURE);
}

3.2 创建UDP Socket (IPv6)

int udp_sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (udp_sock == -1) {
    perror("socket creation failed");
    exit(EXIT_FAILURE);
}

3.3 创建Unix Domain Socket(UDS)

int uds_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (uds_sock == -1) {
    perror("socket creation failed");
    exit(EXIT_FAILURE);
}

3.4 创建非阻塞TCP Socket

// 方法1: 使用socket()的标志
int nonblock_sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);

// 方法2: 使用fcntl()修改已创建的socket
#include <fcntl.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);

3.5 创建原始Socket (需要特权)

// 抓取ICMP包
int raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (raw_sock == -1) {
    perror("raw socket creation failed (may require root privileges)");
    exit(EXIT_FAILURE);
}

4. 网络Socket与Unix Domain Socket对比

4.1 通信机制对比

特性Unix Domain Socket (UDS)网络Socket (TCP/IP)
地址格式文件路径 (例如:“/tmp/my.sock”)IP地址+端口号 (例如:“127.0.0.1:8080”)
通信范围仅限同一主机上的进程可跨网络与远程主机通信
性能更高效(无网络协议栈开销)需经过完整网络协议栈
数据传输直接内核内存拷贝经过网络缓冲区和协议处理
安全性基于文件系统权限控制需要额外的网络安全措施
支持的功能可传递文件描述符无法直接传递文件描述符

4.2 性能差异

UDS比本地环回TCP套接字(127.0.0.1)具有显著优势:

  • 吞吐量通常高50%-100%
  • 延迟更低(无TCP/IP协议栈开销)
  • CPU使用率更低

5. 完整示例:Unix Domain Socket

5.1 服务器端

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    // 创建socket
    int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1)
    {
        perror("socket");
        return EXIT_FAILURE;
    }

    // 准备地址结构
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/tmp/uds_socket", sizeof(addr.sun_path) - 1);

    // 删除可能存在的老socket文件
    unlink(addr.sun_path);

    // 绑定socket到地址
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("bind");
        close(server_fd);
        return EXIT_FAILURE;
    }

    // 监听连接请求
    if (listen(server_fd, 5) == -1)
    {
        perror("listen");
        close(server_fd);
        return EXIT_FAILURE;
    }

    printf("UDS Server waiting for connections on %s\n", addr.sun_path);

    // 接受连接
    int client_fd = accept(server_fd, NULL, NULL);
    if (client_fd == -1)
    {
        perror("accept");
        close(server_fd);
        return EXIT_FAILURE;
    }

    // 通信示例
    char buf[100] = {0};
    ssize_t bytes_read = read(client_fd, buf, sizeof(buf) - 1);
    if (bytes_read > 0)
    {
        printf("UDS Server received: %s\n", buf);
        write(client_fd, "Hello from UDS Server!", 22);
    }

    // 清理资源
    close(client_fd);
    close(server_fd);
    unlink(addr.sun_path);
    return EXIT_SUCCESS;
}

5.2 客户端

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 创建socket
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        return EXIT_FAILURE;
    }
    
    // 准备服务器地址
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/tmp/uds_socket", sizeof(addr.sun_path) - 1);
    
    // 连接到服务器
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        close(sock);
        return EXIT_FAILURE;
    }
    
    // 发送数据
    write(sock, "Hello from UDS Client!", 22);
    
    // 接收响应
    char buf[100] = {0};
    ssize_t bytes_read = read(sock, buf, sizeof(buf) - 1);
    if (bytes_read > 0) {
        printf("UDS Client received: %s\n", buf);
    }
    
    // 清理
    close(sock);
    return EXIT_SUCCESS;
}

6. 完整示例:TCP Socket

6.1 服务器端

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

int main() {
    // 创建socket
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        return EXIT_FAILURE;
    }
    
    // 设置地址重用选项
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        perror("setsockopt");
        close(server_fd);
        return EXIT_FAILURE;
    }
    
    // 准备IPv4地址结构
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
    addr.sin_port = htons(8080);       // 使用8080端口
    
    // 绑定socket到地址
    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        close(server_fd);
        return EXIT_FAILURE;
    }
    
    // 监听连接请求
    if (listen(server_fd, 5) == -1) {
        perror("listen");
        close(server_fd);
        return EXIT_FAILURE;
    }
    
    printf("TCP Server waiting for connections on port 8080...\n");
    
    // 接受连接
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("accept");
        close(server_fd);
        return EXIT_FAILURE;
    }
    
    // 打印客户端信息
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
    printf("Connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
    
    // 通信示例
    char buf[100] = {0};
    ssize_t bytes_read = read(client_fd, buf, sizeof(buf) - 1);
    if (bytes_read > 0) {
        printf("TCP Server received: %s\n", buf);
        write(client_fd, "Hello from TCP Server!", 22);
    }
    
    // 清理资源
    close(client_fd);
    close(server_fd);
    return EXIT_SUCCESS;
}

6.2 客户端

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 创建socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        return EXIT_FAILURE;
    }
    
    // 准备服务器地址
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    
    // 将字符串IP地址转换为网络字节序
    if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sock);
        return EXIT_FAILURE;
    }
    
    // 连接到服务器
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        close(sock);
        return EXIT_FAILURE;
    }
    
    printf("Connected to server at 127.0.0.1:8080\n");
    
    // 发送数据
    write(sock, "Hello from TCP Client!", 22);
    
    // 接收响应
    char buf[100] = {0};
    ssize_t bytes_read = read(sock, buf, sizeof(buf) - 1);
    if (bytes_read > 0) {
        printf("TCP Client received: %s\n", buf);
    }
    
    // 清理
    close(sock);
    return EXIT_SUCCESS;
}

7. 高级Socket选项

使用setsockopt()getsockopt()可以调整socket的各种属性:

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

常用选项示例

// 允许地址复用(避免"Address already in use"错误)
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

// 设置接收超时
struct timeval tv = {.tv_sec = 5, .tv_usec = 0}; // 5秒超时
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

// 启用TCP保活机制
int keepalive = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));

// 设置TCP保活参数(Linux特有)
int idle = 60;      // 60秒无数据则开始探测
int interval = 5;   // 每5秒发送一次探测包
int maxpkt = 5;     // 最多发送5次探测包
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt));

8. 选择建议与最佳实践

8.1 选择合适的Socket类型

  • Unix Domain Socket
    • 同一主机上的进程间通信
    • 需要高性能、低延迟的IPC场景
    • 需要传递文件描述符的场景
    • 示例:Web服务器与本地缓存通信、数据库连接
  • TCP Socket
    • 需要可靠的、有序的数据传输
    • 跨网络/主机的通信
    • 数据完整性要求高的应用
    • 示例:HTTP、FTP、SMTP等应用层协议
  • UDP Socket
    • 需要低延迟、实时性要求高的场景
    • 可以容忍少量数据丢失
    • 短小、独立的消息通信
    • 示例:DNS查询、实时游戏、流媒体

8.2 安全考虑

  • 避免在公开访问的目录中创建Unix Domain Socket
  • 正确设置UDS文件的权限
  • 使用抗DDoS技术保护网络Socket
  • 考虑使用TLS/SSL加密敏感通信

8.3 性能优化技巧

  • 为高性能服务器考虑非阻塞I/O或I/O多路复用(select/poll/epoll)
  • 适当调整socket缓冲区大小
  • 使用TCP_NODELAY禁用Nagle算法降低小包延迟
  • 对批量数据传输,考虑启用TCP_CORK
  • 在多核系统上,避免多线程共享同一socket

9. 总结

socket()函数是构建网络应用的基础,掌握其参数和用法是网络编程的第一步。根据通信需求选择合适的domain、type和protocol组合,能够创建满足各种通信场景的socket,从进程间通信到跨互联网的应用都能胜任。

相关文章:

  • 用 Python 实现一个 Benchmark 工具
  • MySQL数据库精研之旅第五期:CRUD的趣味探索(上)
  • Assembly语言的嵌入式调试
  • 实现一个once函数,传入函数参数只执行一次
  • AI推理胜过人脑?思维模型!【33】心流理论思维模型
  • Vue 的 nextTick 是如何实现的?
  • Docker中设置default-ulimits参数解决资源限制问题
  • 尝试想一下,三进制电脑应该怎么玩(一)
  • 解锁Nginx路由器匹配规则
  • 【CSS】入门基础
  • CSS3学习教程,从入门到精通, CSS3 列表控制详解语法知识点及案例代码(24)
  • QListView开发入门
  • CUDA Memory Fence 函数的功能与硬件实现细节
  • Dubbo分布式开发框架
  • HarmonyOS(扩展篇四):工业互联网操作系统
  • Windows 图形显示驱动开发-WDDM 2.4功能-GPU 半虚拟化(十二)
  • Spring Boot 3.4.3 基于 Caffeine 实现本地缓存
  • MyBatis-Plus 从入门到精通教学文档
  • 高等数学-第七版-上册 选做记录 习题7-5
  • ANSYS Scade One Swan语言与Scade 6的区别 - 状态机部分的改变