34、进程间通信与网络套接字性能分析及使用指南

进程间通信与网络套接字性能分析及使用指南

1. 进程间通信性能比较

在进程间通信(IPC)中,不同的方法在性能上存在差异。对于POSIX调用,使用共享内存内的内存信号量理论上会更快,但实际情况取决于具体实现。

POSIX和System V的共享内存接口便利性相当,效率高低也取决于实现,且同一系统可能对两者采用相同的内核内部机制。POSIX共享内存的优势在于 mmap 调用可用于任何常规文件,且能灵活控制映射部分;缺点是并非所有系统都支持,如部分Linux、FreeBSD和Darwin版本。

通过对六种不同IPC方法(包括套接字)进行测试,发送5000条消息,对Solaris、FreeBSD、Darwin和Linux四个系统的结果进行归一化处理(以100字节消息的FIFO时间为1.00),得到如下性能数据:

方法 100 - 字节消息 2000 - 字节消息 20,000 - 字节消息 100,000 - 字节消息
FIFO S: 1.00
B: 1.00
D: 1.00
L: 1.00
S: 1.22
B: 1.46
D: 1.29
L: 1.51
too big too big
System V消息队列 S: 0.90
B: 0.62
L: 0.31
S: 1.82
B: 3.76
L: 0.64
too big too big
POSIX消息队列 S: 2.02 S: 2.40 S: 7.03 S: 33.39
System V共享内存 S: 1.47
B: 0.94
D: 1.04
L: 0.55
S: 1.41
B: 0.91
D: 1.07
L: 0.53
S: 1.55
B: 0.90
D: 1.02
L: 0.47
S: 1.24
B: 0.90
D: 1.06
L: 0.51
POSIX共享内存 S: 1.27 S: 1.25 S: 1.41 S: 1.37
套接字 S: 1.84
B: 0.81
D: 1.04
L: 0.75
S: 2.15
B: 1.00
D: 1.27
L: 0.95
S: 10.15
B: 7.99
D: 5.52
L: 6.06
S: 44.13
B: 35.83
D: 25.86
L: 31.91

注:S代表Solaris,B代表FreeBSD,D代表Darwin,L代表Linux。

对结果的分析如下:
- 测试结果并非绝对,因为这只是一种可能的测试,且实现并非最优。
- 归一化后,某系统某方法时间快,不代表该方法实现更好,可能是FIFO实现较慢。
- 对于小消息(约100字节),除Darwin 6.6不支持外,System V消息队列在各系统表现出色。
- 套接字性能与两种消息队列方法相近,能处理任意大小消息,且是唯一可跨机器通信的方法(测试仅在单机进行)。
- 在Solaris上,POSIX消息队列性能不如System V消息队列,但能处理大消息。
- 两种共享内存方法的性能与消息大小无关。

基于以上分析,若追求最大性能,小消息使用System V消息队列,大消息使用共享内存;若对性能要求不高且追求简单,可使用套接字。

2. 套接字基础

现代应用常需在不同机器间传输数据,基本机制是套接字。套接字有八个基本系统调用,其中五个是套接字特有的: socket bind listen accept connect read write close ,此外还有约60个相关系统调用。

2.1 套接字工作原理

为理解套接字,先看进程打开FIFO的过程:

fd = open("MyFifo", O_RDONLY);

该系统调用的底层操作如下:
1. 创建I/O端点并分配文件描述符。
2. 将文件描述符绑定到外部名称“MyFifo”。
3. 等待写入者。
4. 返回文件描述符用于读取。

套接字的工作方式类似,但每个步骤由单独的系统调用完成:
- socket :创建通信端点并分配文件描述符。
- bind :将套接字与外部名称关联。
- listen :标记套接字可接受连接。
- accept :阻塞等待连接。
- connect :连接到处于 accept 阻塞状态的套接字。

FIFO在客户端和服务器端的系统调用对称,而连接的套接字通常不对称,客户端和服务器使用不同的系统调用序列。

以下是一个简单的客户端 - 服务器示例代码:

#define SOCKETNAME "MySocket"
int main(void)
{
    struct sockaddr_un sa;
    (void)unlink(SOCKETNAME);
    strcpy(sa.sun_path, SOCKETNAME);
    sa.sun_family = AF_UNIX;
    if (fork() == 0) { /* child -- client */
        int fd_skt;
        char buf[100];
        ec_neg1( fd_skt = socket(AF_UNIX, SOCK_STREAM, 0) )
        while (connect(fd_skt, (struct sockaddr *)&sa, sizeof(sa)) == -1)
            if (errno == ENOENT) {
                sleep(1);
                continue;
            }
            else
                EC_FAIL
        ec_neg1( write(fd_skt, "Hello!", 7 ) )
        ec_neg1( read(fd_skt, buf, sizeof(buf)) )
        printf("Client got \"%s\"\n", buf);
        ec_neg1( close(fd_skt) )
        exit(EXIT_SUCCESS);
    }
    else { /* parent -- server */
        int fd_skt, fd_client;
        char buf[100];
        ec_neg1( fd_skt = socket(AF_UNIX, SOCK_STREAM, 0) )
        ec_neg1( bind(fd_skt, (struct sockaddr *)&sa, sizeof(sa)) )
        ec_neg1( listen(fd_skt, SOMAXCONN) )
        ec_neg1( fd_client = accept(fd_skt, NULL, 0) )
        ec_neg1( read(fd_client, buf, sizeof(buf)) )
        printf("Server got \"%s\"\n", buf);
        ec_neg1( write(fd_client, "Goodbye!", 9 ) )
        ec_neg1( close(fd_skt) )
        ec_neg1( close(fd_client) )
        exit(EXIT_SUCCESS);
    }
EC_CLEANUP_BGN
        exit(EXIT_FAILURE);
EC_CLEANUP_END
}

在这个示例中, bind connect 传递的是 sockaddr 结构, AF_UNIX 表示单机本地通信。服务器按步骤执行,客户端若在服务器 bind connect 会失败,需等待。 accept connect 是阻塞调用,连接建立后像双向管道。

2.2 连接套接字的基本系统调用

连接的套接字通过 accept connect 建立通道,以下是五个基本的套接字特定系统调用:
- socket :创建通信端点

#include <sys/socket.h>
int socket(
    int domain,  /* 域 (AF_UNIX, AF_INET, 等) */
    int type,    /* SOCK_STREAM, SOCK_DGRAM, 等 */
    int protocol /* 特定类型; 通常为零 */
);
/* 返回文件描述符,出错返回 -1 (设置 errno) */
  • bind :将名称绑定到套接字
#include <sys/socket.h>
int bind(
    int socket_fd,         /* 套接字文件描述符 */
    const struct sockaddr *sa, /* 套接字地址 */
    socklen_t sa_len       /* 地址长度 */
);
/* 成功返回 0,出错返回 -1 (设置 errno) */
  • listen :标记套接字可接受连接并设置队列限制
#include <sys/socket.h>
int listen(
    int socket_fd, /* 套接字文件描述符 */
    int backlog    /* 最大连接队列长度 */
);
/* 成功返回 0,出错返回 -1 (设置 errno) */
  • accept :接受新连接并创建新套接字
#include <sys/socket.h>
int accept(
    int socket_fd,    /* 套接字文件描述符 */
    struct sockaddr *sa, /* 套接字地址或 NULL */
    socklen_t *sa_len    /* 地址长度 */
);
/* 返回文件描述符,出错返回 -1 (设置 errno) */
  • connect :连接套接字
#include <sys/socket.h>
int connect(
    int socket_fd,         /* 套接字文件描述符 */
    const struct sockaddr *sa, /* 套接字地址 */
    socklen_t sa_len       /* 地址长度 */
);
/* 成功返回 0,出错返回 -1 (设置 errno) */
2.3 处理多个客户端

为使服务器能处理多个客户端,可对之前的示例进行扩展。以下是服务器端代码:

static bool run_server(struct sockaddr_un *sap)
{
    int fd_skt, fd_client, fd_hwm = 0, fd;
    char buf[100];
    fd_set set, read_set;
    ssize_t nread;
    ec_neg1( fd_skt = socket(AF_UNIX, SOCK_STREAM, 0) )
    ec_neg1( bind(fd_skt, (struct sockaddr *)sap, sizeof(*sap)) )
    ec_neg1( listen(fd_skt, SOMAXCONN) )
    if (fd_skt > fd_hwm)
        fd_hwm = fd_skt;
    FD_ZERO(&set);
    FD_SET(fd_skt, &set);
    while (true) {
        read_set = set;
        ec_neg1( select(fd_hwm + 1, &read_set, NULL, NULL, NULL) )
        for (fd = 0; fd <= fd_hwm; fd++)
            if (FD_ISSET(fd, &read_set)) {
                if (fd == fd_skt) {
                    ec_neg1( fd_client = accept(fd_skt, NULL, 0) )
                    FD_SET(fd_client, &set);
                    if (fd_client > fd_hwm)
                        fd_hwm = fd_client;
                }
                else {
                    ec_neg1( nread = read(fd, buf, sizeof(buf)) )
                    if (nread == 0) {
                        FD_CLR(fd, &set);
                        if (fd == fd_hwm)
                            fd_hwm--;
                        ec_neg1( close(fd) )
                    }
                    else {
                        printf("Server got \"%s\"\n", buf);
                        ec_neg1( write(fd, "Goodbye!", 9 ) )
                    }
                }
            }
    }
    ec_neg1( close(fd_skt) )
    return true;
EC_CLEANUP_BGN
    return false;
EC_CLEANUP_END
}

客户端代码与之前类似,只是消息包含进程ID:

static bool run_client(struct sockaddr_un *sap)
{
    if (fork() == 0) {
        int fd_skt;
        char buf[100];
        ec_neg1( fd_skt = socket(AF_UNIX, SOCK_STREAM, 0) )
        while (connect(fd_skt, (struct sockaddr *)sap, sizeof(*sap)) == -1)
            if (errno == ENOENT) {
                sleep(1);
                continue;
            }
            else
                EC_FAIL
        snprintf(buf, sizeof(buf), "Hello from %ld!",
          (long)getpid());
        ec_neg1( write(fd_skt, buf, strlen(buf) + 1 ) )
        ec_neg1( read(fd_skt, buf, sizeof(buf)) )
        printf("Client got \"%s\"\n", buf);
        ec_neg1( close(fd_skt) )
        exit(EXIT_SUCCESS);
    }
    return true;
EC_CLEANUP_BGN
    /* only child gets here */
    exit(EXIT_FAILURE);
EC_CLEANUP_END
}

主函数设置套接字地址,创建四个客户端子进程并运行服务器:

#define SOCKETNAME "MySocket"
int main(void)
{
    struct sockaddr_un sa;
    int nclient;
    (void)unlink(SOCKETNAME);
    strcpy(sa.sun_path, SOCKETNAME);
    sa.sun_family = AF_UNIX;
    for (nclient = 1; nclient <= 4; nclient++)
        ec_false( run_client(&sa) )
    ec_false( run_server(&sa) )
    exit(EXIT_SUCCESS);
EC_CLEANUP_BGN
        exit(EXIT_FAILURE);
EC_CLEANUP_END
}
2.4 字节顺序

在网络中传递字符串无问题,但传递二进制数字可能因不同机器的字节排列方式不同而出现问题。小端机器中,数字地址是最低字节地址;大端机器中,是最高字节地址。若不同字节序机器间传递二进制数字,会导致数字混乱。

解决方法是使用统一的网络字节序,发送方将主机字节序转换为网络字节序,接收方反之。对于16位和32位整数,有标准的转换函数:
- htons :将16位值从主机字节序转换为网络字节序

#include <arpa/inet.h>
uint16_t htons(
    uint16_t hostnum /* 主机字节序的16位数字 */
);
/* 返回网络字节序的数字 (无错误返回) */
  • htonl :将32位值从主机字节序转换为网络字节序
#include <arpa/inet.h>
uint32_t htonl(
    uint32_t hostnum /* 主机字节序的32位数字 */
);
/* 返回网络字节序的数字 (无错误返回) */
  • ntohs :将16位值从网络字节序转换为主机字节序
#include <arpa/inet.h>
uint16_t ntohs(
    uint16_t netnum /* 网络字节序的16位数字 */
);
/* 返回主机字节序的数字 (无错误返回) */
  • ntohl :将32位值从网络字节序转换为主机字节序
#include <arpa/inet.h>
uint32_t ntohl(
    uint32_t netnum /* 网络字节序的32位数字 */
);
/* 返回主机字节序的数字 (无错误返回) */

以下是一个展示转换函数使用的示例代码:

int main(void)
{
    uint16_t nhost = 0xD04C, nnetwork;
    unsigned char *p;
    p = (unsigned char *)&nhost;
    printf("%x %x\n", *p, *(p + 1));
    nnetwork = htons(nhost);
    p = (unsigned char *)&nnetwork;
    printf("%x %x\n", *p, *(p + 1));
    exit(EXIT_SUCCESS);
}

该示例输出表明运行的Intel Pentium CPU字节序与网络不同。

3. 套接字地址与选项

在使用套接字进行网络编程时,套接字地址和选项是非常重要的概念。

3.1 套接字地址

套接字地址是一个复杂的主题,不同的地址族有不同的地址结构。前面已经介绍过 AF_UNIX 地址族,用于单机本地通信。对于网络通信,常用的地址族是 AF_INET AF_INET6

AF_INET 为例,其地址结构如下:

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family; /* 地址族 (AF_INET) */
    in_port_t      sin_port;   /* 端口号 (网络字节序) */
    struct in_addr sin_addr;   /* IP 地址 */
    char           sin_zero[8];/* 填充字节,使结构大小与 sockaddr 相同 */
};

struct in_addr {
    uint32_t       s_addr;     /* IP 地址 (网络字节序) */
};

在使用时,需要将端口号和IP地址转换为网络字节序:

#include <arpa/inet.h>

struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(8080); // 转换端口号到网络字节序
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr); // 转换 IP 地址到网络字节序
3.2 套接字选项

套接字选项可以用来调整套接字的行为,例如设置超时时间、启用广播等。使用 setsockopt getsockopt 函数来设置和获取套接字选项。

#include <sys/socket.h>

// 设置套接字选项
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);

参数说明:
- sockfd :套接字文件描述符。
- level :选项的级别,如 SOL_SOCKET (通用套接字选项)、 IPPROTO_TCP (TCP 选项)等。
- optname :具体的选项名称,如 SO_REUSEADDR (允许地址重用)、 TCP_NODELAY (禁用 Nagle 算法)等。
- optval :选项的值。
- optlen :选项值的长度。

以下是一个设置地址重用选项的示例:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    int optval = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("setsockopt");
        return 1;
    }

    // 其他操作...

    close(sockfd);
    return 0;
}
4. 无连接套接字

前面介绍的是连接的套接字,而无连接套接字则不需要建立连接,直接发送数据报。无连接套接字使用 SOCK_DGRAM 类型,常用的协议是 UDP。

4.1 无连接套接字的基本系统调用

无连接套接字不使用 listen accept connect 的使用方式也不同。主要使用 sendto recvfrom 函数进行数据的发送和接收。

#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);

// 接收数据报
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数说明:
- sockfd :套接字文件描述符。
- buf :数据缓冲区。
- len :数据长度。
- flags :标志位,通常为 0。
- dest_addr :目标地址( sendto )。
- src_addr :源地址( recvfrom )。
- addrlen :地址长度。

4.2 无连接套接字示例

以下是一个简单的无连接套接字示例,包括客户端和服务器端:

服务器端代码

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        return 1;
    }

    while (1) {
        // 接收数据
        ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
        if (recv_len == -1) {
            perror("recvfrom");
            continue;
        }

        buffer[recv_len] = '\0';
        printf("Received from client: %s\n", buffer);

        // 发送响应
        const char *response = "Message received";
        sendto(sockfd, response, strlen(response), 0, (struct sockaddr *)&client_addr, client_addr_len);
    }

    close(sockfd);
    return 0;
}

客户端代码

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

#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    socklen_t server_addr_len = sizeof(server_addr);
    char buffer[BUFFER_SIZE];

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_addr.sin_port = htons(PORT);

    // 发送数据
    const char *message = "Hello, server!";
    sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, server_addr_len);

    // 接收响应
    ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);
    if (recv_len == -1) {
        perror("recvfrom");
        return 1;
    }

    buffer[recv_len] = '\0';
    printf("Received from server: %s\n", buffer);

    close(sockfd);
    return 0;
}
5. 处理大量客户端的服务器设计

在实际应用中,服务器可能需要处理大量的客户端连接。为了实现这一目标,可以采用以下几种方法:

5.1 多进程模型

为每个客户端连接创建一个新的进程来处理。这种方法简单易懂,但开销较大,因为每个进程都有自己的内存空间和系统资源。

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

#define PORT 8080
#define BUFFER_SIZE 1024

void handle_client(int client_fd) {
    char buffer[BUFFER_SIZE];
    ssize_t recv_len;

    while ((recv_len = read(client_fd, buffer, BUFFER_SIZE)) > 0) {
        buffer[recv_len] = '\0';
        printf("Received from client: %s\n", buffer);
        write(client_fd, "Message received", strlen("Message received"));
    }

    close(client_fd);
}

int main() {
    int sockfd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听连接
    if (listen(sockfd, SOMAXCONN) == -1) {
        perror("listen");
        return 1;
    }

    signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号

    while (1) {
        // 接受连接
        client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_fd == -1) {
            perror("accept");
            continue;
        }

        // 创建子进程处理客户端连接
        pid_t pid = fork();
        if (pid == 0) {
            close(sockfd); // 子进程关闭监听套接字
            handle_client(client_fd);
            exit(0);
        } else {
            close(client_fd); // 父进程关闭客户端套接字
        }
    }

    close(sockfd);
    return 0;
}
5.2 多线程模型

为每个客户端连接创建一个新的线程来处理。线程的开销比进程小,但需要注意线程同步问题。

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

#define PORT 8080
#define BUFFER_SIZE 1024

void *handle_client(void *arg) {
    int client_fd = *(int *)arg;
    char buffer[BUFFER_SIZE];
    ssize_t recv_len;

    while ((recv_len = read(client_fd, buffer, BUFFER_SIZE)) > 0) {
        buffer[recv_len] = '\0';
        printf("Received from client: %s\n", buffer);
        write(client_fd, "Message received", strlen("Message received"));
    }

    close(client_fd);
    free(arg);
    return NULL;
}

int main() {
    int sockfd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听连接
    if (listen(sockfd, SOMAXCONN) == -1) {
        perror("listen");
        return 1;
    }

    while (1) {
        // 接受连接
        client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_fd == -1) {
            perror("accept");
            continue;
        }

        // 创建线程处理客户端连接
        pthread_t thread_id;
        int *client_fd_ptr = malloc(sizeof(int));
        *client_fd_ptr = client_fd;
        if (pthread_create(&thread_id, NULL, handle_client, (void *)client_fd_ptr) != 0) {
            perror("pthread_create");
            free(client_fd_ptr);
            close(client_fd);
        }
        pthread_detach(thread_id); // 分离线程
    }

    close(sockfd);
    return 0;
}
5.3 I/O 多路复用模型

使用 select poll epoll 等函数来监听多个套接字的 I/O 事件,从而在一个线程中处理多个客户端连接。以 epoll 为例:

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

#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10

int main() {
    int sockfd, epoll_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct epoll_event ev, events[MAX_EVENTS];
    char buffer[BUFFER_SIZE];

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    // 初始化服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听连接
    if (listen(sockfd, SOMAXCONN) == -1) {
        perror("listen");
        return 1;
    }

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        return 1;
    }

    // 添加监听套接字到 epoll 实例
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {
        perror("epoll_ctl");
        return 1;
    }

    while (1) {
        // 等待事件发生
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            return 1;
        }

        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == sockfd) {
                // 接受新连接
                client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
                if (client_fd == -1) {
                    perror("accept");
                    continue;
                }

                // 添加新客户端套接字到 epoll 实例
                ev.events = EPOLLIN;
                ev.data.fd = client_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
                    perror("epoll_ctl");
                    close(client_fd);
                }
            } else {
                // 处理客户端数据
                ssize_t recv_len = read(events[i].data.fd, buffer, BUFFER_SIZE);
                if (recv_len <= 0) {
                    // 客户端关闭连接
                    epoll_ctl(epoll_fd, EPOLL_CTL_REMOVE, events[i].data.fd, NULL);
                    close(events[i].data.fd);
                } else {
                    buffer[recv_len] = '\0';
                    printf("Received from client: %s\n", buffer);
                    write(events[i].data.fd, "Message received", strlen("Message received"));
                }
            }
        }
    }

    close(sockfd);
    close(epoll_fd);
    return 0;
}

综上所述,在进程间通信和网络编程中,不同的方法和技术适用于不同的场景。通过对各种 IPC 方法和套接字编程的学习和实践,可以根据具体需求选择合适的方案,以实现高效、稳定的应用程序。在处理大量客户端连接时,需要根据系统资源和性能要求选择合适的服务器设计模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值