【TCP/IP网络编程-尹圣雨-学习笔记】1. 服务器编程、客户端编程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

C1Linux

  1. 服务器编程

    • 步骤

      1. 创建套接字 socket()

        • 函数原型

          int socket (int __domain, int __type, int __protocol) __THROW;

        • 参数

          __domain: 协议族 PF_INET / PF_INET6

          __type: 协议族下数据传输类型 SOCK_STREAM / SOCK_DGRAM

          __protocal: TCP/UDP 协议只需填0。如果 domain 和 type 还无法确认具体的协议则此项选择具体的协议。

        • 返回值

          成功 文件描述符 失败 -1

        • 常用

          int serv_tcp_sock = socket(PF_INET, SOCK_STREAM, 0); // TCP  ipv4 
          int serv_udp_sock = socket(PF_INET, SOCK_DGRAM, 0); // udp , ipv4
          
      2. 设置服务器 ip+port ==> bind()

        • 函数原型

          bind (int __fd, const struct sockaddr * __addr, socklen_t __len)

        • 参数

          __fd: 要绑定的sockfd。一般为 socket() 返回值。

          __addr: 服务器ip+port。sockaddr 类型。

          socklen_t: sizeof(__addr)。

          1. __addr 结构说明

            • sockaddr 结构

              struct sockaddr
              {
                  sa_family_t sin_family; // 地址族
                  char sa_data[14]; // 地址信息
              }; 
              
            • sockaddr_in 结构

              struct sockaddr_in
              {
                  sa_family_t sin_family; // 地址族 AF_INET / AF_INET6
                  uint16_t sin_port; // 16位TCP/UDP端口号
                  struct in_addr sin_addr; //32位IPv4地址
                  char sin_zero[8]; // 不使用
              };
              struct in_addr
              {
                  uint32_t s_addr;
              };
              // 以上内容都以网络字节序保存
              // 主机字节序 <==> 网络字节序 见下文
              
            • sockaddr 与 sockaddr_in 的关系

              1. sockaddr 用于保存地址信息可以是ipv4 or ipv6

              2. sockaddr_in 专用于表示 ipv4 地址。

              3. bind() 中 _addr 要求 sockaddr 类型 因此需要定义 sockaddr_in 的ipv4地址后要强制转换为sockaddr类型。

          2. 主机字节序与网络字节序转换

            • 主机字节序每台机器都有自己的字节序表示和存储数据。分为大端序小端序。大端序高位字节存放到低位地址小端序高位字节存放到高位地址。常见的主机字节序都是小端序。

              • 一段代码测试主机字节序是大端序还是小端序

                // TODO: 测试大小端
                
            • 网络字节序在网络中传输数据需要保证双方使用的字节序一样。因此规定数据在网络中传输时统一使用大端序网络字节序即大端序。

            • 数据在传输前都要经过转换吗

              只需要向sockaddr_in 结构体中传入大端序的ip+port, 其他情况无需考虑字节序问题这个过程是自动的。

            • 转换函数

              1. 整型转换通常用于整型的port与ip如INADDR_ANY

                • 函数原型

                  uint32_t htonl (uint32_t __hostlong);

                  uint16_t htons (uint16_t __hostshort);

                  uint32_t ntohl (uint32_t __netlong);

                  uint16_t ntohs (uint16_t __netshort);

                • 说明

                  h: host 表示主机序

                  n: network, 表示网络序

                  s: short, 表示16bit整型通常用于port

                  l: long, 表示32bit整型通常用于ip

              2. 字符串转整型通常用于字符串型的ip地址转换

                • 函数原型1

                  in_addr_t inet_addr (const char * string);

                • 说明1

                  成功 返回32位大端序整数值

                  失败返回 INADDR_NONE

                  参数字符串ip地址

                • 使用1

                  sockaddr_in serv_addr;
                  serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
                  
                • 函数原型2

                  int inet_aton (const char * string, struct in_addr * addr);

                • 说明2

                  成功 1 失败 0

                  string: ip地址

                  成功后将转换后的IP地址直接存放在 sockaddr_in 结构体中的 in_addr 结构体变量。

                • 使用2

                  sockaddr_in serv_addr;
                  if (!inet_aton(addr, &addr_inet.sin_addr)) {//error
                  }
                  
              3. 辅助函数 atoi

                • 函数原型

                  int atoi (const char *__nptr)

                • 说明

                  atoi 是标准库函数用于将字符型数字转换为数字

                • 使用场景

                  客户端网络编程中接收用户输入 port 将其转换为整型后转网络字节序。

                • 使用

                  sockaddr_in serv_addr;
                  serv_addr.sin_port = htons(atoi("6666"));
                  
        • 返回值

          成功 0 失败 -1

        • 常用

          sockaddr_in serv_addr;
          memset(&serv_addr, 0, sizeof(serv_addr)); // 清0主要清的是最后8字节的sin_zero参数
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // htonl : 转ip
          serv_addr.sin_port = htons(SERV_PORT); // htons: 转port
          bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr));
          
      3. 开启监听状态 & 设置最大监听队列 listen()

        • 函数原型

          int listen (int __fd, int __n);

        • 参数

          __fd: 服务端 socket fd

          __n: 最大监听队列

        • 返回值

          成功 0 失败 -1

      4. 等待接受客户端连接 accept() 被动

        • 函数原型

          int accept (int fd, struct sockaddr * addr, socklen_t * addr_len);

        • 参数

          fd: 处于监听状态的 socket fd

          addr: fd 绑定的地址 sockaddr 类型

          addr_len: 地址大小指针类型。

        • 返回值

          成功: 连接到的客户端文件描述符 失败 -1

        • 常用

          int clnt_sock;
          sockaddr_in clnt_addr;
          socklen_t clnt_addr_size = sizeof(clnt_addr);
          clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size);
          
      5. 读写数据 read() / write()

        • 函数原型

          ssize_t write (int __fd, const void *__buf, size_t __n);

          ssize_t read (int __fd, void *__buf, size_t __nbytes)

        • 参数

          fd: 打开的文件描述符。

          buf: read – 读取后存放的缓冲区 write – 要写的内容。

          n/nbytes: read – 读取的最大字节数 write – 要写的字节数。

        • 返回值

          成功 接收/写入的字节数。 失败 -1

      6. 关闭连接释放内存 close()

    • 完整代码示例

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>
      #include <iostream>
      #define SERV_PORT 6666
      #define MAX_LISTEN_QUEUE 5
      int main(int argc, char* argv[])
      {
          // 1. socket 创建套接字
          int serv_sock;
          serv_sock = socket(PF_INET, SOCK_STREAM, 0);
          if (serv_sock == -1) 
          {
              std::cout << "socket() error!" << std::endl;
          }
          // 2. 设置服务器 ip + port ==> bind() 绑定
          sockaddr_in serv_addr;
          memset(&serv_addr, 0, sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
          serv_addr.sin_port = htons(SERV_PORT);
          if (bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 
          {
              std::cout << "bind() error!" << std::endl;
          }
          // 3. 开启监听状态 listen()  设置最大监听队列
          if (listen(serv_sock, MAX_LISTEN_QUEUE) == -1)
          {
              std::cout << "listen() error!" << std::endl;
          }
          std::cout << "server start successfully! IP: " << ntohl(serv_addr.sin_addr.s_addr) << " Port: " << SERV_PORT << std::endl;
          // 4. 等待接受客户端连接 accept()
          int clnt_sock;
          sockaddr_in clnt_addr;
          socklen_t clnt_addr_size = sizeof(clnt_addr);
          clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size);
          if (clnt_sock == -1)
          {
              std::cout << "accept() error!" << std::endl;
          }
          // 5. 读写数据 -- 向客户机写入 hello clnt_addr
          char msg[] = "hello socket!";
          write(clnt_sock, msg, sizeof(msg));
          // 6. 释放内存
          close(clnt_sock);
          close(serv_sock);
          return 0;
      }
      
  2. 客户端编程

    • 步骤

      1. 创建套接字 socket()

      2. 连接服务器 connect() 主动

        • 函数原型

          int connect (int __fd, const struct sockaddr * __addr, socklen_t __len);

        • 参数

          fd: 客户端socket

          __addr: 服务器地址

          __len: 服务器地址长度

        • 返回值

          成功0 失败-1

        • 常用

          // 填充服务器地址信息
          sockaddr_in serv_addr;
          memset(&serv_addr, 0, sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
          serv_addr.sin_port = htons(atoi("6666"));
          // 连接服务器
          connect(sock, (sockaddr*)&serv_addr, sizeof(serv_addr));
          
      3. 读写数据

      4. 释放内存

    • 完整代码示例

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>
      #include <iostream>
      int main(int argc, char* argv[])
      {
          if (argc != 3)
          {
              std::cout << "Usage: " << argv[0] << "<IP> <port>" << std::endl;
          }
          // 1. 创建套接字 socket()
          int sock = socket(PF_INET, SOCK_STREAM, 0);
          if (sock == -1)
          {
              std::cout << "socket() error!" << std::endl;
          }
          // 2. 设置要连接服务器的 ip + port, 连接 ==> connect
          sockaddr_in serv_addr;
          memset(&serv_addr, 0, sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
          serv_addr.sin_port = htons(atoi(argv[2]));
          if (connect(sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
          {
              std::cout << "connect() error!" << std::endl;
              return 1;
          }
          std::cout << "Connect Successfully!" << std::endl;
          // 3. 读取数据
          char msg[30];
          int read_len = read(sock, msg, sizeof(msg)-1);
          if (read_len == -1)
          {
              std::cout << "read() error!" << std::endl;
          }
          std::cout << "Message from server: " << msg << std::endl;
          // 4. 释放内存
          close(sock);
          return 0;
      }
      
  3. Cookies

    • 面向连接的套接字SOCK_STREAM与面向消息的套接字SOCK_DGRAM

    • 路由器和交换机

    • 网络接口卡NIC

    • 127.0.0.1回环地址

    • 文件

      1. 句柄windows与文件描述符linux

        • windows中区分 socket 操作和文件操作 Linux中不区分

        • linux 标准输入输出及错误的文件描述符

          文件描述符对象
          0标准输入
          1标准输出
          2标准错误
      2. 文件操作

        1. 头文件 <unistd.h>

        2. 打开文件

          • 函数原型

            int open(const char* path, int flag);

          • 文件打开模式

            打开模式含义
            O_CREAT必要时创建文件
            O_TRUNC删除全部现有数据
            O_APPEND维持现有数据追加新数据到末尾
            O_RDONLY只读打开
            O_WRONLY只写打开
            O_RDWR读写打开
          • 返回值

            成功 文件描述符 失败 -1

        3. 关闭文件

          • 函数原型

            int close(int fd);

          • 返回值

            成功 0 失败 -1

    • 以_t为后缀的数据类型

      size_t: unsigned int

      ssize_t: signed int

      这些都是元数据类型

C1Windows

  1. 服务器编程
  2. 客户端编程
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 服务器