zoukankan      html  css  js  c++  java
  • c/c++:网络通信基础socket(网络设计模式、字节序、IP地址转换、sockaddr数据结构、套接字函数、TCP通信流程)

    目录

    1. 概念

    1.1 网络设计模式

      - B/S

      - C/S

    - IP和端口

    - OSI/ISO 网络分层模型

    2. 协议格式

    3. socket编程

    3.1 字节序

    - 接口转换函数

    3.2 IP地址转换

    3.3 sockaddr数据结构

    3.4 套接字函数

    4. TCP通信流程

    tcp 服务器server通信操作流程:

    tcp 客户端client通信操作流程:

     
    1. 概念
    1.1 网络设计模式
      - B/S

        - 客户端: 浏览器
        - 服务器: 服务器

        优势: 跨平台, 开发成本低

        劣势:

        ​    是的协议的固定的: http, https

        ​    不能处理大的数据
      - C/S

        - 客户端: 桌面应用程序
        - 服务器: 后台服务器

         优势: 可以处理大量的磁盘数据

         劣势: 如果跨平台, 需要重新开发, 成本高

     
    - IP和端口

          - IP地址

        - IPV4

          - 实际是一个32位的整形数 -> 本质 -> 4字节   int a;
          - 我们看的的不是这个整形数, 点分十进制字符串 -> 192.168.247.135
            - 分成了4份, 每份1字节, 8bit   ->  char , 最大值为 255  -> 最大取值: 255.255.255.255
          - IP可以有多少个  2^32^ - 1 个

        - IPV6

          - 实际是一个128位的整形数
          - xxx:xxx:xxx:xxx:xxx:xxx:xxx:xxx ,  分成了8分, 每份16位 -> 每一部分以16进制的方式表示
          - IP可以有多少个  2^128^ - 1 个
        - IP地址的作用:
          - 通过IP地址能够找到某一台主机

          - 端口

        - 在一个主机上运行着很多进程
        - 将数据发送到某台主机上的某个进程
        - 如果要进程网络通信, 可以让这个进程绑定一个端口
          - 通过这个端口就可以确定某个进程
        - 端口号: unsigned short int   ->   16位
          - 端口取值范围: 0 -65535    (2^16^)

     
    - OSI/ISO 网络分层模型

      > OSI(Open System Interconnect),即开放式系统互联。 一般都叫OSI参考模型,是ISO(国际标准化组织组织)在1985年研究的网络互联模型。

          - 七层模型

            底层 --------->上层
            物 数 网 传 会 表 应

                > - 物理层:

            >   - 物理层负责最后将信息编码成电流脉冲或其它信号用于网上传输

                > - 数据链路层:  

            >   - 数据链路层通过物理网络链路供数据传输。
            >   - 规定了0和1的分包形式,确定了网络数据包的形式;

                > - 网络层

            >   - 网络层负责在源和终点之间建立连接;
            >   - 此处需要确定计算机的位置,怎么确定?IPv4,IPv6

                > - 传输层

            >   - 传输层向高层提供可靠的端到端的网络数据流服务。
            >   - 每一个应用程序都会在网卡注册一个端口号,该层就是端口与端口的通信

                > - 会话层

            >   - 会话层建立、管理和终止表示层与实体之间的通信会话;
            >   - 建立一个连接(自动的手机信息、自动的网络寻址);

                > - 表示层:

            >   - 对应用层数据编码和转化, 确保以一个系统应用层发送的信息 可以被另一个系统应用层识别;
            >   - 可以理解为:解决不同系统之间的通信,eg:手机上的QQ和Windows上的QQ可以通信;

                > - 应用层:

            >   - 规定数据的传输协议

        四层模型

     

     
    2. 协议格式

     
    3. socket编程

        // 套接字通信分两部分:
         - 服务器端: 被动接受连接的角色, 不会主动发起连接
         - 客户端通信: 主动向服务器发起连接
         
         socket是一套通信接口, 下linux和windows都可以使用, 但是有细微差别

     
    3.1 字节序

        字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)

        - 概念

      - Little-Endian -> 主机字节序
        - 有一个数据: 0x12345678, 在内存中进行存储
        - 内存的低地址位存储数据低位字节, 内存高地址位存储数据的高位字节
      - Big-Endian -> 网络字节序
        - 内存的低地址位存储数据高位字节, 内存的高地址位存储数据的低位字节

        - 字节序举例

        // 使用16进制在内存中表示这两个数,即:
              - 0x12 34 56 78   -> 四字节   char -> 255 -> ff  
                - 0x11223344   -> 四字节

        - 小端

            低地址位 -------------> 高地址位
            0x78     0x56    0x34    0x12
            0x44    0x33    0x22    0x11

        - 大端

            低地址位 -------------> 高地址位
            0x12    0x34    0x56    0x78
            0x11    0x22    0x33    0x44

        - 接口转换函数

    BSD Socket提供了封装好的转换接口,方便程序员使用。

    从主机字节序(h)到网络字节序(n)的转换函数:htons、htonl;

    从网络字节序(n)到主机字节序(h)的转换函数:ntohs、ntohl。

        #include <arpa/inet.h>
          // shot int -> 4字节(64位)
          // h -> host
          // n -> network
          // s -> short
          // l -> long
          // xtoxs() -> 进行端口转换
          uint16_t htons(uint16_t hostshort);
              参数: 主机字节数的short型数值 -> 要转换的数(主机)
              返回值: 转换之后得到是数据 (网络字节序)
          uint16_t ntohs(uint16_t netshort);
         
          // long -> 8字节(64位)
          // xtoxl() -> 进行IP转换
          uint32_t htonl(uint32_t hostlong);
          uint32_t ntohl(uint32_t netlong);

     
    3.2 IP地址转换

        #include <arpa/inet.h>
        // 字符串: 192.168.1.100 (点分十进制字符串)
        // p -> 点分十进制字符串 IP
        // n -> network
        // 将主机字节序的 字符串IP -> 网络字节序的 整形数
        int inet_pton(int af, const char *src, void *dst);
            参数:
                - af: 地址族协议, ipv4, ipv6
                    ipv4: AF_INET, ipv6:AF_INET6
                - src: 点分十进制字符串 IP
                - dst: 传出参数, 执行一块内存的地址, 将转换得到的网络字节序的整形数存储到这块内存中
            返回值:
                -1: 失败
                1: 成功
                0: 查字典
         
        // 网络字节序的整形IP -> 点分十进制字符串 IP
        const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
            参数:
                - af: 地址族协议, ipv4, ipv6
                    ipv4: AF_INET, ipv6:AF_INET6
                - src: 指向要转换的 网络字节序的整形IP 地址
                - dst: 转换成功之后的 点分十进制字符串 存储的位置
                - size: 修饰的就是第三个参数 dst 对应的内存大小
            返回值:
                NULL: 失败
                非空指针, 指向第三个三种指针的内存: 成功

     
    3.3 sockaddr数据结构

        结构体 sockaddr、sockaddr_in用于网络通信

        结构体 sockaddr_un用于进程间通信

        结构体 sockaddr_in用于ipv6通信

        由于结构体sockaddr需要用指针偏移添加IP地址,这样很麻烦,在实际中我们使用sockaddr_in来添加端口号、IP地址。再强转成sockaddr类型,因为这2个结构体大小一样,后面的服务器—客户端程序会有具体体现。

        struct sockaddr {
            sa_family_t sa_family;    // 地址族协议, ipv4, ipv6
            char        sa_data[14];
        }
         
        struct sockaddr_in
        {
            sa_family_t sin_family;        IP选择AF_INET(ipv4)、AF_INET6(ipv6)
            in_port_t sin_port;         端口(网络字节序:htons() )
            struct in_addr sin_addr;    IP地址(网络字节序:inet_pton() )
            //预留空间:
            unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
                       sizeof (in_port_t) - sizeof (struct in_addr)];  
        };
         
        struct in_addr     
        {
            in_addr_t s_addr;        IP地址(网络字节序:inet_pton() )
        };  
         
        typedef unsigned short  uint16_t;
        typedef unsigned int    uint32_t;
        typedef uint16_t in_port_t;
        typedef uint32_t in_addr_t;
        typedef unsigned short int sa_family_t;
        #define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
         

     
    3.4 套接字函数

        #include <arpa/inet.h>    
        // 创建一个套接字
        int socket(int domain, int type, int protocol);
            参数:
                - domain: 地址族协议
                    AF_INET: ipv4
                    AF_INET6: ipv6
                    AF_UNIX, AF_LOCAL: 进行本地套接字通信(进程间通信)
                - type: 通信过程中使用的协议
                    SOCK_STREAM: 流式协议
                    SOCK_DGRAM: 报式协议
                - protocol: 一般写0
                    - SOCK_STREAM: 流式协议默认使用使用: tcp
                    - SOCK_DGRAM: 报式协议默认使用使用: udp
            返回值: 这个文件描述符操作的是内核缓冲区
                成功: 文件描述符 > 0
                失败: -1
         
        // 绑定函数 -> 将fd 和本地的 IP + Port进程绑定
        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
            参数:
                - sockfd: 通过socket函数得到的
                - addr: 需要将IP和Port初始化到这个结构体中
                       - addrlen: 第二个参数结构体占的内存大小
         
        // 设置监听
        int listen(int sockfd, int backlog);    // /proc/sys/net/core/somaxconn
            参数:
                - sockfd: 通过socket函数得到的
                - backlog: 已经连接成功, 但是还没有被处理的连接指定的数值不能大于/proc/sys/net/core/somaxconn 中存储的数据, 默认为128
         
        // 默认是一个阻塞函数, 阻塞等待客户端请求。请求到达, 接收客户端连接,得到一个用于通信的文件描述符
        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
            参数:
                - sockfd: 用于监听的文件描述符(套接字)
                    - addr: 传出参数, 记录了连接成功的客户端的IP和端口信息
                    - addrlen: 第二个参数结构体对应的内存大小
            返回值:
                - 成功: 通信的文件描述符 > 0
                - 失败: -1
                    
        // 客户端使用该函数连接服务器
        int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
            参数:
                - sockfd: 用于通信的文件描述符
                - addr: 客户端要连接的服务器的地址信息
                - addrlen: 第二个参数结构体占的内存大小
            返回值:
                连接成功: 0
                连接失败: -1
                    
         // 写数据
        ssize_t write(int fd, const void *buf, size_t count);
         // 读数据
        ssize_t read(int fd, void *buf, size_t count);

     
    4. TCP通信流程

        // tcp / udp-> 传输层协议
        tcp: 面向连接的, 安全的, 流式传输协议
            - 安全: 不会丢数据
        udp: 面向无连接的, 不安全, 报式传输协议

        tcp 服务器通信操作流程:

        1. 创建一个用于监听的套接字
            - 监听: 监听有客户的连接
            - 套接字: 这个套接字是一个文件描述符
        2. 将这个监听文件描述符和本地的IP和端口绑定  (IP和端口 == 服务器地址信息)
            - 客户端连接服务器的时候使用的就是这个IP和端口
        3. 设置监听, 监听的fd开始工作
        4. 阻塞等待, 当有客户端发起连接, 解除阻塞, 接受客户端的连接, 会得到一个用户通信的套接字(fd)
        5. 通信
            - 接收数据
            - 发送数据
        6. 通信结束, 断开连接

        tcp 客户端的通信流程:

        1. 创建一个用于通信的套接字 (fd)
        2. 连接服务器, 需要指定连接的服务器的 IP 和 Port
        3. 连接成功, 客户端可以直接和服务通信
            - 接收数据
            - 发送数据
        4. 断开连接

    tcp 服务器server通信操作流程:

        #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <string.h>
        #include <arpa/inet.h>
         
        int main()
        {
            // 1.创建用于监听的套接字
            int fd = socket(AF_INET, SOCK_STREAM, 0);
            if (fd == -1)
            {
                perror("socket");
                exit(0);
            }
         
            // 2.绑定
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;            //ipv4
            addr.sin_addr.s_addr = INADDR_ANY;    //获取IP的操作交给了内核
            // 上面的代码等价于:inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
            addr.sin_port = htons(8989);          //端口
            int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
            if (ret == -1)
            {
                perror("bind");
                exit(0);
            }
         
            // 3.设置监听
            int lis_ret = listen(fd, 100);
            if (lis_ret == -1)
            {
                perror("listen");
                exit(0);
            }
         
            // 4.等待被连接
            struct sockaddr_in addr_cli;
            int len = sizeof(addr_cli);
            int connfd = accept(fd, (struct sockaddr*)&addr_cli, &len);
            if (connfd == -1)
            {
                perror("accept");
                exit(0);
            }
         
            // 通讯
            while (1)
            {
                // 读数据
                char recvBuf[1024];
                read(connfd, recvBuf, sizeof(recvBuf));
                printf("recv buf : %s\n", recvBuf);
                // 写数据
                write(connfd, recvBuf, strlen(recvBuf));
            }
            
            //释放
            close(fd);
            close(connfd);
         
            return 0;
        }

     
    tcp 客户端client通信操作流程:

        #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <string.h>
        #include <arpa/inet.h>
         
        int main()
        {
            // 1. 创建用于通信的套接字
            int fd = socket(AF_INET, SOCK_STREAM, 0);
            if(fd == -1)
            {
                perror("socket");
                exit(0);
            }
         
            // 2. 连接服务器
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;  // ipv4
            addr.sin_port = htons(8989);   // 服务器监听的端口, 字节序应该是网络字节序
            inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
            int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
            if(ret == -1)
            {
                perror("connect");
                exit(0);
            }
         
         
            int i = 0;
            // 通信
            while(1)
            {
                // 读数据
                char recvBuf[1024];
                // 写数据
                sprintf(recvBuf, "data: %d\n", i++);
                write(fd, recvBuf, strlen(recvBuf));
                // 如果客户端没有发送数据, 默认阻塞
                read(fd, recvBuf, sizeof(recvBuf));
                printf("recv buf: %s\n", recvBuf);
                sleep(1);
            }
         
            // 释放资源
            close(fd);
         
            return 0;
        }

  • 相关阅读:
    高级排序
    递归
    Linked List
    中缀、后缀、前缀表达式
    队列(queue)
    栈(Stack)
    数组(Array)
    数据结构和算法
    常见排序
    开启
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/15641661.html
Copyright © 2011-2022 走看看