Linux——网络(tcp)

文章目录


目录

文章目录

前言

一、TCP逻辑

[1. 面向连接](#1. 面向连接)

三次握手(建立连接)

四次挥手(关闭连接)

[2. 可靠性](#2. 可靠性)

[3. 流量控制](#3. 流量控制)

[4. 拥塞控制](#4. 拥塞控制)

[5. 基于字节流](#5. 基于字节流)

[6. 全双工通信](#6. 全双工通信)

[7. 状态机](#7. 状态机)

[8. TCP头部结构](#8. TCP头部结构)

[9. TCP的应用场景](#9. TCP的应用场景)

二、编写tcp代码函数

[1. Socket 创建与配置](#1. Socket 创建与配置)

socket()

setsockopt()

[2. 绑定与监听](#2. 绑定与监听)

bind()

listen()

[3. 连接与接受连接](#3. 连接与接受连接)

connect()

accept()

[4. 数据发送与接收](#4. 数据发送与接收)

send()

recv()

[5. 关闭连接](#5. 关闭连接)

close()

shutdown()

[6. 地址转换与解析](#6. 地址转换与解析)

inet_pton()

inet_ntop()

[7. 错误处理](#7. 错误处理)

perror()

strerror()

三、基本客服端和服务端

1、服务端

2、客户端

总结


前言

Linux------网络基础(1)-CSDN博客

Linux------网络(udp)-CSDN博客

  1. TCP协议概述:简要介绍TCP协议的基本特性,包括连接建立、数据传输、流量控制、拥塞控制等。

  2. Linux网络编程基础:介绍Linux下的Socket编程接口,以及如何使用这些接口进行TCP通信。

  3. TCP服务器与客户端的实现:通过实际的代码示例,展示如何编写一个简单的TCP服务器和客户端程序。

  4. TCP协议的性能优化:探讨如何通过调整TCP参数、使用非阻塞I/O、多线程/多进程等技术来提升TCP应用的性能。

  5. 常见问题与调试技巧:分享一些在实际开发中可能遇到的TCP相关问题,以及如何使用工具进行网络调试和故障排查。


一、TCP逻辑

1. 面向连接

TCP是一种面向连接的协议,这意味着在数据传输之前,通信双方需要先建立一个连接。连接的建立和关闭是通过三次握手和四次挥手来完成的。

三次握手(建立连接)
  1. SYN:客户端向服务器发送一个SYN(同步)报文,表示请求建立连接。

  2. SYN-ACK:服务器收到SYN后,回复一个SYN-ACK(同步-确认)报文,表示同意建立连接。

  3. ACK:客户端收到SYN-ACK后,发送一个ACK(确认)报文,连接正式建立。

四次挥手(关闭连接)
  1. FIN:主动关闭方(客户端或服务器)发送一个FIN(结束)报文,表示希望关闭连接。

  2. ACK:被动关闭方收到FIN后,回复一个ACK报文,表示确认收到关闭请求。

  3. FIN:被动关闭方完成数据发送后,发送一个FIN报文,表示自己也准备关闭连接。

  4. ACK:主动关闭方收到FIN后,回复一个ACK报文,连接正式关闭。


2. 可靠性

TCP通过以下机制确保数据的可靠传输:

  • 序列号与确认机制:每个TCP报文都包含一个序列号(Sequence Number),接收方通过发送确认号(Acknowledgment Number)来确认已收到的数据。如果发送方未收到确认,则会重传数据。

  • 超时重传:如果发送方在一定时间内未收到确认,则会重新发送数据。

  • 数据校验:TCP使用校验和(Checksum)来检测数据在传输过程中是否损坏。


3. 流量控制

TCP通过滑动窗口机制实现流量控制,防止发送方发送数据过快导致接收方缓冲区溢出。

  • 接收窗口:接收方通过TCP头部中的窗口字段告知发送方自己当前可接收的数据量。

  • 动态调整:接收方可以根据自身缓冲区的可用空间动态调整窗口大小。


4. 拥塞控制

TCP通过拥塞控制算法避免网络拥塞,常见的算法包括:

  • 慢启动(Slow Start):初始时发送方以较小的窗口发送数据,随后指数增长。

  • 拥塞避免(Congestion Avoidance):当窗口达到阈值后,发送方以线性方式增加窗口大小。

  • 快速重传(Fast Retransmit):当发送方收到三个重复的ACK时,立即重传丢失的报文。

  • 快速恢复(Fast Recovery):在快速重传后,发送方进入快速恢复阶段,避免窗口大幅减小。


5. 基于字节流

TCP是一种基于字节流的协议,这意味着:

  • 无消息边界:TCP将数据视为连续的字节流,不保留发送方写入的数据边界。例如,发送方发送两次数据("Hello"和"World"),接收方可能一次性收到"HelloWorld"。

  • 粘包与拆包:由于TCP的字节流特性,接收方需要自己处理数据的边界问题(如通过长度字段或特殊分隔符)。


6. 全双工通信

TCP支持全双工通信,即通信双方可以同时发送和接收数据。每个TCP连接由两个独立的流组成:

  • 一个流用于从客户端到服务器的数据传输。

  • 另一个流用于从服务器到客户端的数据传输。


7. 状态机

TCP连接的生命周期由一个状态机管理,常见的状态包括:

  • LISTEN:服务器等待客户端连接。

  • SYN_SENT:客户端已发送SYN,等待服务器响应。

  • SYN_RECEIVED:服务器已收到SYN并发送SYN-ACK,等待客户端确认。

  • ESTABLISHED:连接已建立,可以传输数据。

  • FIN_WAIT_1 / FIN_WAIT_2:主动关闭方等待对方的FIN或ACK。

  • CLOSE_WAIT:被动关闭方等待应用程序关闭连接。

  • TIME_WAIT:连接关闭后,等待可能出现的延迟报文。


8. TCP头部结构

TCP头部包含以下关键字段:

  • 源端口和目的端口:标识通信的应用程序。

  • 序列号和确认号:用于数据排序和确认。

  • 标志位:如SYN、ACK、FIN等,用于控制连接状态。

  • 窗口大小:用于流量控制。

  • 校验和:用于数据完整性校验。


9. TCP的应用场景

TCP适用于需要可靠传输的场景,例如:

  • Web浏览(HTTP/HTTPS)

  • 文件传输(FTP)

  • 电子邮件(SMTP/POP3/IMAP)

  • 远程登录(SSH/Telnet)


二、编写tcp代码函数

1. Socket 创建与配置

socket()
  • 功能:创建一个新的套接字(socket),用于网络通信。

  • 原型:

    cpp 复制代码
    int socket(int domain, int type, int protocol);
  • 参数:

    • domain:协议族,如 AF_INET(IPv4)或 AF_INET6(IPv6)。

    • type:套接字类型,如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)。

    • protocol:通常为 0,表示默认协议。

  • 返回值:成功返回套接字文件描述符,失败返回 -1

setsockopt()
  • 功能:设置套接字选项,如重用地址、调整缓冲区大小等。

  • 原型:

    cpp 复制代码
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  • 常用选项:

    • SO_REUSEADDR:允许重用本地地址。

    • SO_RCVBUF / SO_SNDBUF:调整接收/发送缓冲区大小。


2. 绑定与监听

bind()
  • 功能:将套接字绑定到本地地址和端口。

  • 原型:

    cpp 复制代码
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 参数:

    • sockfd:套接字文件描述符。

    • addr:指向 struct sockaddr 的指针,包含地址和端口信息。

    • addrlen:地址结构体的长度。

listen()
  • 功能:将套接字设置为监听模式,等待客户端连接。

  • 原型:

    cpp 复制代码
    int listen(int sockfd, int backlog);
  • 参数:

    • sockfd:套接字文件描述符。

    • backlog:等待连接队列的最大长度。


3. 连接与接受连接

connect()
  • 功能:客户端使用该函数连接到服务器。

  • 原型:

    cpp 复制代码
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 参数:

    • sockfd:套接字文件描述符。

    • addr:指向服务器地址结构体的指针。

    • addrlen:地址结构体的长度。

accept()
  • 功能:服务器接受客户端的连接请求,返回一个新的套接字用于通信。

  • 原型:

    cpp 复制代码
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 参数:

    • sockfd:监听套接字文件描述符。

    • addr:用于存储客户端地址信息。

    • addrlen:地址结构体的长度。


4. 数据发送与接收

send()
  • 功能:通过已连接的套接字发送数据。

  • 原型:

    cpp 复制代码
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • 参数:

    • sockfd:套接字文件描述符。

    • buf:指向要发送数据的缓冲区。

    • len:数据长度。

    • flags:标志位,通常为 0

recv()
  • 功能:通过已连接的套接字接收数据。

  • 原型:

    cpp 复制代码
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 参数:

    • sockfd:套接字文件描述符。

    • buf:指向接收数据的缓冲区。

    • len:缓冲区长度。

    • flags:标志位,通常为 0


5. 关闭连接

close()
  • 功能:关闭套接字,释放资源。

  • 原型:

    cpp 复制代码
    int close(int sockfd);
  • 参数:

    • sockfd:套接字文件描述符。
shutdown()
  • 功能:优雅地关闭连接,可以选择关闭读、写或读写通道。

  • 原型:

    cpp 复制代码
    int shutdown(int sockfd, int how);
  • 参数:

    • sockfd:套接字文件描述符。

    • how:关闭方式,如 SHUT_RD(关闭读)、SHUT_WR(关闭写)、SHUT_RDWR(关闭读写)。


6. 地址转换与解析

inet_pton()
  • 功能:将点分十进制的IP地址转换为二进制格式。

  • 原型:

    cpp 复制代码
    int inet_pton(int af, const char *src, void *dst);
  • 参数:

    • af:地址族,如 AF_INETAF_INET6

    • src:点分十进制字符串。

    • dst:存储二进制结果的缓冲区。

inet_ntop()
  • 功能:将二进制格式的IP地址转换为点分十进制字符串。

  • 原型:

    cpp 复制代码
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    getaddrinfo()
  • 功能:解析主机名和服务名,返回地址信息。

  • 原型:

    cpp 复制代码
    int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);

7. 错误处理

perror()
  • 功能:打印与 errno 相关的错误信息。

  • 原型:

    cpp 复制代码
    void perror(const char *s);
strerror()
  • 功能:将错误码转换为可读的字符串。

  • 原型:

    cpp 复制代码
    char *strerror(int errnum);

三、基本客服端和服务端

1、服务端

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    char *response = "Hello from server";

    // 1. 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 4. 接受客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("Accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 读取客户端数据
    int valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Client says: %s\n", buffer);

    // 6. 发送响应给客户端
    send(new_socket, response, strlen(response), 0);
    printf("Response sent to client\n");

    // 7. 关闭连接
    close(new_socket);
    close(server_fd);
    return 0;
}

2、客户端

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    char *message = "Hello from client";

    // 1. 创建套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 2. 将IP地址从字符串转换为二进制格式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 3. 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection failed");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 4. 发送数据到服务器
    send(sock, message, strlen(message), 0);
    printf("Message sent to server\n");

    // 5. 接收服务器的响应
    int valread = read(sock, buffer, BUFFER_SIZE);
    printf("Server says: %s\n", buffer);

    // 6. 关闭连接
    close(sock);
    return 0;
}

总结

TCP网络编程是构建高性能、高可靠性网络应用的基础。通过理解TCP协议的工作原理、掌握Linux Socket API的使用方法,并实践编写客户端和服务器程序,我们可以逐步掌握网络编程的核心技能。希望本篇博客能为你的学习之旅提供帮助,期待你在网络编程的世界中探索更多可能性!

相关推荐
Mr_Xuhhh5 小时前
进程间通信
android·java·服务器·开发语言·数据库
zwhSunday5 小时前
线程概念、操作
linux·线程
_zwy5 小时前
【Linux权限】—— 于虚拟殿堂,轻拨密钥启华章
linux·运维·c++·深度学习·神经网络
别致的影分身6 小时前
Linux网络 应用层协议 HTTP
运维·网络·网络协议·http
半夏云流6 小时前
CMake常用命令指南(CMakeList.txt)
linux·makefile·cmake
ccnnlxc6 小时前
日志收集Day007
运维·jenkins
鲁子狄7 小时前
[笔记] 极狐GitLab实例 : 手动备份步骤总结
linux·运维·笔记·ubuntu·centos·gitlab
Xam_d_LM7 小时前
【Linux】列出所有连接的 WiFi 网络的密码
linux·服务器·网络
机器视觉小小测试员7 小时前
工业相机常用词语解释
运维·ui·自动化·工业相机