build_embed_linux_system

网络开发应用接口

对于Linux网络编程,最重要的就是socket接口,以及配合使用的select、poll、epoll等事件管理机制;共同构成了网络编程的基本框架。

socket_interface

socket的主要接口主要分为UDP和TCP两种,它们接口类似,不过TCP支持可靠连接,而UDP则不支持,所以接口有些差别。

tcp_socket

tcp_socket的接口分为两部分,服务器端的接口如下:

// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)
/*
AF_INET表示IPv4协议族, AF_INET6表示IPv6协议族,AF_UNIX表示UNIX域套接字
SOCK_STREAM表示面向流的套接字, 支持TCP协议
SOCK_DGRAM表示面向数据报的套接字, 支持UDP协议
SOCK_RAW表示原始套接字, 支持IP协议
*/

// 绑定socket
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int bind (int __fd, const struct sockaddr *__addr, socklen_t __len)

// 监听socket
// @param sockfd: socket句柄
// @param backlog: 最大连接数
// @return: 0成功,-1失败
int listen (int __fd, int __n)

// 接受客户端连接
// @param sockfd: socket句柄
// @param addr: 客户端设备地址
// @param addrlen: 地址长度
// @return: 新的socket句柄
int accept (int __fd, struct sockaddr *__restrict __addr, socklen_t *__restrict __len)

// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags)

// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags)

// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)

对于客户端就连接服务器,进行通讯,接口如下:

// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)   

// 连接服务器
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int connect (int __fd, const struct sockaddr *__addr, socklen_t __len)

// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags) 

// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags)    

// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)

服务器代码如下所示。

// tcp服务器端代码
int tcp_server_task(char *ipaddr, int port)
{
    int sockfd;
    int flag;
    struct sockaddr_in server_addr, client_addr;

    // 创建socket,TCP协议
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }

    // 设置socket属性,允许地址重用
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 绑定socket
        printf("bind error\n");
        return -1;
    }   

    listen(sockfd, 1);

    while (1) {
        int client_len = sizeof(client_addr);
        int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);   // 接受客户端连接
        if (client_fd < 0) {
            printf("accept error\n");
            return -1;
        }
        printf("client ip: %s, port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        while (1) {
            char buf[1024] = {0};
            int len = recv(client_fd, buf, sizeof(buf), 0); // 接收数据
            if (len < 0) {
                printf("recv error\n");
                continue;
            } else if (len == 0) {
                printf("client close\n");
                break;
            }  else {
                // 发送数据
                send(client_fd, buf, len, 0);
            }
            printf("recv: %s\n", buf);
        }
    }
}

客户端代码如下所示。

// tcp客户端代码
int tcp_client_task(char *ipaddr, int port)
{
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建socket,TCP协议
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 连接服务器
        printf("connect error\n");
        return -1;
    }

    while (1) {
        char buf[1024] = {0};
        printf("send: ");
        scanf("%s", buf);
        send(sockfd, buf, strlen(buf), 0);              // 发送数据
        int len = recv(sockfd, buf, sizeof(buf), 0); // 接收数据
        if (len < 0) {
            printf("recv error\n");
            continue;
        }  else {
            printf("recv: %s\n", buf);
            sleep(10);
        }
    }
}

udp_socket

udp_socket的接口和TCP类似,不过UDP没有连接的概念,所以接口也有所不同。

// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)   

// 绑定socket
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int bind (int __fd, const struct sockaddr *__addr, socklen_t __len) 

// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @param to: 目标地址
// @param addrlen: 地址长度
// @return: 发送的字节数            
ssize_t sendto (int __fd, const void *__buf, size_t __n, int __flags, const struct sockaddr *__dest_addr, socklen_t __dest_len)

// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @param from: 来源地址
// @param addrlen: 地址长度
// @return: 接收的字节数
ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags, struct sockaddr *__restrict __addr, socklen_t *__restrict __len)   

// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)

UDP接收端接口如下所示。

// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)   

// 连接服务器
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int connect (int __fd, const struct sockaddr *__addr, socklen_t __len) 

// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @param to: 目标地址
// @param addrlen: 地址长度
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags)

// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @param from: 来源地址
// @param addrlen: 地址长度
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags) 

// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)

UDP服务器的代码如下所示。

// udp服务器端代码
int udp_server_task(char *ipaddr, int port)
{
    int sockfd;
    struct sockaddr_in server_addr, client_addr;

    // 创建socket,UDP协议
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 绑定socket
        printf("bind error\n");
        return -1;
    }

    while (1) {
        int client_len = sizeof(client_addr);
        char buf[1024] = {0};
        int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_len); // 接收数据
        if (len < 0) {
            printf("recv error\n");
            continue;
        } else {
            printf("client ip: %s, port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
            printf("recv: %s\n", buf);
            // 发送数据
            sendto(sockfd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
        }
    }
}

UDP客户端的代码如下所示。

// udp客户端代码
int udp_client_task(char *ipaddr, int port)
{
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建socket,UDP协议
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 连接服务器
        printf("connect error\n");
        return -1;
    }

    while (1) {
        char buf[1024] = {0};
        printf("send: ");
        scanf("%s", buf);
        send(sockfd, buf, strlen(buf), 0);              // 发送数据
        int len = recv(sockfd, buf, sizeof(buf), 0); // 接收数据
        if (len < 0) {
            printf("recv error\n");
            continue;
        }
        printf("recv: %s\n", buf);
    }

    close(sockfd);
}

socket_with_select

对于socket编程,最重要的就是事件管理机制,对于socket编程,最常用的事件管理机制就是select、poll、epoll。

select的接口如下所示。

// 清空文件描述符集合
// @param fdset: 文件描述符集合
void FD_ZERO (fd_set *__fdset)

// 添加文件描述符到集合
// @param fd: 文件描述符
// @param fdset: 文件描述符集合
void FD_SET (int __fd, fd_set *__fdset)

// 等待事件
// @param nfds: 最大的文件描述符+1
// @param readfds: 可读事件的文件描述符集合
// @param writefds: 可写事件的文件描述符集合
// @param exceptfds: 异常事件的文件描述符集合
// @param timeout: 超时时间
// @return: 就绪的文件描述符数量
int select (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, struct timeval *__restrict __timeout)

使用select的TCP服务器的代码如下所示。

// 使用select的TCP服务器
int tcp_server_with_select(char *ipaddr, int port)
{
    int sockfd, max_fd;
    fd_set read_fds;
    struct sockaddr_in server_addr;
    int client_fds[FD_SETSIZE];  // 客户端fd数组
    int i;

    // 初始化客户端fd数组
    for (i = 0; i < FD_SETSIZE; i++) {
        client_fds[i] = -1;
    }

    // 创建socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }

    // 绑定地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        printf("bind error\n");
        return -1;
    }

    // 监听
    listen(sockfd, 5);

    while (1) {
        // 清空fd集合
        FD_ZERO(&read_fds);
        // 添加服务器socket到集合
        FD_SET(sockfd, &read_fds);
        max_fd = sockfd;

        // 添加所有客户端socket到集合
        for (i = 0; i < FD_SETSIZE; i++) {
            if (client_fds[i] > 0) {
                FD_SET(client_fds[i], &read_fds);
                if (client_fds[i] > max_fd) {
                    max_fd = client_fds[i];
                }
            }
        }

        // 等待事件
        int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ret < 0) {
            printf("select error\n");
            continue;
        }

        // 检查是否有新连接
        if (FD_ISSET(sockfd, &read_fds)) {
            struct sockaddr_in client_addr;
            socklen_t client_len = sizeof(client_addr);
            int new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
            if (new_fd < 0) {
                printf("accept error\n");
                continue;
            }

            // 将新连接添加到客户端数组
            for (i = 0; i < FD_SETSIZE; i++) {
                if (client_fds[i] < 0) {
                    client_fds[i] = new_fd;
                    break;
                }
            }
            if (i == FD_SETSIZE) {
                printf("too many clients\n");
                close(new_fd);
            }
        }

        // 处理客户端数据
        for (i = 0; i < FD_SETSIZE; i++) {
            if (client_fds[i] > 0 && FD_ISSET(client_fds[i], &read_fds)) {
                char buf[1024] = {0};
                int len = recv(client_fds[i], buf, sizeof(buf), 0);
                if (len <= 0) {
                    // 客户端断开连接
                    close(client_fds[i]);
                    client_fds[i] = -1;
                } else {
                    // 处理数据
                    send(client_fds[i], buf, len, 0);
                }
            }
        }
    }

    close(sockfd);
    return 0;
}

使用select的UDP服务器的代码如下所示。

// 使用select的UDP服务器
int udp_server_with_select(char *ipaddr, int port)  
{
    int sockfd, max_fd;
    fd_set read_fds;
    struct sockaddr_in server_addr, client_addr;    

    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        printf("socket error\n");
        return -1;
    }

    // 绑定地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipaddr);
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        printf("bind error\n");
        return -1;
    }

    while (1) {
        // 清空fd集合
        FD_ZERO(&read_fds);
        // 添加服务器socket到集合
        FD_SET(sockfd, &read_fds);
        max_fd = sockfd;

        // 等待事件
        int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ret < 0) {
            printf("select error\n");
            continue;
        }

        // 检查是否有新连接
        if (FD_ISSET(sockfd, &read_fds)) {
            socklen_t client_len = sizeof(client_addr);
            char buf[1024] = {0};
            int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_len);
            if (len < 0) {
                printf("recvfrom error\n");
                continue;
            }
            // 处理数据
            sendto(sockfd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
        }
    }

    close(sockfd);
}

summary

本章全面介绍了Linux网络编程的核心技术,重点讲解了TCP和UDP两种主要协议的socket编程接口及其实现原理。通过详细的代码示例,展示了如何构建TCP/UDP服务器和客户端,并深入探讨了select事件管理机制在多路复用中的应用。此外,本章还涵盖了网络编程中的关键概念,如地址族、端口绑定、数据收发等,为开发者提供了从基础到实践的完整指导。这些知识是构建高效、可靠网络应用程序的基石,为后续学习更高级的网络编程技术奠定了坚实基础。

next_chapter

返回目录

直接开始下一节说明: 进程间通讯方法