欢迎光临
我们一直在努力

木下~Linux系统编程之网络通信|TCP

一、网络参考模型

1. OSI 七层模型

ISO 制定的计算机通信互联标准,核心规则为每层使用下层提供的服务,并向上层提供服务,从上到下核心功能如下:

  • 应用层:为应用程序提供服务
  • 表示层:数据格式转化、数据加密解密
  • 会话层:建立、管理和维护会话
  • 传输层:建立、管理和维护端到端连接,通过端口号标识主机内进程
  • 网络层:IP 寻址及路由选择
  • 数据链路层:提供介质访问和链路管理
  • 物理层:物理介质的数据传输
  • 2. TCP/IP 四层模型(工业实际标准)

    是 OSI 模型的落地简化版本,与 OSI 对应关系及核心能力如下:

    TCP/IP 四层模型对应 OSI 模型层级核心协议核心功能
    应用层 应用层 / 表示层 / 会话层 HTTP、FTP、Telnet 处理应用业务逻辑
    传输层 传输层 TCP、UDP 端到端进程通信,端口标识进程
    网络层 网络层 IPv4/IPv6、ARP、RARP、ICMP IP 寻址、路由转发
    网络接口层 数据链路层 / 物理层 以太网协议、MPLS 物理介质传输、链路管理

    核心通信基础

    • 网络通信三要素:IP 地址(定位网络主机)+ 传输层协议(约定传输规则)+ 端口号(定位主机内进程),三元组可唯一标识网络中的一个进程。
    • 常用网络命令:
      • 查看 IP:Windows 用ipconfig;Linux 用ip addr/ifconfig
      • 连通性测试:ping 目标IP/域名(如ping www.baidu.com),TCP/UDP 通信的前提是网络可 ping 通。
    • Linux 文件类型补充:套接字文件为s类型,是 Linux 七大文件类型之一。

    二、核心基础概念

    1. IP 地址

    • 常用格式:点分十进制(如 192.168.10.150),存储分为主机字节序、网络字节序两种格式。
    • 地址分类:A 类 (1-127)、B 类 (128-191)、C 类 (192-223),D/E 类为特殊用途地址。
    • 核心转换函数:
      • in_addr_t inet_addr(const char *cp):点分十进制 IP → 网络字节序整型
      • char *inet_ntoa(struct in_addr in):网络字节序 IP → 点分十进制字符串

    2. 端口号

    • 本质:16 位无符号整数,取值范围0-65535,唯一标识主机内的一个网络进程。
    • 分类:
      • 知名端口:0-1023,固定分配给系统服务(22-SSH、21-FTP、80-HTTP)
      • 动态端口:1024-65535,用户程序可自由使用
    • 注意:网络传输前,端口号必须完成主机字节序与网络字节序的转换。

    3. 字节序

    • 大端序(网络字节序):内存低地址存数据高字节,高地址存数据低字节,网络传输固定使用大端序。
    • 小端序(主机字节序):内存低地址存数据低字节,高地址存数据高字节,多数主机本地存储默认使用小端序。
    • 核心转换函数:
      • htonl/htons:主机字节序 → 网络字节序(l=32 位长整型,s=16 位短整型)
      • ntohl/ntohs:网络字节序 → 主机字节序

    三、传输层核心协议(TCP vs UDP)

    1. 核心定义

    • TCP(传输控制协议):面向连接的可靠传输协议,通信前通过三次握手建立连接,传输过程中校验数据完整性,丢包自动重传,数据有序到达。
    • UDP(用户数据报协议):无连接的不可靠传输协议,无需建链,只管数据发送,不做传输校验和丢包重传,不保证数据有序到达。

    2. 核心区别速览

    特性TCPUDP
    连接性 面向连接,需三次握手建链 无连接,无需建链
    可靠性 可靠,丢包重传、数据有序 不可靠,不保证到达和有序
    套接字类型 SOCK_STREAM 流式套接字 SOCK_DGRAM 报式套接字
    传输效率 较低,有确认和重传开销 较高,无额外传输开销
    适用场景 大批量数据、对完整性要求高 小批量数据、对效率要求高

    四、Socket 套接字核心

  • 核心定义:Socket 是应用层与传输层之间的抽象层,将 TCP/IP 协议族的复杂操作封装为简易接口,供应用层调用实现网络进程通信;本质是通信两端的端点,实现客户端 – 服务端的端到端通信。
  • Socket 三要素:IP 地址 + 传输层协议 + 端口号,唯一标识网络中的一个通信端点。
  • Socket 核心类型:
    • 流式套接字(SOCK_STREAM):对应 TCP 协议,面向连接、可靠传输
    • 报式套接字(SOCK_DGRAM):对应 UDP 协议,无连接、不可靠传输
  • 五、Socket 核心 API 函数(Linux 环境)

    核心头文件:#include <sys/types.h>、#include <sys/socket.h>

    1. 通用函数:创建套接字

    int socket(int domain, int type, int protocol);

    • 功能:创建套接字,返回套接字文件描述符
    • 核心参数:
      • domain:协议族,常用AF_INET(IPv4 协议)
      • type:套接字类型,SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
      • protocol:传输层协议,填 0 则自动匹配 type 对应的协议
    • 返回值:成功返回非负套接字文件描述符,失败返回 – 1

    2. 服务端专属函数

    ① 绑定地址信息

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • 功能:服务端绑定 IP、端口、协议族到套接字,固定服务端通信地址
    • 核心参数:
      • sockfd:socket () 返回的监听套接字描述符
      • addr:地址结构体(常用sockaddr_in替代,内存布局一致),填充服务端 IP、端口、协议族
      • addrlen:addr 结构体的大小,用 sizeof 计算
    • 返回值:成功返回 0,失败返回 – 1
    • 补充:sockaddr_in核心成员:sin_family(协议族)、sin_port(端口号,网络字节序)、sin_addr.s_addr(IP 地址,网络字节序)
    ② 创建监听队列

    int listen(int sockfd, int backlog);

    • 功能:将主动套接字转为被动套接字,创建客户端连接请求的监听队列
    • 核心参数:
      • sockfd:socket () 返回的监听套接字描述符
      • backlog:监听队列的最大客户端连接数
    • 返回值:成功返回 0,失败返回 – 1
    ③ 接收客户端连接

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    • 功能:阻塞等待并接收客户端连接请求,连接成功后创建专属通信套接字
    • 核心参数:
      • sockfd:socket () 返回的监听套接字描述符
      • addr:出参,存储连接成功的客户端地址信息
      • addrlen:入参出参,传入 addr 结构体大小的地址
    • 返回值:成功返回新的非负通信套接字描述符(用于和该客户端收发数据),失败返回 – 1

    3. 客户端专属函数:发起连接

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • 功能:客户端主动向服务端发起连接请求,底层完成 TCP 三次握手
    • 核心参数:
      • sockfd:socket () 返回的客户端套接字描述符
      • addr:填充服务端 IP、端口、协议族的地址结构体
      • addrlen:addr 结构体的大小
    • 返回值:成功返回 0,失败返回 – 1

    4. 通用数据收发函数

    ① 发送数据

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    • 功能:向指定套接字发送数据
    • 核心参数:
      • sockfd:通信套接字描述符(服务端用 accept 返回值,客户端用 socket 返回值)
      • buf:待发送数据的缓冲区首地址
      • len:待发送数据的字节长度
      • flags:发送标志,常规填 0(阻塞发送)
    • 返回值:成功返回实际发送的字节数,失败返回 – 1
    • 补充:可用write()函数完全替代
    ② 接收数据

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    • 功能:从指定套接字接收数据
    • 核心参数:
      • sockfd:通信套接字描述符
      • buf:接收数据的缓冲区首地址
      • len:缓冲区最大可接收字节长度
      • flags:接收标志,常规填 0(无数据时阻塞等待)
    • 返回值:成功返回实际接收的字节数,失败返回 – 1
    • 补充:可用read()函数完全替代

    六、TCP 通信完整流程

    服务端流程

  • socket():创建服务端监听套接字
  • bind():绑定服务端 IP、端口、协议族到监听套接字
  • listen():开启监听,将套接字转为被动模式
  • accept():阻塞等待客户端连接,连接成功生成专属通信套接字
  • send()/recv():通过通信套接字与客户端进行双向数据收发(以进程示例)
  • 通信结束,关闭相关套接字文件描述符
  • #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <signal.h>

    #define PORT 8888
    #define IP "192.168.10.150"
    #define MAX 5

    //实现服务端和客户端的自由聊天 —- 服务端
    int main(int argc,char *argv[])
    {
    //1、创建套接字文件 socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
    perror("socket error\\r\\n");
    return 0;
    }

    //2、绑定服务端的三要素 bind
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;//使用ipv4的协议族
    addr.sin_port = htons(PORT);//端口号
    addr.sin_addr.s_addr = inet_addr(IP);//IP

    if(-1 == bind(sockfd, (struct sockaddr *)&addr,sizeof(addr)))
    {
    perror("bind error\\r\\n");
    return 0;
    }

    //3、创建监听队列 listen
    if(-1 == listen(sockfd,MAX))
    {
    perror("listen error\\r\\n");
    return 0;
    }

    printf("wait for connect …\\r\\n");
    //4、等待客户端连接 accept
    struct sockaddr_in c_addr={0};
    socklen_t addrlen = sizeof(c_addr);
    int fd = accept(sockfd, (struct sockaddr *)&c_addr, &addrlen);
    if(-1 == fd)
    {
    perror("accept error\\r\\n");
    return 0;
    }

    printf("client ip = %s\\r\\n",inet_ntoa(c_addr.sin_addr));
    printf("client port = %d\\r\\n",ntohs(c_addr.sin_port));

    //5、创建子进程 实现自由聊天
    pid_t pid = fork( );
    if(-1 == pid)
    {
    perror("fork error\\r\\n");
    return 0;
    }else if(0 == pid) //子进程 发送数据
    {
    char buf[20] = {0};
    while(1)
    {
    //获取需要发送的内容
    fgets(buf,sizeof(buf),stdin);

    //发送给客户端
    send(fd,buf,strlen(buf),0);

    //判断退出
    if(strcmp("quit\\n",buf) == 0)
    {
    kill(getppid(),2);
    break;
    }

    //清空buf
    memset(buf,0,sizeof(buf));
    }
    }else //父进程 接受数据
    {
    char buf[20] = {0};
    while(1)
    {
    //获取客户端的信息
    recv(fd, buf, sizeof(buf)-1, 0);

    //显示获取到的信息
    printf("receive:%s\\r\\n",buf);

    //判断退出
    if(strcmp("quit\\n",buf) == 0)
    {
    kill(pid,2);
    break;
    }

    //清空buf
    memset(buf,0,sizeof(buf));
    }
    }

    //6、关闭套接子文件 — 绑定的文件要关闭
    // 如果不关闭,自动关闭需要时间,在文件关闭前,无法再次绑定相同的IP 和端口号
    if(-1 == close(sockfd))
    {
    perror("close error\\r\\n");
    return 0;
    }

    return 0;
    }

    客户端流程

  • socket():创建客户端套接字
  • connect():向服务端发起连接请求,完成 TCP 三次握手建链
  • send()/recv():通过套接字与服务端进行双向数据收发(以线程示例)
  • 通信结束,关闭套接字文件描述符
  • #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <pthread.h>
    #include <stdlib.h>

    #define PORT 8888
    #define IP "192.168.10.150"
    #define MAX 5

    void *sock_recv(void *arg);

    //实现服务端和客户端的自由聊天 —- 客户端
    int main(int argc,char *argv[])
    {
    //1、创建套接字文件 socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
    perror("socket error\\r\\n");
    return 0;
    }

    //2、主动申请连接服务端
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;//使用ipv4的协议族
    addr.sin_port = htons(PORT);//端口号
    addr.sin_addr.s_addr = inet_addr(IP);//IP

    if(-1 == connect(sockfd, (struct sockaddr *)&addr,sizeof(addr)))
    {
    perror("connect error\\r\\n");
    return 0;
    }
    printf("connect successful!!!\\r\\n");

    //3、创建线程 接受信息
    pthread_t tid = 0;
    pthread_create(&tid, NULL, sock_recv,(void *)(long)sockfd);

    //4、循环发送信息
    char buf[20] = {0};
    while(1)
    {
    //获取需要发送的内容
    fgets(buf,sizeof(buf),stdin);

    //发送给客户端
    write(sockfd,buf,strlen(buf));

    //判断退出
    if(strcmp("quit\\n",buf) == 0)
    {
    break;
    }

    //清空buf
    memset(buf,0,sizeof(buf));
    }

    //6、关闭套接子文件
    if(-1 == close(sockfd))
    {
    perror("close error\\r\\n");
    return 0;
    }

    return 0;
    }

    //创建线程函数 — 接受服务端的信息
    void *sock_recv(void *arg)
    {
    int fd = (int)(long)arg;
    char buf[20] = {0};
    while(1)
    {
    //获取客户端的信息
    read(fd, buf, sizeof(buf)-1);

    //显示获取到的信息
    printf("receive:%s\\r\\n",buf);

    //判断退出
    if(strcmp("quit\\n",buf) == 0)
    {
    break;
    }

    //清空buf
    memset(buf,0,sizeof(buf));
    }
    exit(0);
    }

    赞(0)
    未经允许不得转载:171主机测评 » 木下~Linux系统编程之网络通信|TCP
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址