43、进程间通信:消息队列与套接字详解

进程间通信:消息队列与套接字详解

1. 消息队列基础

消息队列在进程间通信中展现出显著优势,相较于管道,它无需进程自行提供同步方法。只要消息队列有空间,发送者就能创建队列、放入数据,甚至在接收者启动前退出。以下是一个简单示例:

if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) {
    fprintf(stderr, “msgsnd failed\n”);
    exit(EXIT_FAILURE);
}
if (strncmp(buffer, “end”, 3) == 0) {
    running = 0;
}
exit(EXIT_SUCCESS);

1.1 消息队列工作原理

发送者程序使用 msgget 创建消息队列,再用 msgsnd 添加消息;接收者通过 msgget 获取队列标识符,接收消息直至收到特殊文本“end”,最后用 msgctl 删除队列。

1.2 CD数据库应用改造

为了让CD数据库应用使用消息队列,只需替换相关文件。以下是具体步骤:

1.2.1 修订服务器函数
  1. 包含头文件、声明键和定义结构
#include “cd_data.h”
#include “cliserv.h”
#include <sys/msg.h>
#define SERVER_MQUEUE 1234
#define CLIENT_MQUEUE 4321
struct msg_passed {
    long int msg_key; /* used for client pid */
    message_db_t real_message;
};
  1. 定义队列标识符变量
static int serv_qid = -1;
static int cli_qid = -1;
  1. 创建消息队列
int server_starting()
{
    #if DEBUG_TRACE
    printf(“%d :- server_starting()\n”,  getpid());
    #endif
    serv_qid = msgget((key_t)SERVER_MQUEUE, 0666 | IPC_CREAT);
    if (serv_qid == -1) return(0);
    cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666 | IPC_CREAT);
    if (cli_qid == -1) return(0);
    return(1);
}
  1. 清理资源
void server_ending()
{
    #if DEBUG_TRACE
    printf(“%d :- server_ending()\n”,  getpid());
    #endif
    (void)msgctl(serv_qid, IPC_RMID, 0);
    (void)msgctl(cli_qid, IPC_RMID, 0);
    serv_qid = -1;
    cli_qid = -1;
}
  1. 读取客户端请求
int read_request_from_client(message_db_t *rec_ptr)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
    printf(“%d :- read_request_from_client()\n”,  getpid());
    #endif
    if (msgrcv(serv_qid, (void *)&my_msg, sizeof(*rec_ptr), 0, 0) == -1) {
        return(0);
    }
    *rec_ptr = my_msg.real_message;
    return(1);
}
  1. 发送响应给客户端
int send_resp_to_client(const message_db_t mess_to_send)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
    printf(“%d :- send_resp_to_client()\n”,  getpid());
    #endif
    my_msg.real_message = mess_to_send;
    my_msg.msg_key = mess_to_send.client_pid;
    if (msgsnd(cli_qid, (void *)&my_msg, sizeof(mess_to_send), 0) == -1) {
        return(0);
    }
    return(1);
}
1.2.2 修订客户端函数
  1. 获取队列标识符
int client_starting()
{
    #if DEBUG_TRACE
    printf(“%d :- client_starting\n”,  getpid());
    #endif
    serv_qid = msgget((key_t)SERVER_MQUEUE, 0666);
    if (serv_qid == -1) return(0);
    cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666);
    if (cli_qid == -1) return(0);
    return(1);
}
  1. 清理资源
void client_ending()
{
    #if DEBUG_TRACE
    printf(“%d :- client_ending()\n”,  getpid());
    #endif
    serv_qid = -1;
    cli_qid = -1;
}
  1. 发送消息给服务器
int send_mess_to_server(message_db_t mess_to_send)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
    printf(“%d :- send_mess_to_server()\n”,  getpid());
    #endif
    my_msg.real_message = mess_to_send;
    my_msg.msg_key = mess_to_send.client_pid;
    if (msgsnd(serv_qid, (void *)&my_msg, sizeof(mess_to_send), 0) == -1) {
        perror(“Message send failed”);
        return(0);
    }
    return(1);
}
  1. 读取服务器响应
int read_resp_from_server(message_db_t *rec_ptr)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
    printf(“%d :- read_resp_from_server()\n”,  getpid());
    #endif
    if (msgrcv(cli_qid, (void *)&my_msg, sizeof(*rec_ptr), getpid(), 0) == -1) {
        return(0);
    }
    *rec_ptr = my_msg.real_message;
    return(1);
}
  1. 定义额外函数
int start_resp_to_client(const message_db_t mess_to_send)
{
    return(1);
}
void end_resp_to_client(void)
{
}
int start_resp_from_server(void)
{
    return(1);
}
void end_resp_from_server(void)
{
}

1.3 IPC状态命令

多数Linux系统提供 ipcs ipcrm 命令,用于查看和清理IPC信息与资源。
- 显示信号量状态 :使用 ipcs -s 查看,用 ipcrm -s <id> 删除。
- 显示共享内存状态 :使用 ipcs -m 查看,用 ipcrm -m <id> 删除。
- 显示消息队列状态 :使用 ipcs -q 查看,用 ipcrm -q <id> 删除。

2. 套接字基础

套接字是一种通信机制,可用于本地或跨网络的客户端/服务器系统开发。与管道不同,它明确区分客户端和服务器,能实现多个客户端连接到一个服务器。

2.1 套接字连接

可以将套接字连接类比为打进繁忙大楼的电话。服务器创建套接字并命名,等待客户端连接;客户端创建未命名套接字,连接到服务器的命名套接字。以下是套接字连接的步骤:

graph LR
    A[服务器创建套接字] --> B[服务器命名套接字]
    B --> C[服务器监听连接]
    D[客户端创建套接字] --> E[客户端连接服务器]
    C --> F[服务器接受连接]
    F --> G[服务器与客户端通信]
    E --> G

2.2 简单本地客户端示例

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int sockfd;
    int len;
    struct sockaddr_un address;
    int result;
    char ch = ‘A’;
    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    address.sun_family = AF_UNIX;
    strcpy(address.sun_path, “server_socket”);
    len = sizeof(address);
    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1) {
        perror(“oops: client1”);
        exit(1);
    }
    write(sockfd, &ch, 1);
    read(sockfd, &ch, 1);
    printf(“char from server = %c\n”, ch);
    close(sockfd);
    exit(0);
}

2.3 简单本地服务器示例

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_un server_address;
    struct sockaddr_un client_address;
    unlink(“server_socket”);
    server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, “server_socket”);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
    listen(server_sockfd, 5);
    while(1) {
        char ch;
        printf(“server waiting\n”);
        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd, 
            (struct sockaddr *)&client_address, &client_len);
        read(client_sockfd, &ch, 1);
        ch++;
        write(client_sockfd, &ch, 1);
        close(client_sockfd);
    }
}

2.4 套接字属性

套接字有三个属性:域、类型和协议,还有一个用作名称的地址。

2.4.1 套接字域

常见的套接字域包括:
| 域 | 描述 |
| ---- | ---- |
| AF_UNIX | UNIX内部(文件系统套接字) |
| AF_INET | ARPA Internet协议(UNIX网络套接字) |
| AF_INET6 | IPv6协议 |
| AF_ISO | 基于ISO标准协议的网络 |
| AF_XNS | 施乐网络系统 |

2.4.2 套接字类型
  • 流套接字(SOCK_STREAM) :提供有序、可靠的双向字节流,如TCP/IP连接。
  • 数据报套接字(SOCK_DGRAM) :无连接,消息可能丢失或乱序,如UDP数据报。
2.4.3 套接字协议

通常由套接字类型和域决定,一般选择默认协议(0)。

2.5 创建套接字

使用 socket 系统调用创建套接字:

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

2.6 套接字地址

不同套接字域有不同的地址格式,如AF_UNIX使用 sockaddr_un 结构,AF_INET使用 sockaddr_in 结构。

2.7 命名套接字

使用 bind 系统调用为套接字命名:

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);

2.8 创建套接字队列

使用 listen 系统调用创建队列:

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

2.9 接受连接

使用 accept 系统调用接受连接:

#include <sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);

2.10 请求连接

使用 connect 系统调用请求连接:

#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);

2.11 关闭套接字

使用 close 系统调用关闭套接字:

close(socket);

2.12 套接字通信

将本地套接字示例转换为网络套接字,选择未使用的端口号(如9734)。

2.13 网络客户端示例

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int sockfd;
    int len;
    struct sockaddr_in address;
    int result;
    char ch = ‘A’;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(“127.0.0.1”);
    address.sin_port = 9734;
    len = sizeof(address);
    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1) {
        perror(“oops: client2”);
        exit(1);
    }
    write(sockfd, &ch, 1);
    read(sockfd, &ch, 1);
    printf(“char from server = %c\n”, ch);
    close(sockfd);
    exit(0);
}

2.14 网络服务器示例

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(“127.0.0.1”);
    server_address.sin_port = 9734;
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
    listen(server_sockfd, 5);
    while(1) {
        char ch;
        printf(“server waiting\n”);
        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd, 
            (struct sockaddr *)&client_address, &client_len);
        read(client_sockfd, &ch, 1);
        ch++;
        write(client_sockfd, &ch, 1);
        close(client_sockfd);
    }
}

2.15 主机和网络字节序

不同计算机使用不同的字节序,为确保网络通信中整数的正确传输,需要进行字节序转换:

#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

在服务器和客户端代码中应用这些函数,确保端口号的正确字节序。例如,服务器代码修改为:

server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);

客户端代码修改为:

address.sin_port = htons(9734);

通过以上内容,我们详细介绍了消息队列和套接字的相关知识,包括它们的工作原理、应用示例以及相关的系统调用和命令。这些知识可以帮助你开发出高效、可靠的进程间通信和网络应用程序。

3. 网络编程实践与优化

3.1 网络编程中的错误处理

在网络编程中,各种系统调用可能会失败,因此需要进行错误处理。以下是常见系统调用的错误情况及处理方式:

系统调用 可能的错误值 描述
bind EBADF 文件描述符无效
ENOTSOCK 文件描述符不指向套接字
EINVAL 文件描述符指向已命名的套接字
EADDRNOTAVAIL 地址不可用
EADDRINUSE 地址已被其他套接字绑定
listen EBADF 文件描述符无效
EINVAL 无效参数
ENOTSOCK 文件描述符不指向套接字
accept EBADF 文件描述符无效
ENOTSOCK 文件描述符不指向套接字
EWOULDBLOCK 设置了 O_NONBLOCK 且无待处理连接
EINTR 进程在 accept 时被信号中断
connect EBADF 传入的文件描述符无效
EALREADY 该套接字已有连接正在进行
ETIMEDOUT 连接超时
ECONNREFUSED 服务器拒绝连接请求

3.2 异步连接处理

connect accept 系统调用默认是阻塞的,但可以通过设置 O_NONBLOCK 标志来实现异步操作。以下是异步连接的处理流程:

graph LR
    A[创建套接字] --> B[设置O_NONBLOCK标志]
    B --> C[调用connect]
    C --> D{连接是否立即成功}
    D -- 是 --> E[连接成功]
    D -- 否 --> F{错误是否为EINPROGRESS}
    F -- 是 --> G[使用select检查套接字是否可写]
    G --> H{套接字是否可写}
    H -- 是 --> E
    H -- 否 --> I[连接失败]
    F -- 否 --> I

3.3 多客户端处理

前面的服务器示例只能一次处理一个客户端连接,为了处理多个客户端,可以使用多进程或多线程。以下是使用多进程处理多客户端的示例代码:

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

void handle_client(int client_sockfd) {
    char ch;
    read(client_sockfd, &ch, 1);
    ch++;
    write(client_sockfd, &ch, 1);
    close(client_sockfd);
}

void sigchld_handler(int signo) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;

    signal(SIGCHLD, sigchld_handler);

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(9734);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
    listen(server_sockfd, 5);

    while (1) {
        printf("server waiting\n");
        client_len = sizeof(client_address);
        client_sockfd = accept(server_sockfd, 
            (struct sockaddr *)&client_address, &client_len);

        pid_t pid = fork();
        if (pid == 0) {
            close(server_sockfd);
            handle_client(client_sockfd);
            exit(0);
        } else {
            close(client_sockfd);
        }
    }
}

3.4 套接字选项设置

可以使用 setsockopt 函数设置套接字选项,例如设置 SO_REUSEADDR 选项允许地址重用,避免在服务器重启时出现地址已被使用的错误。

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

int main() {
    int server_sockfd;
    struct sockaddr_in server_address;
    int optval = 1;

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(9734);
    bind(server_sockfd, (struct sockaddr *)&server_address, sizeof(server_address));
    listen(server_sockfd, 5);

    // 后续处理...

    return 0;
}

3.5 网络信息获取

在网络编程中,有时需要获取网络信息,例如主机名、IP地址等。可以使用 gethostbyname gethostbyaddr 函数来实现。

#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

int main() {
    struct hostent *host;
    struct in_addr addr;

    // 通过主机名获取信息
    host = gethostbyname("www.example.com");
    if (host != NULL) {
        printf("Host name: %s\n", host->h_name);
        for (int i = 0; host->h_addr_list[i] != NULL; i++) {
            addr.s_addr = *(unsigned long *)host->h_addr_list[i];
            printf("IP address: %s\n", inet_ntoa(addr));
        }
    }

    // 通过IP地址获取信息
    inet_pton(AF_INET, "192.168.1.1", &addr);
    host = gethostbyaddr(&addr, sizeof(addr), AF_INET);
    if (host != NULL) {
        printf("Host name: %s\n", host->h_name);
    }

    return 0;
}

4. 总结

通过对消息队列和套接字的学习,我们了解了进程间通信和网络编程的基本原理和实现方法。消息队列提供了一种方便的进程间通信方式,无需进程自行同步;套接字则可以实现本地和跨网络的客户端/服务器系统。

在网络编程中,需要注意以下几点:
1. 处理各种系统调用的错误,确保程序的健壮性。
2. 合理使用异步操作,提高程序的性能和响应能力。
3. 采用多进程或多线程处理多客户端连接,满足高并发需求。
4. 设置套接字选项,优化网络通信。
5. 正确处理主机和网络字节序,确保数据在不同计算机之间的正确传输。

掌握这些知识和技巧,可以帮助我们开发出高效、可靠的网络应用程序。

【事件触发一致性】研究多智能体网络如何通过分布式事件驱动控制实现有限时间内的共识(Matlab代码实现)内容概要:本文围绕多智能体网络中的事件触发一致性问题,研究如何通过分布式事件驱动控制实现有限时间内的共识,并提供了相应的Matlab代码实现方案。文中探讨了事件触发机制在降低通信负担、提升系统效率方面的优势,重点分析了多智能体系统在有限时间收敛的一致性控制策略,涉及系统模型构建、触发条件设计、稳定性收敛性分析等核心技术环节。此外,文档还展示了该技术在航空航天、电力系统、机器人协同、无人机编队等多个前沿领域的潜在应用,体现了其跨学科的研究价值和工程实用性。; 适合人群:具备一定控制理论基础和Matlab编程能力的研究生、科研人员及从事自动化、智能系统、多智能体协同控制等相关领域的工程技术人员。; 使用场景及目标:①用于理解和实现多智能体系统在有限时间内达成一致的分布式控制方法;②为事件触发控制、分布式优化、协同控制等课题提供算法设计仿真验证的技术参考;③支撑科研项目开发、学术论文复现及工程原型系统搭建; 阅读建议:建议结合文中提供的Matlab代码进行实践操作,重点关注事件触发条件的设计逻辑系统收敛性证明之间的关系,同时可延伸至其他应用场景进行二次开发性能优化。
【四旋翼无人机】具备螺旋桨倾斜机构的全驱动四旋翼无人机:建模控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的全驱动四旋翼无人机展开,重点研究其动力学建模控制系统设计。通过Matlab代码Simulink仿真实现,详细阐述了该类无人机的运动学动力学模型构建过程,分析了螺旋桨倾斜机构如何提升无人机的全向机动能力姿态控制性能,并设计相应的控制策略以实现稳定飞行精确轨迹跟踪。文中涵盖了从系统建模、控制器设计到仿真验证的完整流程,突出了全驱动结构相较于传统四旋翼在欠驱动问题上的优势。; 适合人群:具备一定控制理论基础和Matlab/Simulink使用经验的自动化、航空航天及相关专业的研究生、科研人员或无人机开发工程师。; 使用场景及目标:①学习全驱动四旋翼无人机的动力学建模方法;②掌握基于Matlab/Simulink的无人机控制系统设计仿真技术;③深入理解螺旋桨倾斜机构对飞行性能的影响及其控制实现;④为相关课题研究或工程开发提供可复现的技术参考代码支持。; 阅读建议:建议读者结合提供的Matlab代码Simulink模型,逐步跟进文档中的建模控制设计步骤,动手实践仿真过程,以加深对全驱动无人机控制原理的理解,并可根据实际需求对模型控制器进行修改优化。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值