• Socket编程-基础使用


    最后更新:2019-10-25

    一 基本概念

    socket, 又称为"套接字"或者"插座". 是操作系统提供的一种进程间通信机制.目前大多用于不同网络设备之间的通信. socket 位于应用层与传输层之间, 通过传递给 socket 不同的参数, socket 最终选择不一样的协议(TCP/UDP等), 也就是说 socket 其实传输层协议簇的软件抽象.

    图片来自于网络


    1.1 流程概述

    在网络应用中, 通信的两个进程之间主要用的是客户端/服务器 (C/S)模式, 即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务. socket 最开始设计于 Unix, Unix/Linux 设计哲学是 "一切皆文件", 因此,我们可以像文件操作(open/read/write/close)一样来操作 socket.

    下图展示展示 C/S 之间如何使用 socket

    对于 TCP 服务端来说:

    • 创建 socket, 创建一个通信断点描述符, 但在进程中为未打开状态;
    • 绑定(bind) socket, 将 socket 绑定到某个地址以及端口上;
    • 监听(listen) socket, 用于表示该 socket 为一个被动链接(passive socket), 可以理解为 服务器的socket,需要客户端来主动连接了;
    • 接收客户端的请求(accept), 服务端通过该方法接收客户端的连接请求;
    • 读(read)/写(write)
    • 关闭 socket

    对于客户端来说就比较的简单: 创建一个socket, 然后连接服务器(connect), 完成对应的数据操作(读写), 完成后关闭(close);

    二 socket 相关接口

    2.1 创建 socket()

    int socket(int domain, int type, int protocol);
    

    socket 创建返回一个 socket 描述符, 跟文件描述符类似,后面的读写都需要依赖于这个描述符.

    2.1.1 参数-协议族(domain)

    协议族决定了使用的 socket 地址类型;

    • Unix 域 socket: AF_UNIXAF_LOCAL, 使用地址类型为: struct sockaddr_un
    struct sockaddr_un {
       sa_family_t sun_family;               /* AF_UNIX */
       char        sun_path[108];            /* Pathname */
    };
    
    • AF_INET, ipv4, 使用地址类型为: struct sockaddr_in
    struct sockaddr_in {
       sa_family_t    sin_family; /* address family: AF_INET */
       in_port_t      sin_port;   /* port in network byte order */
       struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
       uint32_t       s_addr;     /* address in network byte order */
    };
    
    
    • AF_INET6, ipv6, 使用地址类型为: sockaddr_in6
    struct sockaddr_in6 {
       sa_family_t     sin6_family;   /* AF_INET6 */
       in_port_t       sin6_port;     /* port number */
       uint32_t        sin6_flowinfo; /* IPv6 flow information */
       struct in6_addr sin6_addr;     /* IPv6 address */
       uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
    };
    
    struct in6_addr {
       unsigned char   s6_addr[16];   /* IPv6 address */
    };
    
    • 其他类型(AF_X25 / AF_AX25 / AF_NETLINK 等),其他类型目前还没有涉及到,不做过多讨论,感兴趣可以访问手册进行研究;

    2.1.2 参数-类型(type)

    表示 socket 通信的类型

    • SOCK_STREAM: 可靠的面向流服务或流套接字 (TCP)
    • SOCK_DGRAM: 数据报文服务或者数据报文套接字 (UDP)
    • SOCK_SEQPACKET:可靠的连续数据包服务
    • SOCK_RAW: 网络层之上自行指定运输层协议头,即原始套接字

    2.1.3 参数-协议类型(protocol)

    指定实际使用的传输协议, 传 0 表示根据前面的 domain 和 type 选择协议;

    2.2 其他函数略过

    其他的函数没有什么特别需要注意的地方,直接参考下面例子以及手册就能看懂


    三 示例程序-本地进程间通信

    当给 socket() 的协议族传入(AF_UNIXAF_LOCAL)时,可表示Unix域socket, 用于本地进程间通信.

    其中地址类型中的 sun_path是一个值得注意的地方,根据文档所描述,有三种类型

    + pathname: 文件类型,这种文件需要服务端与客户端对文件都有操作权限, 程序执行过程中,可以看到对应的文件.
    + unnamed
    + abstract: 抽象类型, 这种类型与 pathname 区别在于,给 sun_path 的第一个值为 ;

    3.1 客户端程序

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <string.h>
    #include <stddef.h>
    #include <unistd.h>
    
    static const char *SERVERNAME = "@servername";
    int main(int argc, char const *argv[])
    {
        int sockfd;
        struct sockaddr_un serverAddr;
        socklen_t len;
        char buffer[1024];
    
        sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
        if (sockfd < 0)
        {
            perror("init fail");
            exit(1);
        }
    
        memset(&serverAddr, 0, sizeof(serverAddr));
        serverAddr.sun_family = AF_UNIX;
        strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  
    
        serverAddr.sun_path[0] = '';
        len = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);
    
        if (connect(sockfd, (struct sockaddr *)&serverAddr, len) < 0)
        {
            perror("connect fail");
            exit(1);
        }
    
        while (fgets(buffer, sizeof(buffer), stdin) != NULL)
        {
            write(sockfd, buffer, strlen(buffer));
        }
    
        close(sockfd);
    
        return 0;
    }
    
    

    3.2 服务端程序

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <string.h>
    #include <stddef.h>
    #include <unistd.h>
    
    static const char *SERVERNAME = "@servername";
    
    int main(int argc, char const *argv[])
    {
        int sockfd;
        struct sockaddr_un serverAddr;
        struct sockaddr_un clientAddr;
        socklen_t clientLen;
        socklen_t serverLen;
    
        char buffer[1024];
        int connfd;
    
        if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        {
            perror("init fail");
            exit(1);
        }
        
        // 2. 设置对应地址,然后进行 connect
        memset(&serverAddr, 0, sizeof(serverAddr));
        serverAddr.sun_family = AF_UNIX;
        strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  
    
        // abstract & calculate length
        serverAddr.sun_path[0] = '';
        serverLen = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);
    
        if (bind(sockfd, (struct sockaddr *)&serverAddr, serverLen) < 0)
        {
            perror("bind fail");
            exit(1);
        }
    
        if (listen(sockfd, 20) < 0)
        {
            perror("listen fail");
            exit(1);
        }
    
        while (1)
        {
            // 阻塞到有客户端链接
            if ((connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientLen))  < 0) 
            {
                printf("accept fail 
    ");
                continue;
            }
            printf("accept success 
    ");
    
            while(1) 
            {  
                memset(buffer, 0, sizeof(buffer));
                
                int n = read(connfd, buffer, sizeof(buffer));  
                if (n < 0) {  
                    perror("read error");  
                    break;  
                } else if(n == 0) {  
                    printf("EOF
    ");  
                    break;  
                }  
                
                printf("received: %s", buffer);  
            }  
            close(connfd);
        }
    
        close(sockfd);
    
        return 0;
    }
    
    
    

    四 实例程序-IPV4(6)网络通信

    4.1 客户端程序

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    
    int main(int argc, char const *argv[])
    {
        // ipv4
        int sockfd;
        struct sockaddr_in addr_in;
        char buffer[1000];
        
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("init socket fail");
            exit(1);
        }
    
        bzero(&addr_in, sizeof(addr_in));    
        addr_in.sin_family = AF_INET;
        addr_in.sin_port = htons(9898);
        addr_in.sin_addr.s_addr = inet_addr("103.101.153.8");
    
        if ((connect(sockfd, (struct sockaddr *)&addr_in, sizeof(addr_in)))<0)
        {
            perror("connect fail");
            exit(1);
        }
    
        while (fgets(buffer, sizeof(buffer), stdin))
        {
            write(sockfd, buffer, strlen(buffer));
        }
    
        close(sockfd);
    
        return 0;
    }
    
    

    4.2 服务端程序

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h> 
    
    
    int main(int argc, char const *argv[])
    {
        int serfd, clifd;
        struct sockaddr_in seraddr_in, cliaddr_in;
        socklen_t clilen;
        char buffer[1000];
    
        if ((serfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("init socket fail");
            exit(1);
        }
    
        bzero(&seraddr_in, sizeof(seraddr_in));
        seraddr_in.sin_family = AF_INET;
        seraddr_in.sin_port = htons(9898);
        seraddr_in.sin_addr.s_addr = inet_addr("103.101.153.8");
    
        if (bind(serfd, (struct sockaddr *)&seraddr_in, sizeof(seraddr_in)) < 0)
        {
            perror("bind fail");
            exit(1);
        }
    
        if (listen(serfd, 20) < 0)
        {
            perror("listen fail");
            exit(1);
        }
    
        while (1)
        {
            if ((clifd = accept(serfd, (struct sockaddr *)&cliaddr_in, &clilen)) < 0)
            {
                perror("accept fail");
                continue;
            }
    
            while (1)
            {
                memset(buffer, 0, sizeof(buffer));
                int len = read(clifd, buffer, sizeof(buffer));
    
                if (len < 0 )
                {
                    perror("read fail");
                    break;
                }
    
                if (len == 0)
                {
                    perror("EOF");
                    break;
                }
    
                printf("receive: %s", buffer);
            }
    
            close(clifd);
        }
    
        close(serfd);
    
        return 0;
    }
    
    

    对比本地与网络通信,就是选择的协议族以及对应的地址不一样而已.

    参考链接:

  • 相关阅读:
    linux命令查询网站
    UTC(世界协调时间)时区和各个时区时间的转换
    dev-c++ 中写完源文件生成exe程序怎么避免运行闪退?
    numpy和time计时程序
    进化算法之粒子群算法和Matlab实现(多维)
    tf.config:GPU 的使用与分配(转载)
    限制TensorFlow只在CPU上运行的方法
    Python重要学习链接
    SpringCloud 之 Nacos 注册配置中心
    Jenkins 自动化部署入门详细教程
  • 原文地址:https://www.cnblogs.com/gaox97329498/p/11740987.html
Copyright © 2020-2023  润新知