Linux网络编程之TCP文件传输

1. 要求

在Linux环境下,编程实现文件的上传和下载,即客户端可以发送文件给服务器,服务器将文件写到服务器端文件系统中;客户端请求下载文件时服务器读取文件内容,发送给客户端,客户端接收内容并写入本地文件。要求

(1)源代码格式化良好并适当注释;

(2)除上述核心功能外,尽量完善程序,比如使其使用方便等;

(3)提交报告,报告中包括程序源代码和测试效果截图。

2. 基本设计思路

2.1 服务器和客户端的socket连接

服务器通过对socket进行监听listen(),等待客户端的主动连接connect()。

服务器在建立连接后通过pork()函数赋值进程,使子进程关闭服务器的socket监听并向客户端提供服务;父进程则继续监听socket,继续与其他客户端进行连接。

  • 为什么客户端connect前不需要进行bind?

操作系统会自动选择一个可用的本地 IP 地址和端口号来作为客户端套接字的地址,并将这个地址作为连接请求的源地址发送给服务器。这个过程称为“自动绑定”

2.2 客户端的服务请求

在建立了与服务端的socket连接后,客户端通过向服务端发送相关的指令来请求相对应的服务。

通过输入指令发送到服务端,使服务端解析指令,并向客户端返回相对应的数据包。客户端接收并解析数据包来获取相关信息。

2.3 服务端的服务提供

服务端在接收到客户端发来的指令后,根据指令的不同,执行不同的操作。

若接收到客户端下载文件的指令,则将相对应的文件封装成数据包,发送到客户端。

若接收到客户端上传文件的指令,则调用recv()函数来接收由客户端发送过来的数据包,并解析内容写入文本中。

3. 源代码

3.1 server.cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<dirent.h>
#include<string>
#include<vector>

#define port 8888
#define backlog 5
#define MAXBUFF 4096
#define MAXNAMELEN 100

void process_con_server(int s);
void analyzeCommand(char *command, int s);
void sendMessage(int s);
char** splitString(char* str, int& num);
void sendFile(int s, char *fname);
void recvFile(int s, char *fname);

/*文件信息*/
typedef struct FileMess{
    unsigned long fileLen;
    char fileName[100];
} FileMess;

/*数据包*/
typedef struct DataPack{
    char type;     //'D'表示数据,'M'表示文件信息, 'E'表示错误数据包
    int packSize;   //整个数据包的大小
    char content[MAXBUFF];  //数据包携带数据缓冲区
    int contentLen;     //数据包中数据的长度
    unsigned long position;    //数据在文件中的字节位置
    FileMess fileMess;      //文件信息
} DataPack;

/*目录信息数据包*/
typedef struct DirPack{
    int flag;      //标志位,1表示包含目录文件名,0表示结束目录发送的空数据包
    char content[100];
} DirPack;

int main(int argc, char *argv[]){
    int ss, sc;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int err;
    pid_t pid;
    
    /*创建套接字socket*/
    ss = socket(AF_INET, SOCK_STREAM, 0);
    if(ss < 0){
        printf("Create Socket error!!!\n");
        return -1;
    }
    
    /*设置服务器基本信息*/
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    /*绑定服务器地址到套接字socket*/
    err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(err < 0){
        printf("Bind server address error!!!\n");
        return -1;
    }
     
    /*启动监听*/
    err = listen(ss, backlog);  //设置最大排队数量为backlog
    if(err < 0){
        printf("Start listenning failure!!!\n");
        return -1;
    }

    /*服务器建立连接和提供服务*/
    while(1){
        socklen_t addrlen = sizeof(struct sockaddr);
        /*接收来自客户端client的套接字socket*/
        sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
        if(sc < 0){
            printf("accept error!\n");
            continue;   //跳过创建子进程,避免浪费
        }

        /*创建新进程继续处理排队中的客户端连接*/
        pid = fork();
        if(pid == 0){       //父进程的pid永远不为0,未初始化时为随机值
            close(ss);                  //在子进程中关闭服务器监听
            process_con_server(sc);     //向客户端提供服务
        }
        else{
            close(sc);
            
        }
    }
    return 0;
}

/*服务器处理与客户端的连接服务*/
void process_con_server(int s){
    ssize_t size;
    char buffer[1024];      //使用1024bytes的缓冲区来接收用户指令

    while(1){
        memset(buffer, 0, sizeof(buffer));
        
        size = recv(s, buffer, sizeof(buffer), 0);   //会读取回车\n
        if(size == 0)
            return;

        write(STDOUT_FILENO, buffer, size);
        analyzeCommand(buffer, s);  //解析用户指令
    }
    close(s);
}


/*解析客户端指令*/
void analyzeCommand(char *command, int s){
    int substrNum;
    char** substr = splitString(command, substrNum);
    
    /*显示可下载的文件列表, 将列表通过socket传输到客户端*/
    if(!strcmp(substr[0], "ls\n")){
        sendMessage(s);
    }
    /*下载指定文件*/
    else if(!strcmp(substr[0], "download")){
        sendFile(s, substr[1]);
    }
    /*接收来自客户端上传的文件*/
    else if(!strcmp(substr[0], "send")){
        recvFile(s, substr[1]);
    }
    else{
        char *mes = "Please write down the right command!!!\n";
        write(s, mes, strlen(mes));
    }
}

/*以一个或多个空格分割字符串*/
/*返回值:指针数组*/
/*一般输入的指令是 指令+文件名的形式  例如"send test.txt"*/
char** splitString(char* str, int& num) {
    int len = strlen(str);
    char** result = new char*[len];

    int count = 0;
    int start = 0;
    /*遍历分割字符串*/
    for (int i = 0; i < len; ++i) {
        if (str[i] == ' ') {
            if (i - start > 0) {
                int size = i - start;
                result[count] = new char[size + 1];     //size+1是为了尾部添加'\0'作为截至
                strncpy(result[count], &str[start], size);
                result[count][size] = '\0';
                ++count;
            }
            start = i + 1;
        }
    }

    /*若字符串不以空格结尾,则需要将字符串尾部添加*/
    if (len - start > 0) {
        int size = len - start;
        result[count] = new char[size + 1];
        strncpy(result[count], &str[start], size);
        result[count][size] = '\0';
        ++count;
    }

    num = count;
    return result;
}

/*服务器向客户端发送文件,即客户端下载文件*/
void sendFile(int s, char *fname){  //fname含有'\n'回车
    DataPack dataPack;
    char path[100] = "./resources/";
    //去除字符串fname末尾的回车
    char *fName = (char*)malloc(strlen(fname)-1);   
    strncpy(fName, fname, strlen(fname)-1);
    //拼接文件名与资源路径得到文件路径
    strncat(path, fName, strlen(fname)-1);

    //判断用户获取的资源文件是否存在
    int st = access(path, F_OK);
    if(-1 == st){
        /*设置错误信息,并以错误信息类型发送数据包*/
        dataPack.type = 'E';
        dataPack.packSize = sizeof(DataPack);
        char *buffer = "The file isn't exist!!!\n";
        strncpy(dataPack.content, buffer, strlen(buffer));
        dataPack.contentLen = strlen(buffer);
        dataPack.position = 0;
        send(s, &dataPack, dataPack.packSize, 0);
        return;
    }

    /*获取并发送文件信息*/
    struct stat statbuf;
    stat(path, &statbuf);
    dataPack.type = 'M';
    dataPack.packSize = sizeof(DataPack);
    dataPack.fileMess.fileLen = statbuf.st_size;
    strncpy(dataPack.fileMess.fileName, fName, strlen(fName));
    unsigned long sRe = send(s, &dataPack, dataPack.packSize, 0);
    printf("成功发送文件信息数据包!\n");
    

    /*发送文件内容*/
    unsigned long sendedCount = 0;      //记录已发送的数据大小
    int fd = open(path, O_RDONLY);     //打开文件
    while(sendedCount < statbuf.st_size){
        //构造文件内容数据包
        DataPack filedata;
        memset(&filedata, 0, sizeof(DataPack));
        unsigned long readBytes = read(fd, filedata.content, MAXBUFF);
        filedata.contentLen = readBytes;
        filedata.type = 'D';
        filedata.packSize = sizeof(DataPack);
        filedata.position = sendedCount;

        unsigned long sDa = send(s, &filedata, filedata.packSize, 0);
        if(sDa > 0){
            sendedCount += filedata.contentLen;
        }
        printf("成功发送数据:%ld bytes\n", sendedCount);
    }

    //构造结束标志数据包,标志文件传输完毕
    memset(&dataPack, 0, sizeof(DataPack));
    dataPack.type = 'E';
    send(s, &dataPack, sizeof(DataPack), 0);
    close(fd);
    return;        
}

/*服务器接收文件,即客户端上传文件*/
void recvFile(int s, char *fname){
    
    unsigned long fileSize = 0;     //记录接收文件的大小
    unsigned long recvedCount = 0;  //记录已接收的数据量大小
    int fd = 0;
    DataPack *dataPack = (DataPack *)malloc(sizeof(DataPack));
    while(1){
        memset(dataPack, 0, sizeof(DataPack));
        unsigned long recvBytes = recv(s, dataPack, sizeof(DataPack), 0);

        //'E'类型的数据包为错误数据包
        //在文件传输完成后,会发送一个空内容(content)的错误数据包,表示文件传输完成
        //也可以选择重新定义一个新的类型的数据包作为结束数据包类型
        if(dataPack->type == 'E'){   //该数据包为错误数据包
            write(STDOUT_FILENO, dataPack->content, dataPack->contentLen);
            break;
        }

        //'M'类型的数据包为文件信息数据包,根据相应的信息创建文件
        else if(dataPack->type == 'M'){
            char path[200] = "./upload/";
            strcat(path, dataPack->fileMess.fileName);
            //无论什么时候都创新创建文件
            fd = open(path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
            fileSize = dataPack->fileMess.fileLen;
        }
        
        //'D'类型数据包为文件内容数据包,解析数据包内容,并写入相应文件的相应位置
        else if(dataPack->type = 'D'){
            lseek(fd, recvedCount, SEEK_SET);
            write(fd, dataPack->content, dataPack->contentLen);
            recvedCount += dataPack->contentLen;
       }
    }
    if (fd != 0){
        close(fd);      //关闭文件描述符
    }
}

void sendMessage(int s){
    DIR *dirp;
    struct dirent *direntp;
    int count = 0;
    DirPack *dirpack = (DirPack *)malloc(sizeof(DirPack));
    memset(dirpack, 0, sizeof(DirPack));
    dirpack->flag = 1;
    // 打开当前目录
    dirp = opendir("./resources/");
    // 读取目录内容
    while ((direntp = readdir(dirp)) != NULL) {
        // 忽略当前目录和父目录
        if (strcmp(direntp->d_name, ".") == 0 || strcmp(direntp->d_name, "..") == 0) {
            continue;
        }

        // 将文件名发送到客户端中
        char *dirfName = strdup(direntp->d_name);   //获取文件名
        strncpy(dirpack->content, dirfName, strlen(dirfName));
        send(s, dirpack, sizeof(DirPack), 0);
        // 清空数据包的内容
        memset(dirpack->content, 0, sizeof(dirpack->content));
    }

    //构造结束数据包
    dirpack->flag = 0;
    send(s, dirpack, sizeof(DirPack), 0);

    // 关闭目录
    closedir(dirp);
}

3.2 client.cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<unistd.h>
#include<arpa/inet.h>

#define port 8888
#define MAXBUFF 4096

void process_con_client(int s);             //处理与服务器的连接服务
char** splitString(char* str, int& num);    //以一个或多个空格分割字符串
void analyzeCommand(char *command, int s);  //解析用户输入的命令    
void recvFile(int s, char *fname);          //接收来自服务器的文件数据包
void sendFile(int s, char *fname);          //向服务器上传文件
void recvMessage(int s);                    //接收可下载文件目录信息

/*文件信息*/
typedef struct FileMess{
    unsigned long fileLen;
    char fileName[100];
} FileMess;

/*文件数据包*/
typedef struct DataPack{
    char type;     //'D'表示数据,'M'表示文件信息, 'E'表示错误数据包
    int packSize;   //整个数据包的大小
    char content[MAXBUFF];  //数据包携带数据缓冲区
    int contentLen;     //数据包中数据的长度
    unsigned long position;    //数据在文件中的字节位置
    FileMess fileMess;      //文件信息
} DataPack;

/*目录信息数据包*/
typedef struct DirPack{
    int flag;      //标志位,1表示包含目录文件名,0表示结束目录发送的空数据包
    char content[100];
} DirPack;

int main(int argc, char *argv[]){
    int s;
    struct sockaddr_in server_addr;
    int err;

    /*创建套接字socket*/
    s = socket(AF_INET, SOCK_STREAM, 0);
    if(s < 0){
        printf("Create socket error!!!\n");
        return -1;
    }

    /*设置连接服务器IP地址和端口号*/
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);

    /*为什么客户端connect前不需要进行bind?*/
    /*操作系统会自动选择一个可用的本地 IP 地址和端口号来作为客户端套接字的地址,
    并将这个地址作为连接请求的源地址发送给服务器。这个过程称为“自动绑定”*/

    /*连接服务器*/
    err = connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
    if(err < 0){
        printf("Connect error!!!\n");
        return -1;
    }
    process_con_client(s);  //请求服务
    close(s);       //关闭连接
    return 0;
}

/*处理客户端与服务器的连接*/
void process_con_client(int s){
    ssize_t size = 0;
    char buffer[1024];

    //反复处理用户输入的命令
    while(1){
        memset(buffer, 0, sizeof(buffer));
        size = read(STDIN_FILENO, buffer, 1024);
        if(size > 0){
            write(s, buffer, size);
            analyzeCommand(buffer, s);
        }
    }
    close(s);
}


/*以一个或多个空格分割字符串*/
/*返回值:指针数组*/
/*num:传出参数,字符串分割成的子串个数*/
char** splitString(char* str, int& num) {
    int len = strlen(str);
    char** result = new char*[len];

    int count = 0;
    int start = 0;
    /*遍历分割字符串*/
    for (int i = 0; i < len; ++i) {
        if (str[i] == ' ') {
            if (i - start > 0) {
                int size = i - start;
                result[count] = new char[size + 1];     //size+1是为了尾部添加'\0'作为截至
                strncpy(result[count], &str[start], size);
                result[count][size] = '\0';
                ++count;
            }
            start = i + 1;
        }
    }

    /*若字符串不以空格结尾,则需要将字符串尾部添加*/
    if (len - start > 0) {
        int size = len - start;
        result[count] = new char[size + 1];
        strncpy(result[count], &str[start], size);
        result[count][size] = '\0';
        ++count;
    }

    num = count;
    return result;
}

/*解析客户端用户输入的指令*/
void analyzeCommand(char *command, int s){
    int substrNum;
    char** substr = splitString(command, substrNum);

    /*判断用户输入的指令是否符合格式*/
    /*此处仅提供三种指令,ls、download [filename]、send [filename]*/
    if(substrNum > 2){
        char *buffer = "The format of command is error!!!\n";
        write(STDOUT_FILENO, buffer, strlen(buffer));
    }
    /*显示可下载的文件列表, 接收来自服务器传输过来的信息*/
    if(!strcmp(substr[0], "ls\n")){
        recvMessage(s);
    }
    /*下载指定文件*/
    if(!strcmp(substr[0], "download")){
        recvFile(s, substr[1]);
    }
    /*上传指定的文件到服务器*/
    if(!strcmp(substr[0], "send")){
        sendFile(s, substr[1]);
    }
}

/*执行文件下载命令,接收服务器发来的数据包*/
void recvFile(int s, char *fname){
    
    unsigned long fileSize = 0;     //记录接收文件的大小
    unsigned long recvedCount = 0;  //记录已接收的数据量大小
    int fd = 0;
    DataPack *dataPack = (DataPack *)malloc(sizeof(DataPack));  //动态分配内存
    while(1){
        memset(dataPack, 0, sizeof(DataPack));
        unsigned long recvBytes = recv(s, dataPack, sizeof(DataPack), 0);

        //'E'类型的数据包为错误数据包
        //在文件传输完成后,会发送一个空内容(content)的错误数据包,表示文件传输完成
        //也可以选择重新定义一个新的类型的数据包作为结束数据包类型
        if(dataPack->type == 'E'){
            write(STDOUT_FILENO, dataPack->content, dataPack->contentLen);
            break;
        }
        
        //'M'类型的数据包为文件信息数据包,根据相应的信息创建文件
        else if(dataPack->type == 'M'){
            char path[200] = "./download/";
            char *fName = (char*)malloc(strlen(fname)-1);   
            strncpy(fName, fname, strlen(fname)-1);   
            strncat(path, fName, strlen(fname)-1);
            //无论什么时候都创新创建文件
            fd = open(path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
            fileSize = dataPack->fileMess.fileLen;
        }
        
        //'D'类型数据包为文件内容数据包,解析数据包内容,并写入相应文件的相应位置
        else if(dataPack->type = 'D'){
            lseek(fd, recvedCount, SEEK_SET);
            write(fd, dataPack->content, dataPack->contentLen);
            recvedCount += dataPack->contentLen;
       }
    }
    if (fd != 0){
        close(fd);      //关闭文件描述符
    }
}

/*接收服务器发来的可下载文件目录数据包*/
void recvMessage(int s){
    DirPack *dirpack = (DirPack *)malloc(sizeof(DirPack));      //动态分配内存
    memset(dirpack, 0, sizeof(DirPack));
    int count = 0;
    while(1){
        recv(s, dirpack, sizeof(DirPack), 0);
        ++count;
        //接收完毕目录后会发现一个flag标志为0的数据包,表示目录数据包发送完毕
        if(dirpack->flag == 0){
            memset(dirpack, 0, sizeof(DirPack));    
            break;
        }
        write(STDOUT_FILENO, dirpack->content, sizeof(dirpack->content));

        //排布显示
        if(count%4 == 0){
            write(STDOUT_FILENO, "\n", 1);
        }
        else {
            write(STDOUT_FILENO, "\t", 1);
        }
        memset(dirpack, 0, sizeof(DirPack));
    }
    free(dirpack);
}

/*执行上传文件命令,向服务器发送文件信息和内容数据包*/
void sendFile(int s, char *fname){  //fname含有'\n'回车
    DataPack dataPack;

    //去除字符串fname末尾的回车
    char *fName = (char*)malloc(strlen(fname)-1);   
    strncpy(fName, fname, strlen(fname)-1);

    //判断是否存在相应名称的文件
    int st = access(fName, F_OK);
    if(-1 == st){
        /*设置错误信息,并以错误信息类型发送数据包*/
        dataPack.type = 'E';
        dataPack.packSize = sizeof(DataPack);
        char *buffer = "The file isn't exist!!!\n";
        strncpy(dataPack.content, buffer, strlen(buffer));
        dataPack.contentLen = strlen(buffer);
        dataPack.position = 0;
        send(s, &dataPack, dataPack.packSize, 0);
        return;
    }
    

    /*获取并发送文件信息*/
    struct stat statbuf;
    stat(fName, &statbuf);
    dataPack.type = 'M';
    dataPack.packSize = sizeof(DataPack);
    dataPack.fileMess.fileLen = statbuf.st_size;
    strncpy(dataPack.fileMess.fileName, fName, strlen(fName));
    unsigned long sRe = send(s, &dataPack, dataPack.packSize, 0);
    if(sRe > 0){
        printf("成功发送文件信息数据包!\n");
    }
    

    /*发送文件内容*/
    unsigned long sendedCount = 0;      //记录已发送的数据大小
    int fd = open(fName, O_RDONLY);     //打开文件
    //当发送的数据量小于文件大小时则继续发送数据
    while(sendedCount < statbuf.st_size){

        DataPack filedata;
        memset(&filedata, 0, sizeof(DataPack));
        unsigned long readBytes = read(fd, filedata.content, MAXBUFF);
        filedata.contentLen = readBytes;
        filedata.type = 'D';
        filedata.packSize = sizeof(DataPack);
        filedata.position = sendedCount;

        unsigned long sDa = send(s, &filedata, filedata.packSize, 0);
        if(sDa > 0){
            sendedCount += filedata.contentLen;
            printf("成功发送数据:%ld bytes\n", sendedCount);
        }
        
    }

    /*发送传输结束数据包*/
    memset(&dataPack, 0, sizeof(DataPack));
    dataPack.type = 'E';
    send(s, &dataPack, sizeof(DataPack), 0);
    close(fd);
    return;        
}

4. 结果测试

4.1 g++编译源代码
  • server.cpp
g++ server.cpp -o server

image-20230307205135652

  • client.cpp
g++ client.cpp -o client

image-20230307205623115

4.2 相应文件内容
  • 客户端

image-20230307212414638

  • 服务端

image-20230307212450792

4.3 运行效果
  • 服务端网络配置

image-20230307205931447

  • client

image-20230307210343897

  • server

image-20230307210400687

运行流程:

  1. 服务端运行server程序,启动监听;客户端启动client程序,主动与服务端建立连接;

  2. 客户端向服务端发送‘ls’指令,服务端收到‘ls’指令后,打印指令,并向客户端提供当前目录下的resources文件夹中所包含的文件目录;

    客户端收到文件目录后,打印文件目录;

  3. 客户端向服务端发送‘download test3.txt’指令,服务端收到‘download test3.txt’指令后,打印指令,并打开resource文件夹中中test3.txt文件,读取内容与文件信息,打包成数据包发送到客户端。

    客户端收到数据包后,解析数据包内容,并在download文件夹中创建相应类型的文件,将数据包的内容写入文件中。

  4. 客户端向服务端发送‘download test2.txt’指令,服务端收到‘download test2.txt’指令后,打印指令,并打开resource文件夹中中test2.txt文件,读取文件信息,打包成数据包发送到客户端;读取内容,发现内容为空,故不发送文件内容数据包。

    客户端收到文件信息数据包,解析数据包内容,并在download文件夹中创建相应类型的文件。由于没有文件内容数据包,此时边不执行文件内容写入操作;

  5. 客户端向服务端发送‘send sdtos.txt’指令,服务端收到‘send sdtos.txt’指令后,打印指令。

    客户端读取当前目录下的sdtos.txt文件的文件信息和文件内容,将其封装成数据包,发送到服务端;

    在收到客户端发送过来的数据包后,在当前目录的upload文件夹中创建相应的文件,并向文件中写入相应的文件内容。

  • 客户端

image-20230307212145671

  • 服务端

在这里插入图片描述

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux网络编程中,TCP是一种可靠的传输协议,用于在网络上建立可靠的连接。TCP连接具有以下特点和问题: 1. TCP连接不能同时打开多次,当一个TCP连接处于TIME_WAIT状态时,无法立即使用该连接占用的端口来建立新的连接。如果需要强制立即使用处于TIME_WAIT状态的连接所占用的端口,可以通过setsockopt()方法设置socket选项SO_REUSEADDR来实现。 2. TCP是面向字节流的协议,发送端执行的写操作次数和接收端执行的读操作次数之间没有数量关系。这可能导致粘包问题,即发送端多次发送的数据在接收端一次性收取完成,无法准确区分出数据的边界。为了解决粘包问题,可以采用以下方法: - 发送端每次发送数据后等待接收端的回复再进行下一次发送。 - 定义协议,通过在数据中添加特定的标识来区分数据的边界,例如使用【长】【宽】【高】表示长度、宽度和高度。 3. TIME_WAIT状态存在的意义主要有两个: - 可靠地终止TCP连接,确保双方都已经完全接收了对方的所有数据。 - 确保迟到的TCP报文有足够的时间被识别并丢弃,特别是在服务器主动关闭连接时。 总结起来,在Linux网络编程中,TCP作为一种可靠的传输协议,需要注意处理TIME_WAIT状态和粘包问题。可以通过设置socket选项和制定协议来解决这些问题,以保证网络连接的可靠性和数据的准确传输。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Linux网络编程 | TCP详解](https://blog.csdn.net/weixin_52983138/article/details/125077602)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值