• unix socket接口


    socket

    创建套接字文件:

    #include <sys/socket.h>
    
    // 成功返回非负套接字描述符,失败返回-1
    int socket(int domain, int type, int protocol);
    

    domain值:

    domain 描述
    AF_INET IPv4 Internet protocols
    AF_INET6 IPv6 Internet protocols

    type值:

    type 描述
    SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
    SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).

    protocol值:

    protocol 描述
    IPPROTO_TCP TCP协议
    IPPROTO_UDP UDP协议

    socket里面并没有定义protocal的宏,需要额外引入<netinet/in.h>

    sockaddr

    sockaddr用来记录ip和端口信息,sockaddr是通用的结构体,没有具体划分ip和端口的信息字段,对于ipv4地址需要用sockaddr_in结构体,对于ipv6需要用sockaddr_in6结构体:

    <sys/socket.h>
    
    // 通用地址信息结构体
    struct sockaddr {
        sa_family_t sa_family;      // 地址簇
        char sa_data[14];           // 填充字符
    };
    
    <netinet/in.h>
    
    // 存储ipv4地址
    struct in_addr {
        in_addr_t s_addr;           // ipv4地址(网络序4字节)
    };
    
    // ipv4地址和端口信息
    struct sockaddr_in {
        sa_family_t sin_family;     // AF_INET
        in_port_t sin_port;         // 端口号(网络序2字节)
        struct in_addr sin_addr;    // ipv4地址
        char sin_zero[8];           // 填充字符
    };
    
    // 存储ipv6地址
    struct in6_addr {
        uint8_t s6_addr[16];        // ipv6地址(网络序16字节)
    };
    
    // ipv6地址和端口信息
    struct sockaddr_in6 {
        sa_family_t sin6_family;    // AF_INET6
        in_port_t sin6_port;        // 端口号(网络序2字节)
        uint32_t sin6_flowinfo;     // 流信息
        struct in6_addr sin6_addr;  // ipv6地址
        uint32_t sin6_scope_id;     // 作用域的接口集合
    };
    

    在用sockaddr_in结构体之前,需要清零整个结构体。
    将字符串转换为ip地址类型可以用inet_pton,转换成功返回1:

    <netinet/in.h>
    
    // 转换成功返回1,ip字符串不合法返回0,地址簇不支持返回-1
    int inet_pton(int af, const char *src, void *dst);
    

    除了通过字符串转换ip地址之外,<netinet/in.h>头文件定义了INADDR_ANY来表示0.0.0.0这个通配地址。

    bind

    将socket套接字绑定到指定ip地址和端口上:

    #include <sys/socket.h>
    
    // 失败返回-1,成功返回0
    int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
    

    对于服务器来说,如果要同时监听多张网卡,那么就需要绑定到INADDR_ANY上,如果只接受本机访问,那么需要绑定到127.0.0.1上。

    listen

    将服务器的套接字描述符状态从CLOSED转移到LISTEN

    #include <sys/socket.h>
    
    // 失败返回-1,成功返回0
    int listen(int sockfd, int backlog);
    

    backlog表示连接队列的大小,虽然标准中并没有说明,但BSD4.2的实现中,连接队列里面包括两种状态的连接:

    • 一部分连接刚接收到SYN包,处于SYN_RCVD状态。
    • 一部分连接已经完成了三次握手过程,处于ESTABLISHED状态

    不能将backlog设置为0,因为这一行为标准未定义,如果不想客户端可以连接自己,那么需要关闭该监听套接字。并且实际的连接队列的大小不一定等于backlog,比如有的实现会乘以一个系数1.5。

    accept

    用于接受连接队列中处于ESTABLISHED状态的连接:

    #include <sys/socket.h>
    
    // 成功返回非负的连接套接字描述符,失败返回-1
    int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    

    cliaddr可以用来获取客户端的地址和端口信息。

    #include <arpa/inet.h>
    
    // 转换成功返回dst指针,转换失败返回NULL
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    

    <netinet/in.h>头文件定义了保存ipv4地址和ipv6字符串所需的字符数组长度:

    #define INET_ADDRSTRLEN 16  /* for IPv4 dotted-decimal */
    #define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */
    

    connect

    客户端用来连接服务端:

    #include <sys/socket.h>
    
    // 失败返回-1,成功返回0
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    当客户端调用该接口的时候,就开始TCP三次握手,首先发送SYN包,若:

    • 一直没有收到回复,那么会触发超时机制(75秒),在此期间会不断重发SYNC包,直到返回ETIMEOUT错误
    • 如果服务主机返回RST(reset),那么表示服务主机上该端口没有服务在监听,那么客户端返回ECONNREFUSED错误
    • 如果返回ICMP包,表示网络或主机不可达,那么还是会触发超时机制,在此期间会不断重发SYNC包,直到返回EHOSTUNREACH错误

    客户端代码

    #include <iostream>
    #include <string>
    #include <cstring>          // memset strlen
    #include <cstdint>          // uint16_t
    #include <cstdio>           // snprintf
    #include <sys/socket.h>     // socket
    #include <netinet/in.h>     // IPPROTO_TCP htons
    #include <arpa/inet.h>      // inet_pton
    #include <unistd.h>         // write read
    
    int main(int argc, char const *argv[])
    {
        // 创建连接套接字
        int connectfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (-1 == connectfd) {
            std::cout << "create connectfd failed" << std::endl;
            return -1;
        }
        std::cout << "connectfd: " << connectfd << std::endl;
    
        // 设置服务器地址(ipv4)
        sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        std::string servIP = "127.0.0.1";
        uint16_t servPort = 23333;
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(servPort);
        int res = inet_pton(AF_INET, servIP.c_str(), &servAddr.sin_addr);
        if (1 != res) {
            std::cout << "presentation(" << servIP << ") to numeric failed" << std::endl;
            return -1;
        }
        std::cout << "set server Address success" << std::endl;
    
        // 连接服务器
        res = connect(connectfd, reinterpret_cast<sockaddr *>(&servAddr), sizeof(servAddr));
        if (-1 == res) {
            std::cout << "connect server failed" << std::endl;
            return -1;
        }
        std::cout << "connect server success" << std::endl;
    
        // 体验echo服务
        while (true) {
            // 获取一行输入
            std::string line;
            getline(std::cin, line);
    
            if ("quit" == line) {
                break;
            }
    
            // 发送消息
            ssize_t nLeft = line.size();
            const char *curStr = line.c_str();
            while (0 != nLeft) {
                ssize_t nWrite = write(connectfd, curStr, nLeft);
                if (nWrite < 0) {
                    std::cout << "write connectfd failed" << std::endl;
                    break;
                }
                nLeft -= nWrite;
                curStr += nWrite;
            }
            if (0 != nLeft) {
                break;
            }
    
            // 输出回复
            constexpr int MAX = 1024;
            char buf[MAX + 1];
            ssize_t nRead = read(connectfd, buf, sizeof(buf));
            if (nRead < 0) {
                std::cout << "read connectfd failed" << std::endl;
                break;
            }
            buf[nRead] = '';
            std::cout << buf << std::endl;
        }
    
        // 关闭套接字
        close(connectfd);
    
        return 0;
    }
    

    服务端代码:

    #include <iostream>
    #include <string>
    #include <cstring>          // memset
    #include <cstdint>          // uint16_t
    #include <sys/socket.h>     // socket
    #include <netinet/in.h>     // IPPROTO_TCP htons ntohs INET_ADDRSTRLEN
    #include <arpa/inet.h>      // inet_pton inet_ntop
    #include <unistd.h>         // write read
    
    int main(int argc, char const *argv[])
    {
        // 创建监听socket
        int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (-1 == listenfd) {
            std::cout << "create listenfd failed" << std::endl;
            return -1;
        }
        std::cout << "listenfd: " << listenfd << std::endl;
    
        // 设置服务器地址(ipv4)
        sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        std::string servIP = "0.0.0.0";
        uint16_t servPort = 23333;
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(servPort);
        int res = inet_pton(AF_INET, servIP.c_str(), &servAddr.sin_addr);
        if (1 != res) {
            std::cout << "presentation(" << servIP << ") to numeric failed" << std::endl;
            return -1;
        }
        std::cout << "set server Address success" << std::endl;
    
        // 绑定服务器地址
        res = bind(listenfd, reinterpret_cast<sockaddr*>(&servAddr), sizeof(servAddr));
        if (0 != res) {
            std::cout << "bind listenfd to server address failed" << std::endl;
            return -1;
        }
        std::cout << "bind listenfd to server address success" << std::endl;
    
        // 套接字开启监听
        res = listen(listenfd, 5);
        if (0 != res) {
            std::cout << "listen listenfd failed" << std::endl;
            return -1;
        }
        std::cout << "listen listenfd success" << std::endl;
    
        // 服务循环
        while (true) {
            // 接受远端连接
            sockaddr_in clientAddr;
            socklen_t len = sizeof(clientAddr);
            int connectfd = accept(listenfd, reinterpret_cast<sockaddr*>(&clientAddr), &len);
            if (-1 == connectfd) {
                std::cout << "accept connectfd failed" << std::endl;
                return -1;
            }
            char clientIP[INET_ADDRSTRLEN];
            uint16_t clientPort = ntohs(clientAddr.sin_port);
            inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));
            std::cout << "accept connectfd(" << clientIP << ":" << clientPort << ") success" << std::endl;
    
            // echo服务
            while (true) {
                // 接受消息
                constexpr int MAX = 1024;
                char buf[MAX + 1];
                ssize_t nRead = read(connectfd, buf, sizeof(buf));
                if (nRead < 0) {
                    std::cout << "read connectfd failed" << std::endl;
                    break;
                } else if (0 == nRead) {
                    std::cout << "read EOF" << std::endl;
                    break;
                }
                buf[nRead] = '';
                std::cout << buf << std::endl;
    
                // echo消息
                ssize_t nLeft = strlen(buf);
                const char *curStr = buf;
                while (0 != nLeft) {
                    ssize_t nWrite = write(connectfd, curStr, nLeft);
                    if (nWrite < 0) {
                        std::cout << "write connectfd failed" << std::endl;
                        break;
                    }
                    nLeft -= nWrite;
                    curStr += nWrite;
                }
                if (0 != nLeft) {
                    break;
                }
            }
    
            // 结束本轮服务
            close(connectfd);
        }
    
        return 0;
    }
    
  • 相关阅读:
    [Functional Programming] Building a Timer UI by Composing Callbacks
    [Debug] Use Chrome DevTools console utilities to make debugging easier
    [Debug] Copy a network request as fetch or cURL with Chrome DevTools
    [CSS] Use CSS Variables with Calc and HSL to Implement Dark Mode
    [Kotlin] Adding functions to existing classes (extension functions)
    [Kotlin] Unit testing throws exception
    [Kotlin] Try resource, use {} block
    少儿编程教学环境开发之架构选型篇
    分布式系统理论之Quorum机制
    存储基本概念(lun,volume,HBA,DAS,NAS,SAN,iSCSI,IPSAN)
  • 原文地址:https://www.cnblogs.com/HachikoT/p/12683352.html
Copyright © 2020-2023  润新知