嵌入式学习之系统编程(十)网络编程之TCP传输控制协议

news2025/6/6 12:22:56

目录

一、网络模型

1、服务器/客户端模型

2、C/S与B/S区别

3、P2P模型

二、TCP(传输控制协议)

(一)TCP概述

(二)TCP的特征(面问高频问题)

1、有链接

三次握手:建立连接;四次挥手:断开连接

2、可靠传输(因为有链接)

3、TCP流式套接字

4、扩展:黏包问题

(三)TCP(相关编程)

1、TCP服务端主干函数及调用顺序

(1)socket函数

(2)bind函数

(3)listen函数

(4)accept函数

(5)recv函数

(7)close函数

2、TCP客户端主干函数及调用顺序

connect函数

3、代码示例

(1)服务端

(2)客户端

(四)练习:复制文件(收发结构体)

1、stat函数

2、服务端代码

3、客户端代码

三、远程登录工具

1、telnet  (一般局域网远程登录,不加密(明文))

2、ssh2(远程登录,加密)

一、网络模型

1、服务器/客户端模型

(1)C/S:client server

(2)B/S:browser server

(3)P2P:peer to peer

2、C/S与B/S区别

(1)客户端不同:B/S模型中B为通用客户端,C/S模型中C为专用客户端;

(2)使用协议不同

                B/S模型:用HTTP(超文本传输协议)

                C/S模型:自定义(自由度更高)

(3)功能角度

                B/S模型:功能设计相对简单

                C/S模型:功能设计复杂

(4)资源角度

                B/S模型:所有资源都由服务端发过来

                C/S模型:资源可认为是本地的(事先下好的)

3、P2P模型

下载软件、文件、视频可用(既是客户端也干了服务器的事(提供服务))

二、TCP(传输控制协议)

(一)TCP概述

TCP 是 TCPIIP 体系中非常复杂的一个协议。下面介绍TCP最主要的特点。

(1)TCP是面向连接的运输层协议。这就是说,应用程序在使用TCP协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的TCP连接。这就是说,应用进程之间的通信好像在“打电话”:通话前要先拨号建立连接,通话结束后要挂机释放连接。

(2)每一条 TCP连接只能有两个端点(endpoint),每一条 TCP连接只能是点对点的(一对一)。

(3)TCP提供可靠交付的服务。也就是说,通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达。

(4)TCP提供全双工通信。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。在发送时应用程序在把数据传送给 TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。

(5)面向字节流。TCP 中的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和 TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。TCP并不知道所传送的字节流的含义。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的TCP共10个数据块,但接收方的 TCP可能只用了4个数据块就把收到的字节流交付给了上层的应用程序)。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接收方的应用程序必须有能力识别收到的字节流,把它还原成有意义的应用层数据。

注:三次握手的第一次封装在accept()函数和connect()函数中;

        四次挥手双方都调用close()函数,双方都把close()函数走完才认为四次挥手结束。

(二)TCP的特征(面问高频问题)

1、有链接

先确定链路,建立连接(在整个通信过程中一直保持,除非双方断开连接)

扩展:

三次握手:建立连接;四次挥手:断开连接

三次握手:

第一次握手 TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT 同步已发送状态

第二次握手 TCP服务器收到请求报文后,如果同意连接,则会向客户端发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了 SYN-RCVD 同步收到状态

第三次握手 TCP客户端收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED已建立连接状态 触发三次握手。

四次挥手:

第一次挥手 客户端发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态

第二次挥手 服务器端接收到连接释放报文后,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT 关闭等待状态

第三次挥手 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成。

 1-3三次握手(建立连接),4-7处理数据(收发),7-10四次挥手(断开连接)

第8步:ACK1022(第一种情况:之前请求的数据未发完,先应答,说明收到断开,套接字进入半连接状态;第二种情况:双方数据量没那么大,服务器没什么要发的,第八步是有可能被省略的)

SYN:请求连接

ACK:应答

编号:协商双方起始编号

2、可靠传输(因为有链接)

(1)应答机制

(2)超时重传

3、TCP流式套接字

(1)流式套接字:全双工(既能收也能发)(因为是双缓冲区(发送缓冲区和接收缓冲区))

(2)流式套接字示意图:

第一种:recv(100)一把收完(拿走)

第二种:recv(3)一个一个取,不会丢

(3)流式套接字(两个)特性:

        数据本身是连续的(无边界);

        有顺序的(数据会有序到达)(发和收的次序可不对应)

4、扩展:黏包问题

(1)(双方发数据事先未约定好)数据发送过去接收方没办法正常按发送方那边拆开包;

(2)原因:数据无边界;

(3)解决方案

        a.设计分隔符(加分隔符(自己设定))

        b.固定大小(典型代表:struct(结构体))

        c.自定义协议

 eg:AA CC 04 1 2 3 4 CRC1 CRC2 BB DD

        AA CC:开头标志

        BB DD:结尾标志

        04:标识数据长度(自定义)

        1 2 3 4:正文4个字节

(三)TCP(相关编程)

1、TCP服务端主干函数及调用顺序

server调用顺序:socket()-->bind()--->listen()-->accept()-->recv()-->write()-->close()

(1)socket函数

函数原型int socket(int domain, int type, int protocol);
功能:程序向内核提出创建一个基于内存的套接字描述符
参数:domain  地址族,PF_INET == AF_INET ==>互联网程序
          PF_UNIX == AF_UNIX ==>单机程序
          type    套接字类型:
          SOCK_STREAM  流式套接字 ===》TCP   
          SOCK_DGRAM   用户数据报套接字===>UDP
          SOCK_RAW     原始套接字  ===》IP
          protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id;
               失败  -1;

注:监听套接字

eg:int listfd = socket(AF_INET,SOCK_STREAM,0);

监听套接字只有一个,功能与3次握手有关

(2)bind函数

函数原型int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
功能:如果该函数在服务器端调用,则表示将参数1相关的文件描述符文件与参数

           2 指定的接口地址关联,用于从该接口接受数据。

         如果该函数在客户端调用,则表示要将数据从参数1所在的描述符中取出并从

         参数2所在的接口设备上发送出去。
注意:如果是客户端,则该函数可以省略,由默认接口发送数据。

参数:sockfd 之前通过socket函数创建的文件描述符,套接字id
           my_addr 是物理接口的结构体指针。表示该接口的信息。

           socklen_t addrlen: 参数2 的长度。

  struct sockaddr      通用地址结构
  {
  u_short sa_family;  地址族
  char sa_data[14];   地址信息
  };

  转换成网络地址结构如下:
  struct _sockaddr_in    ///网络地址结构
  {
  u_short    sin_family; 地址族
  u_short    sin_port;   ///地址端口
  struct in_addr  sin_addr;   ///地址IP
  char    sin_zero[8]; 占位
  };

  struct in_addr
  {
  in_addr_t s_addr;
  }

返回值:成功 0;失败  -1

(3)listen函数

函数原型int listen(int sockfd, int backlog);
功能:在参数1所在的套接字id上监听等待链接。
参数:sockfd  套接字id
          backlog 允许链接的个数。
返回值:成功  0;失败  -1

(4)accept函数

函数原型int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从已经监听到的队列中取出有效的客户端链接并接入到当前程序。
参数:sockfd 套接字id
           addr  如果该值为NULL ,表示不论客户端是谁都接入。
                    如果要获取客户端信息,则事先定义变量并传入变量地址,函数执行

                        完毕将会将客户端 信息存储到该变量中。
          addrlen: 参数2的长度,如果参数2为NULL,则该值也为NULL;
                           如果参数不是NULL,&len;
                          一定要写成len = sizeof(struct sockaddr);

返回值:成功 返回一个用于通信的新套接字id;从该代码之后所有通信都基于该id
               失败  -1;

注:通信套接字

a.在服务端方面来说,通信套接字代表客户端;

b.有几个客户端就有几个通信套接字;

(5)recv函数

函数原型ssize_t recv(int sockfd, void *buf, size_t len,int flags);
功能:从指定的sockfd套接字中以flags方式获取长度为len字节的数据到指定的buff

           内存中。
参数:sockfd:如果服务器则是accept的返回值的新fd
                         如果客户端则是socket的返回值旧fd
          buff 用来存储数据的本地内存,一般是数组或者动态内存。
          len 要获取的数据长度
          flags 获取数据的方式,0 表示阻塞接受。

返回值: >0  实际收到的字节数

                ==0  表示对方断开

                <0 (-1,出错)

(6)send函数

函数原型int send(int sockfd, const void *buf, size_t len, int flags);
功能:从buf所在的内存中获取长度为len的数据以flags方式写入到sockfd对应的

          套接字中。
参数:sockfd:如果是服务器则是accept的返回值新fd
                        如果是客户端则是sockfd的返回值旧fd
           msg 要发送的消息
           len 要发送的消息长度
           flags 消息的发送方式。
返回值:成功  发送的字符长度;失败  -1

(7)close函数

close(conn); //关闭通信套接字(走的4次挥手),代表不同客户端;

close(listfd); //关闭监听套接字(新用户找不到端口和IP。之前连上的不影响)

2、TCP客户端主干函数及调用顺序

client调用顺序:socket()-->connect()-->send()-->recv()-->close()

connect函数

函数原型int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数固定有客户端使用,表示从当前主机向目标主机发起链接请求。
参数:sockfd 本地socket创建的套接子id
         addr 远程目标主机的地址信息。
         addrlen: 参数2的长度。
返回值:成功 0;失败 -1

3、代码示例

(1)服务端

#include <arpa/inet.h>          // 包含IP地址处理函数(如inet_addr)
#include <netinet/in.h>         // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h>         // 包含IP协议相关定义
#include <stdio.h>              // 标准输入输出头文件
#include <stdlib.h>             // 标准库头文件(含内存分配、exit等)
#include <string.h>             // 字符串处理头文件
#include <sys/socket.h>         // 套接字编程核心头文件
#include <sys/types.h>          // 数据类型定义头文件
#include <time.h>               // 时间处理头文件
#include <unistd.h>             // 包含Unix系统函数(如close、sleep)

typedef struct sockaddr*(SA);    // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写

int main(int argc, char** argv)  // 主函数,argc为参数个数,argv为参数数组
{
    // 监听套接字
    int listfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP监听套接字(AF_INET:IPv4,SOCK_STREAM:TCP协议,0:默认协议)
    if (-1 == listfd)  // 检查套接字创建是否失败
    {
        perror("socket");  // 打印错误信息(显示"socket"及具体错误)
        return 1;          // 失败则退出程序,返回1表示错误
    }

    // man 7 ip (注释:提示查看ip协议手册)
    struct sockaddr_in ser, cli;  // 定义服务器(ser)和客户端(cli)的网络地址结构体
    bzero(&ser, sizeof(ser));     // 清空服务器地址结构体(置零)
    bzero(&cli, sizeof(cli));     // 清空客户端地址结构体(置零)

    ser.sin_family = AF_INET;     // 设置地址族为IPv4
    ser.sin_port = htons(50000);  // 设置端口号(htons:主机字节序转网络字节序)
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");  // 设置IP地址(本地回环地址,inet_addr:字符串转网络字节序IP)

    int ret = bind(listfd, (SA)&ser, sizeof(ser));  // 将套接字绑定到指定地址和端口
    if (-1 == ret)  // 检查绑定是否失败
    {
        perror("bind");  // 打印绑定错误信息
        return 1;        // 失败则退出
    }

    // 三次握手的排队数 (注释:提示backlog参数含义,即未完成连接队列的最大长度)
    listen(listfd, 3);  // 开始监听,允许最多3个未完成连接排队

    socklen_t len = sizeof(cli);  // 初始化客户端地址结构体长度(用于accept函数)

    // 通信套接字
    int conn = accept(listfd, (SA)&cli, &len);  // 接受客户端连接,返回新的通信套接字conn
    if (-1 == conn)  // 检查连接接受是否失败
    {
        perror("accept");  // 打印接受连接错误信息
        return 1;          // 失败则退出
    }

    while (1)  // 循环处理客户端通信(死循环,直到客户端断开或出错)
    {
        char buf[256] = {0};  // 定义接收缓冲区,初始化为0
        // ret >0 实际收到的字节数;==0 表示对方断开;-1 出错。(注释:recv返回值含义)
        ret = recv(conn, buf, sizeof(buf), 0);  // 从客户端接收数据(conn:通信套接字,buf:缓冲区,sizeof(buf):最大接收字节数,0:默认参数)
        if(ret <=0)  // 处理接收失败或客户端断开
        {
            break;  // 退出循环
        }
        printf("cli:%s\n", buf);  // 打印客户端发送的内容

        time_t tm;  // 定义时间变量
        time(&tm);  // 获取当前时间(存储到tm中)
        struct tm * info = localtime(&tm);  // 将时间转换为本地时间结构体(包含时分秒等信息)

        sprintf(buf, "%s %d:%d:%d\n", buf, info->tm_hour, info->tm_min, info->tm_sec);  // 将客户端消息与当前时间拼接成新字符串,存入buf
        send(conn, buf, strlen(buf), 0);  // 将拼接后的字符串发送回客户端(conn:通信套接字,buf:数据,strlen(buf):数据长度,0:默认参数)
    }

    close(conn);  // 关闭通信套接字
    close(listfd);  // 关闭监听套接字
    // system("pause");  // 注释:Windows下暂停程序,Linux无需此操作
    return 0;  // 程序正常退出,返回0
}

(2)客户端

#include <arpa/inet.h>          // 包含IP地址处理函数(如inet_addr)
#include <netinet/in.h>         // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h>         // 包含IP协议相关定义
#include <stdio.h>              // 标准输入输出头文件
#include <stdlib.h>             // 标准库头文件(含内存分配、exit等)
#include <string.h>             // 字符串处理头文件
#include <sys/socket.h>         // 套接字编程核心头文件
#include <sys/types.h>          // 数据类型定义头文件
#include <time.h>               // 时间处理头文件(注:代码中未实际使用time.h相关功能,可能为冗余包含)
#include <unistd.h>             // 包含Unix系统函数(如close、sleep)

typedef struct sockaddr*(SA);    // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写

int main(int argc, char** argv)  // 主函数,argc为参数个数,argv为参数数组
{
    int conn = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字(AF_INET:IPv4,SOCK_STREAM:TCP协议,0:默认协议)
    if (-1 == conn)  // 检查套接字创建是否失败
    {
        perror("socket");  // 打印错误信息(显示"socket"及具体错误)
        return 1;          // 失败则退出程序,返回1表示错误
    }

    // man 7 ip (注释:提示查看ip协议手册)
    struct sockaddr_in ser, cli;  // 定义服务器(ser)和客户端(cli)的网络地址结构体(注:cli未实际使用)
    bzero(&ser, sizeof(ser));     // 清空服务器地址结构体(置零)
    bzero(&cli, sizeof(cli));     // 清空客户端地址结构体(置零,注:此处冗余,因未使用cli)

    ser.sin_family = AF_INET;     // 设置地址族为IPv4
    ser.sin_port = htons(50000);  // 设置端口号(htons:主机字节序转网络字节序,对应服务器端口)
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");  // 设置服务器IP地址(本地回环地址)

    int ret = connect(conn, (SA)&ser, sizeof(ser));  // 向服务器发起连接请求(conn:套接字,&ser:服务器地址,sizeof(ser):地址长度)
    if (-1 == ret)  // 检查连接是否失败
    {
        perror("connect");  // 打印连接错误信息
        return 1;        // 失败则退出
    }

    while (1)  // 循环发送/接收数据(死循环,直到服务器断开或出错)
    {
        char buf[256] = {0};  // 定义缓冲区,初始化为0
        strcpy(buf, "this is tcp test");  // 向缓冲区写入固定字符串(作为发送内容)
        send(conn, buf, strlen(buf), 0);  // 向服务器发送数据(conn:套接字,buf:数据,strlen(buf):数据长度,0:默认参数)

        ret = recv(conn, buf, sizeof(buf), 0);  // 接收服务器返回的数据
        if (ret <= 0)  // 处理接收失败或服务器断开(ret=0:对方断开;ret=-1:出错)
        {
            break;  // 退出循环
        }
        printf("ser:%s", buf);  // 打印服务器返回的内容
        fflush(stdout);          // 刷新输出缓冲区(确保立即打印,避免缓存延迟)
        sleep(1);                // 休眠1秒(控制发送频率,避免高频请求)
    }

    close(conn);  // 关闭套接字,释放资源
    // system("pause");  // 注释:Windows下暂停程序,Linux无需此操作
    return 0;  // 程序正常退出,返回0
}

(四)练习:复制文件(收发结构体)

1、stat函数

包含头文件及函数原型:

功能:得到文件大小

2、服务端代码

#include <arpa/inet.h>          // 包含IP地址处理函数(如inet_addr)
#include <fcntl.h>              // 包含文件操作标志(如O_WRONLY、O_CREAT)
#include <netinet/in.h>         // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h>         // 包含IP协议相关定义
#include <stdio.h>              // 标准输入输出头文件
#include <stdlib.h>             // 标准库头文件(含内存分配、exit等)
#include <string.h>             // 字符串处理头文件
#include <sys/socket.h>         // 套接字编程核心头文件
#include <sys/types.h>          // 数据类型定义头文件
#include <time.h>               // 时间处理头文件(注:代码中未实际使用,可能为冗余包含)
#include <unistd.h>             // 包含Unix系统函数(如close、write)

typedef struct sockaddr*(SA);    // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写

// 定义数据传输包结构体PACK
typedef struct {
    char filename[256];          // 文件名(存储接收文件的名称)
    char buf[1024];              // 数据缓冲区(存储文件数据块)
    int buf_len;                 // 本次缓冲区有效数据长度
    int total_len;               // 文件总长度
} PACK;

int main(int argc, char** argv)  // 主函数,argc为参数个数,argv为参数数组
{
    // 创建监听套接字
    int listfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP监听套接字(IPv4,TCP协议,默认参数)
    if (-1 == listfd) {  // 检查套接字创建失败
        perror("socket");  // 打印错误信息
        return 1;
    }

    // man 7 ip (注释:提示查看ip协议手册)
    struct sockaddr_in ser, cli;  // 定义服务器(ser)和客户端(cli)的网络地址结构体
    bzero(&ser, sizeof(ser));     // 清空服务器地址结构体(置零)
    bzero(&cli, sizeof(cli));     // 清空客户端地址结构体(置零)

    ser.sin_family = AF_INET;     // 设置地址族为IPv4
    ser.sin_port = htons(50000);  // 设置端口号(主机字节序转网络字节序)
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");  // 设置本地回环IP地址

    int ret = bind(listfd, (SA)&ser, sizeof(ser));  // 绑定套接字到指定地址和端口
    if (-1 == ret) {  // 检查绑定失败
        perror("bind"); 
        return 1;
    }

    // 三次握手排队数(backlog,未完成连接队列最大长度)
    listen(listfd, 3);  // 开始监听,允许最多3个未完成连接

    socklen_t len = sizeof(cli);  // 初始化客户端地址结构体长度(用于accept)

    // 接受客户端连接,获取通信套接字
    int conn = accept(listfd, (SA)&cli, &len);  
    if (-1 == conn) {  // 检查连接接受失败
        perror("accept"); 
        return 1;
    }

    int first_flag = 0;       // 标志位:0表示未接收文件头,1表示已接收
    int fd = -1;              // 文件描述符(用于写入文件)
    int current_size = 0;     // 已接收数据总长度
    int total_size = 0;       // 文件总长度

    while (1) {  // 循环接收文件数据,直到传输完成
        PACK pack;  // 定义PACK结构体变量(存储单个数据包)
        bzero(&pack, sizeof(pack));  // 清空结构体(置零)
        ret = recv(conn, &pack, sizeof(pack), 0);  // 接收客户端发送的数据包

        if (0 == first_flag) {  // 首次接收(处理文件头信息)
            first_flag = 1;  // 标记已接收文件头
            // 打开文件(O_WRONLY:只写,O_TRUNC:清空文件(若存在),O_CREAT:不存在则创建,权限0666)
            fd = open(pack.filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);  
            if (-1 == fd) {  // 检查文件打开失败
                perror("open"); 
                return 1;
            }
            total_size = pack.total_len;  // 获取文件总长度
        }

        if (0 == pack.buf_len) {  // 若本次数据长度为0(客户端发送结束标志)
            break;  // 退出循环
        }

        write(fd, pack.buf, pack.buf_len);  // 将数据写入文件
        current_size += pack.buf_len;  // 累计已接收数据长度
        printf("%d/%d\n", current_size, total_size);  // 打印接收进度(已接收/总长度)

        bzero(&pack, sizeof(pack));  // 清空结构体(注:此处可能冗余,因后续未再使用pack)
        strcpy(pack.buf, "go on");  // 向pack.buf写入"go on"(注:代码中注释了发送操作,实际未发送)
        // send(conn, &pack, sizeof(pack), 0);  // 注释:原计划发送确认信息,但代码中未执行
    }

    close(conn);  // 关闭通信套接字
    close(listfd);  // 关闭监听套接字
    close(fd);  // 关闭文件
    // system("pause");  // 注释:Windows下暂停程序,Linux无需此操作
    return 0;
}

3、客户端代码

#include <arpa/inet.h>          // 包含IP地址处理函数(如inet_addr)
#include <fcntl.h>              // 包含文件操作标志(如O_RDONLY)
#include <netinet/in.h>         // 包含网络地址结构体(如sockaddr_in)
#include <netinet/ip.h>         // 包含IP协议相关定义
#include <stdio.h>              // 标准输入输出头文件
#include <stdlib.h>             // 标准库头文件(含内存分配、exit等)
#include <string.h>             // 字符串处理头文件
#include <sys/socket.h>         // 套接字编程核心头文件
#include <sys/stat.h>           // 包含文件状态结构体stat(用于获取文件属性)
#include <sys/types.h>          // 数据类型定义头文件(重复包含,可优化)
#include <time.h>               // 时间处理头文件(注:代码中未实际使用,冗余包含)
#include <unistd.h>             // 包含Unix系统函数(如close、read、usleep)

typedef struct sockaddr*(SA);    // 定义SA为指向sockaddr结构体的指针类型,简化后续代码书写

// 定义数据传输包结构体PACK(与服务器端一致)
typedef struct {
    char filename[256];          // 文件名(存储待发送的文件名)
    char buf[1024];              // 数据缓冲区(存储文件数据块)
    int buf_len;                 // 本次缓冲区有效数据长度
    int total_len;               // 文件总长度
} PACK;

int main(int argc, char** argv)  // 主函数,argc为参数个数,argv为参数数组
{
    int conn = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字(IPv4,TCP协议,默认参数)
    if (-1 == conn) {  // 检查套接字创建失败
        perror("socket");  // 打印错误信息
        return 1;
    }

    // man 7 ip (注释:提示查看ip协议手册)
    struct sockaddr_in ser, cli;  // 定义服务器(ser)和客户端(cli)的网络地址结构体(cli未实际使用)
    bzero(&ser, sizeof(ser));     // 清空服务器地址结构体(置零)
    bzero(&cli, sizeof(cli));     // 清空客户端地址结构体(置零,冗余,因未使用cli)

    ser.sin_family = AF_INET;     // 设置地址族为IPv4
    ser.sin_port = htons(50000);  // 设置服务器端口号(主机字节序转网络字节序)
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");  // 设置服务器IP地址(本地回环地址)

    int ret = connect(conn, (SA)&ser, sizeof(ser));  // 向服务器发起连接请求
    if (-1 == ret) {  // 检查连接失败
        perror("connect"); 
        return 1;
    }

    PACK pack;  // 定义PACK结构体变量(用于封装文件数据)
    strcpy(pack.filename, "1.png");  // 设置待发送的文件名(路径可写全,此处为相对路径或绝对路径,代码中实际使用绝对路径/home/linux/1.png)

    struct stat st;  // 定义文件状态结构体(用于获取文件大小)
    ret = stat("/home/linux/1.png", &st);  // 获取文件状态信息(通过绝对路径获取)
    if (-1 == ret) {  // 检查stat调用失败(如文件不存在)
        perror("stat"); 
        return 1;
    }
    pack.total_len = st.st_size;  // 存储文件总长度到PACK结构体

    int fd = open("/home/linux/1.png", O_RDONLY);  // 以只读方式打开文件
    if (-1 == fd) {  // 检查文件打开失败
        perror("open"); 
        return 1;
    }

    while (1) {  // 循环读取文件数据并发送,直到文件读取完毕
        pack.buf_len = read(fd, pack.buf, sizeof(pack.buf));  // 从文件读取数据到缓冲区,获取实际读取长度
        send(conn, &pack, sizeof(pack), 0);  // 向服务器发送整个PACK结构体(包含文件名、数据、长度等)

        if (pack.buf_len <= 0) {  // 处理读取结束(buf_len=0)或出错(buf_len=-1)
            break;  // 退出循环
        }

        bzero(&pack, sizeof(pack));  // 清空PACK结构体(注:发送后清空,后续循环重新填充,逻辑合理)
        // recv(conn, &pack, sizeof(pack), 0);  // 注释:原计划接收服务器确认,但代码中未执行
        usleep(1000 * 10);  // 休眠10毫秒(控制发送频率,避免网络拥塞)
    }

    close(conn);  // 关闭套接字
    close(fd);    // 关闭文件
    // system("pause");  // 注释:Windows下暂停程序,Linux无需此操作
    return 0;
}

三、远程登录工具

1、telnet  (一般局域网远程登录,不加密(明文))

                远程登录工具,默认都是系统安装。
                使用格式: telnet ip地址  端口
                 eg: telnet 192.168.1.1  8888
       注意:如果没有写端口,则默认登录23 号端口。

2、ssh2(远程登录,加密)

3、注:

sudo ufw status //查看防火墙状态

sudo ufw disable //关闭防火墙
sudo apt-get install openssh-server openssh-client //安装工具命令

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【react+antd+vite】优雅的引入svg和阿里巴巴图标

1.安装相关包 由于是vite项目&#xff0c;要安装插件来帮助svg文件引入进来&#xff0c;否则会失败 npm下载包 npm i vite-plugin-svgr vite.config.ts文件内&#xff1a; import svgr from "vite-plugin-svgr"; //... export default defineConfig({plugins: …

3D动画在微信小程序的实现方法

微信小程序支持通过多种方式实现3D动画效果&#xff0c;主要包括使用CSS3、WebGL及第三方库。以下为具体方法&#xff1a; 一. 使用CSS3 Transform实现基础3D动画详解 CSS3的transform属性提供了强大的2D/3D变换功能&#xff0c;通过简单的代码就能实现复杂的视觉效果。在小程…

计算机网络 | 1.2 计算机网络体系结构与参考模型

计算机网络体系结构与参考模型 目录 计算机网络体系结构与参考模型 【思维导图】 1、计算机的分层结构 1、为什么要分层&#xff1f; 2、什么是计算机网络体系结构 2、计算机网络协议、接口和服务 1&#xff09;协议&#xff1a; 2&#xff09;接口&#xff1a; 3…

网心云 OEC/OECT 笔记(1) 拆机刷入Armbian固件

目录 网心云 OEC/OECT 笔记(1) 拆机刷入Armbian固件网心云 OEC/OECT 笔记(2) 运行RKNN程序 外观 内部 PCB正面 PCB背面 PCB背面 RK3566 1Gbps PHY 配置 OEC 和 OECT(OEC-turbo) 都是基于瑞芯微 RK3566/RK3568 的网络盒子, 没有HDMI输入输出. 硬件上 OEC 和 OECT…

【Web应用】若依框架:基础篇17二次开发-项目名称修改-新建业务模块

文章目录 ⭐前言⭐一、课程讲解⭐二、自己手动实操⭐总结 标题详情作者JosieBook头衔CSDN博客专家资格、阿里云社区专家博主、软件设计工程师博客内容开源、框架、软件工程、全栈&#xff08;,NET/Java/Python/C&#xff09;、数据库、操作系统、大数据、人工智能、工控、网络、…

MQTT入门实战宝典:从零起步掌握物联网核心通信协议

MQTT入门实战宝典&#xff1a;从零起步掌握物联网核心通信协议 前言 物联网时代&#xff0c;万物互联已成为现实&#xff0c;而MQTT协议作为这个时代的"数据总线"&#xff0c;正默默支撑着从智能家居到工业物联的各类应用场景。本文将带你揭开MQTT的神秘面纱&#…

05【Linux经典命令】Linux 用户管理全面指南:从基础到高级操作

目录 前言 1 Linux用户管理基础概念 1.1 Linux用户类型 1.2 用户相关配置文件 1.3 UID与GID 2 用户创建与管理 2.1 创建用户 2.2 设置用户密码 3 用户权限管理 3.1 授予sudo权限 3.2 以其他用户身份执行命令 4 用户信息查询 4.1 查看用户基本信息 4.2 查看用户所…

使用vite-plugin-html在 HTML 文件中动态注入数据,如元数据、环境变量、标题

vite-plugin-html 是一个用于 Vite 构建工具的插件&#xff0c;它可以帮助你在构建过程中动态注入一些 HTML 内容&#xff0c;比如标题、元数据、环境变量等。通过使用这个插件&#xff0c;你可以根据项目的配置和环境变量自动生成带有动态内容的 HTML 文件&#xff0c;适用于 …

Kinova机械臂在Atlas手术导航系统中的核心作用

Kinova机械臂凭借其高精度运动控制和智能交互功能&#xff0c;成为Atlas手术导航系统的重要组成部分。该系统通过实时跟踪患者位置和精确规划手术路径&#xff0c;提高了医疗过程的精准性与效率。灵活的设计使外科医生能够更轻松地操作复杂的手术工具&#xff0c;从而提升患者安…

性能优化之SSR、SSG

一、SSR和SSG介绍 SSR&#xff08;Server-Side Rendering&#xff0c;服务端渲染&#xff09;和 SSG&#xff08;Static Site Generation&#xff0c;静态站点生成&#xff09;是现代前端框架&#xff08;如 Next.js、Nuxt.js、Gatsby&#xff09;的核心渲染策略&#xff0c;用…

经典算法:回文链表

题目&#xff1a;234. 回文链表 给你一个单链表的头节点 head&#xff0c;请你判断该链表是否为 回文链表。如果是&#xff0c;返回 true&#xff1b;否则&#xff0c;返回 false。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#x…

uboot移植之GPIO上电初始状态的调整

开发板在上电之后&#xff0c;GPIO都有一个默认初始状态&#xff0c;这个状态可能是高电平也可能是低电平。而我们的应用程序在正式接管控制这些GPIO&#xff0c;是在内核起来并成功加载根文件系统之后。所以在内核启动的这段时间内&#xff0c;这些GPIO保持在一种不受控的状态…

本地id_rsa.pub输入到服务器~/.ssh/authorized_keys后,依然需要输入密码的解决办法

首先检查服务器&#xff1a; sudo vim /etc/ssh/sshd_config 然后把这两个修改为&#xff1a; 如果依然需要输入密码&#xff0c;在本地终端&#xff1a; ssh -v userserver 查看认证过程&#xff0c;例如我这里提示说明客户端已成功尝试使用密钥认证&#xff1a; 进一步…

【设计模式-3.7】结构型——组合模式

说明&#xff1a;本文介绍结构型设计模式之一的组合模式 定义 组合模式&#xff08;Composite Pattern&#xff09;又叫作整体-部分&#xff08;Part-Whole&#xff09;模式&#xff0c;它的宗旨是通过将单个对象&#xff08;叶子节点&#xff09;和组合对象&#xff08;树枝…

Unity Mac 笔记本操作入门

在 macOS 笔记本电脑上使用 Unity Editor 的场景视图 (Scene View) 旋转视角&#xff0c;主要依赖于触摸板手势和键盘修饰键的组合。由于没有物理中键&#xff0c;操作方式会与 Windows 鼠标略有不同。 以下是具体的旋转视角操作&#xff1a; 1. 基本旋转视角 (Orbit) 这是最…

实时数据仓库是什么?数据仓库设计怎么做?

目录 一、实时数据仓库是什么 &#xff08;一&#xff09;实时数据仓库的定义 &#xff08;二&#xff09;实时数据仓库的特点 二、实时数据仓库的应用场景 &#xff08;一&#xff09;金融行业 &#xff08;二&#xff09;电商行业 &#xff08;三&#xff09;物联网行…

Linux(12)——基础IO(下)

目录 六、重定向 &#x1f4c4;输出重定向 &#x1f4c4;输入重定向 &#x1f4c4;追加重定向 &#x1f4c4;dup2 七、理解一切皆文件 八、缓冲区 &#x1f9e0;什么是缓冲区 &#x1f9e0;为什么要引入缓冲区 &#x1f4c4;缓冲区类型 九、FILE 六、重定向 我们这…

WPF可拖拽ListView

1.控件描述 WPF实现一个ListView控件Item子项可删除也可拖拽排序&#xff0c;效果如下图所示 2.实现代码 配合 WrapPanel 实现水平自动换行&#xff0c;并开启拖拽 <ListViewx:Name"listView"Grid.Row"1"Width"300"AllowDrop"True&…

[蓝桥杯]倍数问题

倍数问题 题目描述 众所周知&#xff0c;小葱同学擅长计算&#xff0c;尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况&#xff0c;当有很多个数之后就会比较苦恼。现在小葱给了你 nn 个数&#xff0c;希望你从这 nn 个数中找到三个数&#xff0c;使得…

【MySQL】 约束

一、约束的定义 MySQL 约束是用于限制表中数据的规则&#xff0c;确保数据的 准确性 和 一致性 。约束可以在创建表时定义&#xff0c;也可以在表创建后通过修改表结构添加。 二、常见的约束类型 2.1 NOT NULL 非空约束 加了非空约束的列不能为 NULL 值&#xff0c;如果可以…