TCP/IP网络编程 第五章 TCP传输的数据边界

36 阅读4分钟

client 如何完整接收server数据

上一章节,client 直接通过一次read 获取server 返回值,但是在最初你并不知道server 给你传递多长字节的数据;因此就必须在应用层协商数据接收的边界;比如收到char Q 的时候就结束本次TCP连接;

第一版应用层协议

设计如下的应用层协议

image.png

举个例子: 假设client 输入如下

3
12
24
36
+

表示的意思是client 要传递3个number, 分别是12,24,36要对他们进行加法运算,也就是要server 计算12+24+36=

需要注意以下2点:

  1. client 传递的oprand count 是1字节,传递的运算符也是1字节,这两个都不需要转换字节序
  2. operand 是4字节,这个需要传输的时候转换为网络字节序,在本地进行计算的时候要转换成主机序列

这是我server.c 代码

需要注意的点: 63~68 行 对operand 转换成本机字节序计算,因为网络字节序统一是大端序,我的mac是小端序,测试命令 sysctl hw.byteorder输出是1234

#include <arpa/inet.h>  // For network address conversion functions
#include <stdio.h>      // For standard input and output functions
#include <stdlib.h>     // For general utility functions like exit()
#include <string.h>     // For memory manipulation functions like memset()
#include <sys/socket.h> // For socket-related functions and structures
#include <unistd.h>     // For close() function

#define BUF_SIZE 1024
#define OPSZ 4 // Operand size (bytes per operand)

void error_handling(char *message);
int calculate(int opnd_cnt, int opnds[], char operator);

int main(int argc, char *argv[]) {
  int serv_sock;        // Server socket descriptor
  int clnt_sock;        // Client socket descriptor
  char opmsg[BUF_SIZE]; // Store operation message
  int result, opnd_cnt, i;
  struct sockaddr_in serv_addr; // Server address structure
  struct sockaddr_in clnt_addr; // Client address structure
  socklen_t clnt_addr_size;     // Size of client address structure

  if (argc != 2) {
    printf("Usage: %s <port>\n", argv[0]);
    exit(1);
  }

  // Create a socket for the server
  serv_sock = socket(PF_INET, SOCK_STREAM, 0);
  if (serv_sock == -1)
    error_handling("socket() error");

  // Initialize the server address structure
  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET; // IPv4
  serv_addr.sin_addr.s_addr =
      htonl(INADDR_ANY); // Accept connections from any IP
  serv_addr.sin_port =
      htons(atoi(argv[1])); // Port number in network byte order

  // Bind the socket
  if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    error_handling("bind() error");

  // Start listening for incoming connections
  if (listen(serv_sock, 5) == -1)
    error_handling("listen() error");

  // Accept a client connection
  clnt_addr_size = sizeof(clnt_addr);
  clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
  if (clnt_sock == -1)
    error_handling("accept() error");

  // Read the operand count (1 byte from client)
  uint8_t opnd_cnt_byte;
  read(clnt_sock, &opnd_cnt_byte, 1);
  opnd_cnt = (int)opnd_cnt_byte; // Convert to integer
  printf("Operand count received: %d\n", opnd_cnt);

  // Read operands (convert from network byte order to host byte order)
  int opnds[opnd_cnt];
  for (i = 0; i < opnd_cnt; i++) {
    int net_operand;
    read(clnt_sock, &net_operand, OPSZ); // Read 4 bytes (network byte order)
    opnds[i] = ntohl(net_operand);       // Convert to host byte order
    printf("Operand %d: %d\n", i + 1, opnds[i]);
  }

  // Read the operator (1 byte from client)
  char operator;
  read(clnt_sock, &operator, 1);
  printf("Operator received: %c\n", operator);

  // Perform the calculation
  result = calculate(opnd_cnt, opnds, operator);
  // result = ntohl(result); // Convert to network byte order
  printf("Calculation result: %d\n", result);

  // Convert the result to network byte order and send it back to client
  int net_result = htonl(result);
  write(clnt_sock, &net_result, sizeof(net_result));

  // Close sockets
  close(clnt_sock);
  close(serv_sock);

  return 0;
}

void error_handling(char *message) {
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

int calculate(int opnd_cnt, int opnds[], char operator) {
  int result = opnds[0];
  for (int i = 1; i < opnd_cnt; i++) {
    switch (operator) {
    case '+':
      result += opnds[i];
      break;
    case '-':
      result -= opnds[i];
      break;
    case '*':
      result *= opnds[i];
      break;
    case '/':
      if (opnds[i] != 0)
        result /= opnds[i];
      else {
        printf("Division by zero error!\n");
        exit(1);
      }
      break;
    default:
      printf("Unknown operator: %c\n", operator);
      exit(1);
    }
  }
  return result;
}

这是我的client.c 代码

关键点是42~48 行把本地字节序的4字节operand 转换成网络字节序 也就是大端序

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4

void error_handling(char *message);

int main(int argc, char *argv[]) {
  int sock;
  char opmsg[BUF_SIZE];
  int result, opnd_cnt, i;
  struct sockaddr_in serv_adr;

  if (argc != 3) {
    printf("Usage : %s <IP> <port>\n", argv[0]);
    exit(1);
  }

  sock = socket(PF_INET, SOCK_STREAM, 0);
  if (sock == -1)
    error_handling("socket() error");

  memset(&serv_adr, 0, sizeof(serv_adr));
  serv_adr.sin_family = AF_INET;
  serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
  serv_adr.sin_port = htons(atoi(argv[2]));

  if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
    error_handling("connect() error!");
  else
    puts("Connected...........");

  fputs("Operand count: ", stdout);
  scanf("%d", &opnd_cnt);
  opmsg[0] = (char)opnd_cnt;

  for (i = 0; i < opnd_cnt; i++) {
    int oprand;
    printf("Operand %d: ", i + 1);
    scanf("%d", &oprand);
    oprand = htonl(oprand);
    memcpy(&opmsg[i * OPSZ + 1], &oprand, OPSZ);
  }
  fgetc(stdin);
  fputs("Operator: ", stdout);
  scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]);
  write(sock, opmsg, opnd_cnt * OPSZ + 2);
  read(sock, &result, RLT_SIZE);

  result = ntohl(result);
  printf("Operation result: %d \n", result);
  close(sock);
  return 0;
}

void error_handling(char *message) {
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

演示结果

启动server

image.png

启动client

image.png

server输出结果

image.png