客户端下载服务器所在目录的文件
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