【网络编程】TCP 通信

文章目录

  • 一、传输层协议TCP
  • 二、通信流程
    • [2.1 服务器端通信流程](#2.1 服务器端通信流程)
    • [2.2 客户端的通信流程](#2.2 客户端的通信流程)
  • 三、套接字代码
    • [3.1 server.c](#3.1 server.c)
    • [3.2 client.c](#3.2 client.c)
    • [3.3 编译运行](#3.3 编译运行)
    • [3.4 局限](#3.4 局限)

一、传输层协议TCP

特点: 面向连接的,安全的流式传输协议

  • 面向连接
    • 连接:三次握手,建立双向连接
    • 断开:四次挥手,双向断开
  • 安全的
    • 在通信过程中会对数据包进行校验,判断对方有没有接收到发送的数据
      • 如果数据没有被对方接收(数据丢失),就会对这个数据块进行重传
  • 流式传输
    • 接收和发送端处理的数据量可以是不对等的
      • 举例:
        • A 端每隔 5 s发送 4k 数据
        • B 端每隔 1 s接收 100 字节

二、通信流程

2.1 服务器端通信流程

在服务器端有两类文件描述符:

  • 监听的
    • 监测有没有新的客户端连接
    • 服务器端只需要一个就够了
  • 通信的
    • 负责和建立连接的客户端通信
    • 和多少个客户端建立连接,通信的文件描述符就有多少个

套接字中的文件描述符

  • 文件描述符对应内核中的两块内存
    • 一个读缓冲区
    • 一个写缓冲区
  • 通信的文件描述符和监听的文件描述符对应的内核中的内存结构是一样的
  • 监听的文件描述符
    • 读缓冲区
      • 客户端连接服务器,向服务器发送连接请求,这个请求数据进入到了服务器端的监听的文件描述符的读缓冲区
      • 只要是这个缓冲区中有数据意味着有新的客户端连接
  • 通信的文件描述符
    • 读缓冲区
      • 保存的是对端发送过来的数据,通过调用读函数将数据从内核中读出来
        • ssize_t read(int fd, void *buf, size_t count);
    • 写缓冲区
      • 调用发送数据的函数,要发送的数据被写入到套接字对应的写缓冲区
        • ssize_t write(int fd, const void *buf, size_t count);
      • 内核检测到写缓冲区中有数据,会将数据发送到网络的另一端,发送的数据会进到对端的通信文件描述符对应的读缓冲区

通信流程:

  1. 创建一个用于监听的套接字,这个套接字就是一个文件描述符
    • 类似于管道中的文件描述符,对应是内核中的内存,通过文件描述符操作就可以读写内核中的内存数据
c 复制代码
	int lfd = socket();
  1. 让监听的文件描述符和本地的IP+端口进行绑定,为了让客户端找到服务器
    • 绑定成功后,lfd 就可以监测到有没有客户端连接请求
c 复制代码
	bind();
  1. 给绑定成功的套接字设置监听
c 复制代码
	listen();
  1. 等待并接受客户端的连接,得到一个新的用于通信的文件描述符
c 复制代码
	int cfd = accept();
  1. 使用 accept 返回值对应的通信的文件描述符和客户端通信
  • 接收数据
c 复制代码
	read();
	recv();
  • 发送数据
c 复制代码
	write();
	send();
  1. 断开连接,关闭文件描述符
  • 关闭通信的文件描述符:就不能通信
  • 关闭监听的文件描述符:不饿能监测客户端连接
c 复制代码
	close();

2.2 客户端的通信流程

  • 在 TCP 客户端文件描述符只有一种:通信的文件描述符
  1. 创建用于通信的套接字 == (文件描述符)

    复制代码
     int fd = socket();
  2. 使用得到的通信的文件描述符连接服务器,通过服务器绑定的IP和端口进行连接

    复制代码
     connect();
  3. 连接成功之后,通信

  • 接收数据

    复制代码
      read();
      recv();
  • 发送数据

    复制代码
      write();
      send();
  1. 断开和服务器的连接

    复制代码
     close();

三、套接字代码

3.1 server.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


int main() {

    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1) {
        perror("socket");
        exit(0);
    }

    // 2. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET; // IPv4
    addr.sin_port = htons(8989); // 网络字节序
    addr.sin_addr.s_addr = INADDR_ANY; // 0地址
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret == -1) {
        perror("bind");
        exit(0);
    }

    // 3. 设置监听
    ret = listen(lfd, 128);
    if (ret == -1) {
        perror("listen");
        exit(0);
    }

    // 4. 等待并接受客户端连接
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
    if (cfd == -1) {
        perror("accept");
        exit(0);
    }

    // 5. 通信
    while (1) {
        
        // 接收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        int len = recv(cfd, buf, sizeof(buf), 0);
        if (len == 0) {
            printf("客户端已经断开连接...\n");
            break;
        } else if (len > 0) {
            printf("recv buf: %s\n", buf);

            // 回复数据
            send(cfd, buf, strlen(buf)+1, 0); // +1 '\0'
        } else {
            perror("recv");
            break;
        }
        
    }

    // 6. 断开连接
    close(cfd);
    close(lfd);

    return 0;
}

3.2 client.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>


int main() {

    // 1. 创建通信的套接字
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1) {
        perror("socket");
        exit(0);
    }

    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET; // IPv4
    addr.sin_port = htons(8989); // 网络字节序
    // 将 192.168.69.141 转换为大端的整型数
    inet_pton(AF_INET, "192.168.69.141", &addr.sin_addr.s_addr);
    int ret = connect(cfd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret == -1) {
        perror("connect");
        exit(0);
    }

    // 3. 通信
    int num = 0;
    while (1) {
        
        // 发送数据
        char buf[1024];
        sprintf(buf, "hello world, %d, ......", num++);
        send(cfd, buf, strlen(buf) + 1, 0);

        // 接收数据
        memset(buf, 0, sizeof(buf));
        int len = recv(cfd, buf, sizeof(buf), 0);
        if (len == 0) {
            printf("服务器已经断开连接...\n");
            break;
        } else if (len > 0) {
            printf("recv buf: %s\n", buf);
        } else {
            perror("recv");
            break;
        }

        sleep(1);
    }

    // 6. 断开连接
    close(cfd);

    return 0;
}

3.3 编译运行

  • 编译命令
bash 复制代码
gcc server.c -o s
gcc client.c -o c
  • 运行
bash 复制代码
./s
./c


3.4 局限

客户端并发只需要启动多个客户端即可,每启动一个客户端就得到一个进程,这每一个进程都会去连接服务器。

重点在服务器,服务器只能处理单个客户端的连接,如果要处理多个客户端的连接,那必须要有多线程或多进程。

阻塞函数acceptrecvsend 这三类函数有任意一个阻塞了,程序就会阻塞,再有额外的客户端连接程序也处理不了。

相关推荐
国科安芯3 小时前
高速CANFD收发器ASM1042在割草机器人轮毂电机通信系统中的适配性研究
网络·单片机·嵌入式硬件·性能优化·机器人·硬件工程
晓梦.4 小时前
IPSec 安全基础
服务器·网络·安全
btyzadt5 小时前
虚拟机蓝屏问题排查与解决
linux·运维·网络
佩佩(@ 。 @)6 小时前
网络编程-创建TCP协议服务器
服务器·网络·tcp/ip
G_H_S_3_6 小时前
【网络运维】Shell 脚本编程:while 循环与 until 循环
linux·运维·网络·shell
Kookoos8 小时前
System.IO.Pipelines 与“零拷贝”:在 .NET 打造高吞吐二进制 RPC
网络协议·rpc·.net·零拷贝·二进制协议·pipelines
云川之下8 小时前
【网络】使用 DNAT 进行负载均衡时,若未配置配套的 SNAT,回包失败
运维·网络·负载均衡
MC皮蛋侠客8 小时前
使用Python实现DLT645-2007智能电表协议
python·网络协议·tcp/ip·能源
2301_801673019 小时前
实验二 Cisco IOS Site-to-Site Pre-share Key
网络·智能路由器
As33100109 小时前
Manus AI 与多语言手写识别技术全解析
大数据·网络·人工智能