Linux&C语言简单实现TCP客户端下载服务器文件-TCP 粘包问题-传输层

客户端下载服务器所在目录的文件

在这里插入图片描述

TCP 粘包问题- -面试

  • TCP的底层有一个 Nagel 算法,这个算法会将一定短的时间内的

  • 发往同一个接收端的多个小的数据包

  • 组装成一个整体 发送给对方

  • 但是对方没法确定每条消息的边界,

  • 所以就导致了 接收方收到消息后无法正确的解析

解决方案

- 只要保证每次发送的数据包一样大,接收方也按照这个大小去接收
- 就不会出现粘包问题
- 每次收发的数据不要以字符数组的方式发送,
- 把要发送的数据封装成一个结构体发送给对方,对方也要使用同样的结构体去接
- 循环发送文件内容

recv

功能:
	在套接字中接收一条消息
	如果对方的socket已经关闭了,recv会返回0

send

功能:
	向套接字中发送一条消息
 	如果对方的socket已经关闭了,第一次send会返回0
             				 第二次send会报错  SIGPIPE

要求

- 客户端和服务器不要运行在同一路径
- 客户端先给服务器发送要下载的文件名
- 服务器收到文件名后,判断当前路径下是否有该文件 (open(只读)--ENOENT)
- 如果文件不存在,则发送 不存在的提示  给客户端
- 如果文件存在,也要先告诉客户端文件存在,然后
- 循环读取文件内容,发送给客户端
- 客户端新建一个文件,接收文件内容,并保存到文件中。

代码实现

服务器-----server.c

#include <stdio.h>
/*socket-bind-listen-accept*/
#include <sys/types.h>
#include <sys/socket.h>
/*memset-strcpy*/
#include <string.h>
/*sockaddr_in结构体*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
/*htons*/
#include <arpa/inet.h>
/*inet_addr*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*close*/
#include <unistd.h>
/*exit*/
#include <stdlib.h>
#include <errno.h>
/*open*/
#include <sys/stat.h>
#include <fcntl.h>

#define ERRLOG(errmsg)                                       \
    do                                                       \
    {                                                        \
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                      \
        exit(-1);                                            \
    } while (0)
//用结构体传输--防止TCP 粘包问题
typedef struct __MSG
{
    int num;
    char buff[128];
} msg_t;

int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    //端口号  填 8888 9999 6789 ...都可以
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    // 3.将套接字和网络信息结构体绑定
    if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
    {
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if (-1 == listen(sockfd, 10))
    {
        ERRLOG("listen error");
    }

    //用来保存客户端信息的结构体
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr)); //清空
    socklen_t client_addr_len = sizeof(client_addr);

    int accept_fd;

    char filename[128] = {0};
    char buff[128] = {0};
    int ret = 0;
    int fd;
    msg_t msg;                      //用结构体传输--防止TCP 粘包问题
    memset(&msg, 0, sizeof(msg_t)); //清空

ACC: //重新等待客户端连接
    //阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
    printf("等待客户端\n");
    accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (-1 == accept_fd)
        ERRLOG("accept error");

    printf("客户端 (%s:%d) 连接成功\n",
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

RECV_filename: //重新接收
    //接收客户端发来的数据
    if (0 > recv(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("recv error");
    strcpy(filename, msg.buff);
    printf("要下载的文件是:[%s]\n", filename);
    //只读
    if (-1 == (fd = open(filename, O_RDONLY))) //-1错误
    {
        if (errno == ENOENT)
        { //文件不存在
            //发送文件不存在的消息给客户端
            memset(&msg, 0, sizeof(msg_t)); //清空
            strcpy(msg.buff, "文件不存在");
            if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
                ERRLOG("send error");

            goto RECV_filename; //重新接收
        }
        ERRLOG("open error"); //其他错误
    }

    //文件存在也要发送 文件存在的信息给客户端
    memset(&msg, 0, sizeof(msg_t));
    strcpy(msg.buff, "文件存在");
    if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    //循环发送文件内容
    while ((ret = read(fd, buff, 128)) > 0)
    {
        memset(&msg, 0, sizeof(msg_t));
        printf("ret= %d\n", ret);
        msg.num = ret;
        strcpy(msg.buff, buff);
        if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
            ERRLOG("send error");
    }
    //发送文件结束的标志
    memset(&msg, 0, sizeof(msg_t));
    strcpy(msg.buff, "结束");
    msg.num = 0; //结束的标志
    if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    close(fd);
    close(accept_fd);
    goto ACC; //重新等待客户端连接
    close(sockfd);

    return 0;
}

客户端----client.c

#include <stdio.h>
/*socket-bind-listen-accept*/
#include <sys/types.h>
#include <sys/socket.h>
/*memset-strcpy*/
#include <string.h>
/*sockaddr_in结构体*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
/*htons*/
#include <arpa/inet.h>
/*inet_addr*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*close*/
#include <unistd.h>
/*exit*/
#include <stdlib.h>
#include <errno.h>
/*open*/
#include <sys/stat.h>
#include <fcntl.h>

//用结构体传输--防止TCP 粘包问题
typedef struct __MSG
{
    int num;
    char buff[128];
} msg_t;

#define ERRLOG(errmsg)                                       \
    do                                                       \
    {                                                        \
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                      \
        exit(-1);                                            \
    } while (0)

int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    //端口号  填 8888 9999 6789 ...都可以
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    //与服务器建立连接
    if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
    {
        ERRLOG("connect error");
    }

    char filename[128] = {0};
    char buff[128] = {0};
    msg_t msg;                      //用结构体传输--防止TCP 粘包问题
    memset(&msg, 0, sizeof(msg_t)); //清空

INPUT_filename:
    printf("下载文件名: ");
    scanf("%s", filename);
    strcpy(msg.buff, filename);
    //发--将文件名发给服务器
    if (0 > send(sockfd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    //收--接收文件是否存在的信息
    if (0 > recv(sockfd, &msg, sizeof(msg_t), 0))
        ERRLOG("recv error");

    //如果文件不存在 重新输入文件名
    if (0 == strcmp(msg.buff, "文件不存在"))
    {
        printf("文件不存在\n");
        goto INPUT_filename; //重新发送
    }

    //文件存在 则循环接收文件内容 并写到文件里
    if (strcmp(msg.buff, "文件存在") == 0)
    {
        int dfd;
        if ((dfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
            ERRLOG("open src error");

        while (0 != recv(sockfd, &msg, sizeof(msg_t), 0))
        {
            printf("msg.num = %d\n", msg.num);
            if (msg.num == 0)
                break;

            if (-1 == write(dfd, msg.buff, msg.num))
                ERRLOG("write error");
        }
        close(dfd);
    }
    printf("文件[%s]下载完成\n", filename);
    close(sockfd);
    return 0;
}

执行结果

在这里插入图片描述在这里插入图片描述

注意

- 使用scanf输入的字符串 结尾没有 '\n
scanf("%s", filename); 

- 如果使用 fgets() 需要手动清理换行符
fgets(filename, N, stdin);
filename[strlen(filename)-1] = '\0';//将结尾的 \n 换成 \0

6. 非原创

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值