山高疑日近,海阔觉天低

socket

一,基本函数

在 C++ 的网络编程中,socket 是实现网络通信的基础。以下是针对 TCP 和 UDP 协议中服务端与客户端常用的 socket 相关函数的介绍。TCP 是面向连接的协议,保证数据传输的可靠性;而 UDP 是无连接的协议,简单快速,但不保证可靠传输。

函数 TCP 服务端 TCP 客户端 UDP 服务端 UDP 客户端
socket() 创建一个新的 socket,返回一个文件描述符 创建一个新的 socket,返回一个文件描述符 创建一个新的 socket,用于发送和接收数据 创建一个新的 socket,用于发送和接收数据
bind() 将本地地址绑定到 socket 上,指定本地端口 通常可选,用于绑定到特定的本地端口(不常用) 将本地地址绑定到 socket 上,用于监听特定端口 通常可选,用于绑定到特定的本地端口(不常用)
listen() 监听绑定的网络端口,准备接受客户端连接
accept() 阻塞等待,接受来自客户端的连接,返回新的 socket 文件描述符
connect() 连接到远程服务器的地址和端口
sendto() 用于在无连接模式下发送数据到指定的远程地址和端口 发送数据到指定的服务器地址和端口
recvfrom() 用于接收远程发送来的数据,同时获取发送者的地址和端口 接收来自服务器的数据,同时获得服务器的地址和端口
send() 发送数据到已连接的 socket 发送数据到已连接的 socket (需要bind,不常用) (需要bind,不常用)
recv() 从已连接的 socket 接收数据 从已连接的 socket 接收数据 (需要bind,不常用) (需要bind,不常用)

说明:

socket() TCP & UDP: 使用时需要指定协议类型(socket(AF_INET, SOCK_STREAM, 0) 为 TCP, socket(AF_INET, SOCK_DGRAM, 0) 为 UDP)。
bind() TCP & UDP: 此步骤是将端口和地址分配给 socket,使其确定数据交换的网络入口点。
listen() TCP: 初始化服务器以接受连接请求。
accept() TCP: 为每个连接请求,此函数都会创建一个新的已连接 socket,专用于与请求的客户端进行通信。
connect() TCP: 用于客户端建立与服务器的连接。
sendto() recvfrom() UDP: 由于 UDP 是无连接的,每次发送和接收操作都需要指明对方的地址信息。
send() recv() TCP: 这两个函数分别用于发送和接收数据,操作基于一个已经建立的连接。
UDP: 不常用,因为UDP面向无连接的,可以用sendto,向局域网随便发送而不用建立连接,当然如果固定了IP和端口,也可以用

这个表格提供了 TCP 和 UDP 在两种常见的网络通信环境(客户端与服务端)下各自使用的主要 Socket 函数。根据你的具体需求,选择合适的函数和操作方法是实现有效网络通讯的关键。

二,属性操作

1.setsockopt

1.1 SO_SNDBUF/SO_RCVBUF 设置内核IP层发送缓存大小

对于 TCP 和 UDP 来说,增大 SO_SNDBUF 大小会增加可用于队列输出数据的缓冲区,理论上可以提高传输性能,特别是在高延迟或高吞吐量的应用场景中。然而,对于 UDP 来说,由于缺乏重传机制,发件人需要特别注意保证数据不会因为网络或接收方的状况而丢失。对于 TCP,较大的缓冲区可能帮助改善网络的整体利用率,尤其是在窗口规模较大时(如在长距离延迟大的连接中)。在实际应用中,选择适当的 SO_SNDBUF 大小需要考虑网络条件、应用场景和系统资源。注意MTU是工作的在第二层,比SO_SNDBUF低,设置SO_SNDBUF大于MTU会被分包,TCP倒是没问题,但UDP可能出问题,①防火墙不支持udp分包②UDP是不可靠链接,接受的顺序可能不对了

SO_RCVBUF 比较容易理解:它是指内核为特定套接字分配的缓冲区大小,用于存储从网络到达但尚未被拥有该套接字的程序读取的数据。对于 TCP,如果数据到达而你没有读取,缓冲区会逐渐填满,发送方会被通知减慢发送速度(通过 TCP 窗口调整机制)。对于 UDP,一旦缓冲区满了,新的数据包将被直接丢弃。
SO_SNDBUF 仅对 TCP 有意义(在 UDP 中,你发送的数据会直接发送到网络)。对于 TCP,如果远程端没有读取数据(导致远程缓冲区填满,TCP 会将此情况通知你的内核,你的内核会停止发送数据,而是将数据积累在本地缓冲区中,直到本地缓冲区也填满)。或者,如果网络出现问题,内核没有收到发送数据的确认,也可能导致缓冲区填满。此时,内核会减慢发送数据的速度,直到最终发送缓冲区填满。如果发生这种情况,应用程序对该套接字的后续 write() 调用将被阻塞(或者如果设置了 O_NONBLOCK 选项,会返回 EAGAIN 错误)。

int snd_size = (8192*2 + 10);
socklen_t optlen = sizeof(snd_size);
if(setsockopt(fd,SOL_SOCKET,SO_SNDBUF, (void *)&snd_size, optlen) == -1){
        debug("Warning: setsockopt SO_SNDBUF failed.\n");
        return -1;
}

1.2 SO_SNDTIMEO/SO_RCVTIMEO 设置发送接受延迟

非阻塞模式下,设置 SO_RCVTIMEO 或 SO_SNDTIMEO 后,如果没有数据可读或发送缓冲区满,操作会立即返回 EAGAIN/EWOULDBLOCK,不会等待超时时间。如果超时时间设置为零(默认值),则操作永远不会超时。超时仅对执行套接字 I/O 的系统调用有效(例如 accept(2)、connect(2)、read(2)、recvmsg(2)、send(2)、sendmsg(2));超时对 select(2)、poll(2)、epoll_wait(2) 等调用无效。

if (SOCKET_ERR == setsockopt(ptFtpClass->fd_ctl_socket, SOL_SOCKET, SO_RCVTIMEO,(char*) (&time_out), sizeof(struct timeval)))
{
    debug("set ftp socket timeout setsockopt(SO_RCVTIMEO)  error!\n" );
    ftp_close_ctl_socket(ptFtpClass);
    return SOCKET_ERR;
}

1.3 SO_KEEPALIVE/TCP_KEEPIDLE

仅仅对TCP有效,TCP_KEEPIDLE 是 SO_KEEPALIVE 的补充,只有在 SO_KEEPALIVE (默认关闭)启用时,TCP_KEEPIDLE 才有效。如果未设置 TCP_KEEPIDLE,仅仅用SO_KEEPALIVE则使用系统级参数 tcp_keepalive_time(Linux 默认通常为 7200 秒,即 2 小时)。

// 启用 SO_KEEPALIVE
int keepalive = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) < 0) {
     perror("setsockopt SO_KEEPALIVE");
      return 1;
}

// 设置 TCP_KEEPIDLE(60秒空闲后开始探测)
int idle = 60;
if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) < 0) {
    perror("setsockopt TCP_KEEPIDLE");
    return 1;
}

1.4 SO_REUSEADDR

用于控制绑定地址的行为,特别是在处理 TCP/UDP 套接字绑定时的地址重用问题。它解决了在某些场景下地址绑定失败的问题,例如服务器重启或多个套接字绑定到相同地址的情况。以下是 SO_REUSEADDR 的详细解释,包括它的作用、适用场景和注意事项。特别是服务器快速重启:服务器进程崩溃或重启后,TCP 连接可能仍处于 TIME_WAIT 状态,导致新进程无法绑定到原端口。

int reuse = 1;
 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
   perror("setsockopt SO_REUSEADDR");
   return 1;
}

2.ioctl

2.1FIONBIO

    unsigned long ul = 1;
    if (ioctl(socket, FIONBIO, &ul) < 0) {
        return -1;
    }
    return 0;

3.socket函数

以下是一些常见的 socket 参数组合及其用途:

domain type protocol 用途示例
AF_INET SOCK_STREAM 0 or IPPROTO_TCP TCP 服务器/客户端(如 HTTP、FTP)。
AF_INET SOCK_DGRAM 0 or IPPROTO_UDP UDP 通信(如 DNS、实时音视频)。
AF_INET SOCK_RAW IPPROTO_ICMP ICMP 通信(如 ping 程序)。
AF_INET6 SOCK_STREAM 0 or IPPROTO_TCP IPv6 TCP 连接。
AF_UNIX SOCK_STREAM 0 本地进程间通信(如数据库连接)。
AF_PACKET SOCK_RAW 0 网络数据包捕获(如 Wireshark)。
AF_BLUETOOTH SOCK_STREAM 0 蓝牙 RFCOMM 通信。

AF_UNIX + SOCK_DGRAM 例子

#include 
#include <sys/socket.h>
#include <sys/un.h>
#include 
#include 
#include 

#define SOCKET_PATH "/tmp/unix_dgram_socket"
#define CLIENT_PATH "/tmp/unix_dgram_client"

int main() {
    // 创建 Unix 域数据报套接字
    int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    // 设置客户端地址并绑定(可选,方便服务器回复)
    unlink(CLIENT_PATH);
    struct sockaddr_un client_addr = {0};
    client_addr.sun_family = AF_UNIX;
    strncpy(client_addr.sun_path, CLIENT_PATH, sizeof(client_addr.sun_path) - 1);

    if (bind(sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
        perror("bind");
        close(sock);
        return 1;
    }

    // 设置服务器地址
    struct sockaddr_un server_addr = {0};
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    // 发送消息
    const char *message = "Hello, Server!";
    if (sendto(sock, message, strlen(message), 0,
               (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto");
        close(sock);
        return 1;
    }
    printf("Sent to server: %s\n", message);

    // 接收服务器回复
    char buffer[1024];
    ssize_t len = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
    if (len < 0) {
        perror("recvfrom");
    } else {
        buffer[len] = '
#include 
#include <sys/socket.h>
#include <sys/un.h>
#include 
#include 
#include 
#define SOCKET_PATH "/tmp/unix_dgram_socket"
#define CLIENT_PATH "/tmp/unix_dgram_client"
int main() {
// 创建 Unix 域数据报套接字
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
// 设置客户端地址并绑定(可选,方便服务器回复)
unlink(CLIENT_PATH);
struct sockaddr_un client_addr = {0};
client_addr.sun_family = AF_UNIX;
strncpy(client_addr.sun_path, CLIENT_PATH, sizeof(client_addr.sun_path) - 1);
if (bind(sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
perror("bind");
close(sock);
return 1;
}
// 设置服务器地址
struct sockaddr_un server_addr = {0};
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 发送消息
const char *message = "Hello, Server!";
if (sendto(sock, message, strlen(message), 0,
(struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("sendto");
close(sock);
return 1;
}
printf("Sent to server: %s\n", message);
// 接收服务器回复
char buffer[1024];
ssize_t len = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
if (len < 0) {
perror("recvfrom");
} else {
buffer[len] = '\0';
printf("Received from server: %s\n", buffer);
}
// 清理
close(sock);
unlink(CLIENT_PATH);
return 0;
}
'; printf("Received from server: %s\n", buffer); } // 清理 close(sock); unlink(CLIENT_PATH); return 0; }

4.poll

表格:POLLERR、POLLHUP、POLLNVAL 发生原因

事件 描述 发生原因 典型场景
POLLERR 表示文件描述符上发生了错误,通常是 socket 级别的异常。 1. 连接被对端重置(如客户端发送 RST 分组)2. 网络错误(如不可达的网络)3. socket 配置错误(如设置了无效选项)4. 对端发送非法数据(如协议错误)5. SO_RCVBUF/SO_SNDBUF 溢出(缓冲区满)。 – 客户端突然断开,服务器 socket 触发 POLLERR。- 网络中断导致 TCP 连接失败。- 尝试在已关闭的 socket 上执行操作(如 recv)。
POLLHUP 表示文件描述符的连接被挂起,通常是对端关闭了连接或 socket 已断开。 1. 对端正常关闭连接(如客户端调用 closeshutdown)2. 网络中断(如拔掉网线)。3. 对端进程崩溃(未正常关闭 socket)4. TCP 连接超时(如保活探测失败)。 – 客户端退出,服务器 socket 触发 POLLHUP。- 客户端网络断开,服务器检测到连接中断。- TCP 保活机制检测到对端不可达。
POLLNVAL 表示文件描述符无效,通常是描述符未打开、已关闭或不合法。 1. 文件描述符已关闭(如调用 close 后仍监控)2. 描述符未初始化(如传递了 -1)3. 描述符不是 socket(如普通文件误用)4. pollfd 数组越界(访问无效索引)。 – 程序 bug:监控已关闭的 socket。错误地将非 socket 描述符(如普通文件)传入 poll。数组越界导致 poll 访问无效描述符。
#include 
#include 
#include 
#include 
#include <sys/socket.h>
#include <netinet/in.h>
#include 
#include 

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    // 创建监听 socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        exit(1);
    }

    // 设置 SO_REUSEADDR,允许快速重用端口
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        exit(1);
    }

    // 配置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定和监听
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(1);
    }
    if (listen(server_fd, 5) < 0) {
        perror("listen");
        exit(1);
    }
    printf("Server listening on port %d\n", PORT);

    // 初始化 pollfd 数组
    struct pollfd fds[MAX_CLIENTS + 1]; // +1 用于监听 socket
    int nfds = 1; // 当前监控的文件描述符数量
    fds[0].fd = server_fd;
    fds[0].events = POLLIN; // 监控监听 socket 是否有新连接

    // 初始化客户端 socket 数组
    for (int i = 1; i <= MAX_CLIENTS; ++i) {
        fds[i].fd = -1; // -1 表示未使用
        fds[i].events = POLLIN; // 监控客户端 socket 是否有数据
    }

    while (1) {
        // 调用 poll 监控所有 socket
        int ret = poll(fds, nfds, -1); // -1 表示无限超时
        if (ret < 0) {
            perror("poll");
            exit(1);
        }

        // 检查每个文件描述符的状态
        for (int i = 0; i < nfds; ++i) {
            if (fds[i].fd < 0) continue; // 跳过未使用的描述符

            // 检查是否有事件
            if (fds[i].revents == 0) continue;

            // 1. 监听 socket 有新连接 (POLLIN)
            if (i == 0 && (fds[i].revents & POLLIN)) {
                client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
                if (client_fd < 0) {
                    perror("accept");
                    continue;
                }

                // 查找空闲位置存储新客户端
                int j;
                for (j = 1; j <= MAX_CLIENTS; ++j) { if (fds[j].fd == -1) { fds[j].fd = client_fd; fds[j].events = POLLIN; // 监控数据可读 if (j >= nfds) nfds = j + 1; // 更新监控数量
                        printf("New client connected: fd=%d, slot=%d\n", client_fd, j);
                        break;
                    }
                }
                if (j > MAX_CLIENTS) {
                    printf("Too many clients, rejecting connection\n");
                    close(client_fd);
                }
                continue;
            }

            // 2. 客户端 socket 有数据可读 (POLLIN)
            if (fds[i].revents & POLLIN) {
                ssize_t len = recv(fds[i].fd, buffer, BUFFER_SIZE - 1, 0);
                if (len > 0) {
                    buffer[len] = '
#include 
#include 
#include 
#include 
#include <sys/socket.h>
#include <netinet/in.h>
#include 
#include 
#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// 创建监听 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(1);
}
// 设置 SO_REUSEADDR,允许快速重用端口
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
exit(1);
}
// 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定和监听
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(server_fd, 5) < 0) {
perror("listen");
exit(1);
}
printf("Server listening on port %d\n", PORT);
// 初始化 pollfd 数组
struct pollfd fds[MAX_CLIENTS + 1]; // +1 用于监听 socket
int nfds = 1; // 当前监控的文件描述符数量
fds[0].fd = server_fd;
fds[0].events = POLLIN; // 监控监听 socket 是否有新连接
// 初始化客户端 socket 数组
for (int i = 1; i <= MAX_CLIENTS; ++i) {
fds[i].fd = -1; // -1 表示未使用
fds[i].events = POLLIN; // 监控客户端 socket 是否有数据
}
while (1) {
// 调用 poll 监控所有 socket
int ret = poll(fds, nfds, -1); // -1 表示无限超时
if (ret < 0) {
perror("poll");
exit(1);
}
// 检查每个文件描述符的状态
for (int i = 0; i < nfds; ++i) {
if (fds[i].fd < 0) continue; // 跳过未使用的描述符
// 检查是否有事件
if (fds[i].revents == 0) continue;
// 1. 监听 socket 有新连接 (POLLIN)
if (i == 0 && (fds[i].revents & POLLIN)) {
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
// 查找空闲位置存储新客户端
int j;
for (j = 1; j <= MAX_CLIENTS; ++j) { if (fds[j].fd == -1) { fds[j].fd = client_fd; fds[j].events = POLLIN; // 监控数据可读 if (j >= nfds) nfds = j + 1; // 更新监控数量
printf("New client connected: fd=%d, slot=%d\n", client_fd, j);
break;
}
}
if (j > MAX_CLIENTS) {
printf("Too many clients, rejecting connection\n");
close(client_fd);
}
continue;
}
// 2. 客户端 socket 有数据可读 (POLLIN)
if (fds[i].revents & POLLIN) {
ssize_t len = recv(fds[i].fd, buffer, BUFFER_SIZE - 1, 0);
if (len > 0) {
buffer[len] = '\0';
printf("Received from fd=%d: %s\n", fds[i].fd, buffer);
// 回复客户端
const char *response = "Server received your message\n";
send(fds[i].fd, response, strlen(response), 0);
} else if (len == 0) {
// 客户端关闭连接
printf("Client disconnected: fd=%d\n", fds[i].fd);
close(fds[i].fd);
fds[i].fd = -1; // 标记为未使用
} else {
perror("recv");
}
continue;
}
// 3. 客户端 socket 发生异常 (POLLERR, POLLHUP, POLLNVAL)
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
if (fds[i].revents & POLLERR) {
printf("Error on fd=%d (POLLERR)\n", fds[i].fd);
}
if (fds[i].revents & POLLHUP) {
printf("Hang up on fd=%d (POLLHUP)\n", fds[i].fd);
}
if (fds[i].revents & POLLNVAL) {
printf("Invalid fd=%d (POLLNVAL)\n", fds[i].fd);
}
// 关闭异常 socket
close(fds[i].fd);
fds[i].fd = -1; // 标记为未使用
continue;
}
}
}
// 清理(实际不会到达)
close(server_fd);
return 0;
}
'; printf("Received from fd=%d: %s\n", fds[i].fd, buffer); // 回复客户端 const char *response = "Server received your message\n"; send(fds[i].fd, response, strlen(response), 0); } else if (len == 0) { // 客户端关闭连接 printf("Client disconnected: fd=%d\n", fds[i].fd); close(fds[i].fd); fds[i].fd = -1; // 标记为未使用 } else { perror("recv"); } continue; } // 3. 客户端 socket 发生异常 (POLLERR, POLLHUP, POLLNVAL) if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { if (fds[i].revents & POLLERR) { printf("Error on fd=%d (POLLERR)\n", fds[i].fd); } if (fds[i].revents & POLLHUP) { printf("Hang up on fd=%d (POLLHUP)\n", fds[i].fd); } if (fds[i].revents & POLLNVAL) { printf("Invalid fd=%d (POLLNVAL)\n", fds[i].fd); } // 关闭异常 socket close(fds[i].fd); fds[i].fd = -1; // 标记为未使用 continue; } } } // 清理(实际不会到达) close(server_fd); return 0; }
赞(0) 打赏
未经允许不得转载:Mr.Zhang » socket

评论 抢沙发

你的打赏是我的动力

登录

找回密码

注册