利用 TCP 完成文件传输的设计
实验目的
输入文件路径,利用 TCP 实现客户文件向服务器的传输,并实现对 TCP 的基本封装
实验原理
1.服务器
(1)建立 socket
(2)确定服务器 scokaddr_in 结构体
(3)点分十进制 IP 转换
(4)使用 bind 绑定套接字
(5)使用 listen 监听
(6)使用 accept 接受连接请求
(7)accept 返回新的套接字描述符
(8)使用 recv 接收传来的数据(文件路径)
(9)打开文件,这里需要文件名
(10)从该字符串获取文件名
(11)使用 recv 接收文件内容
(12)判断 recv 函数返回的状态
(13)将接收到的内容放入缓冲区
(14)将缓冲区内容写入文件
(15)关闭文件
(16)关闭 socket
# 2.客户端
(1)建立 socket
(2)确定服务器 scokaddr_in 结构体
(3)点分十进制 IP 转换
(4)使用 connect 连接
(5)打开文件
(6)准备缓冲区
(7)缓冲区初始化置空
(8)将文件内容读入缓冲区
(9)使用 send 将缓冲区内容发送到服务器
(10)文件内容发送完成
(11)关闭文件
(12)关闭 socket
实验步骤
1.服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 1111 // 设置端口号
#define LISTENQ 10 // 设置监听队列长度
#define BUFFSIZE 1024 // 设置缓冲区大小
#define FILE_BUFFSIZE 100 // 设置文件缓冲区大小
#define END_FLAG "end" // 结束标记
int passiveTCP() {//封装 tcp 的建立
// 创建套接字,使用流数据格式
int serv_fd = socket(AF_INET, SOCK_STREAM, 0);
// 定义一个地址结构体变量
struct sockaddr_in serv_addr;
// 清零地址
memset(&serv_addr, 0, sizeof(serv_addr));
// 给地址结构体设置值
serv_addr.sin_family = AF_INET; // Ipv4
serv_addr.sin_port = htons(PORT); // 指定端口号
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 指定任意ip
bind(serv_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // 套接字绑定地址结构
listen(serv_fd, LISTENQ); // 设置为监听模式
return serv_fd; // 返回套接字
}
int main() {
int serv_fd = passiveTCP(); // 创建套接字并绑定端口和ip
char filename[FILE_BUFFSIZE];
char buffer[BUFFSIZE];
int cnt = 0;
while (1) {
//将文件接收到服务器根目录
int clie_fd;
// 定义一个新的地址结构变量
struct sockaddr_in clie_addr;
// 清零地址结构
memset(&clie_addr, 0, sizeof(clie_addr));
int len = sizeof(clie_addr);
// 处理连接请求, 返回一个新的套接字,用户客户端通信
printf("初始化成功\n");
if ((clie_fd = accept(serv_fd, (struct sockaddr *)&clie_addr, &len)) < 0) {
printf("error\n");
}
else {
char buff_t[BUFFSIZE] = { 0 }; //做中间临时保存
cnt ++;
printf("===connect success %d===\n", cnt);
// 处理考虑有多个文件的情况
while(1){
memset(filename, 0, FILE_BUFFSIZE);
memset(buffer, 0, BUFFSIZE);
recv(clie_fd, filename, sizeof(filename), 0);//接收文件名
if( strcmp(filename, END_FLAG) == 0 || strcmp(filename, "") == 0){
break; // 文件全部发送完毕
}
printf("#> %s\n", filename);
// 对文件名进行处理
int t = (int)(strchr(filename, '.') - filename); // 计算'.'的下标
// 判断文件有无后缀
if( t < 0 )
{
strcat(filename, "_副本");
printf("#> save filename: [%s]\n", filename);
}
else{
// 将后缀保存
strcpy(buff_t, filename + t);
printf("#> %s\n", buff_t);
strcpy(filename + t, "_副本");
printf("#> %s\n", filename);
strcat(filename, buff_t);
printf("#> save filename: [%s]\n", filename);
}
FILE *fp = fopen(filename, "w");//创建文件
printf("transport start\n");
int n;
// 接收文件内容,并写入打开的文件中,直到文件结尾
while ((n = recv(clie_fd, buffer, BUFFSIZE, 0)) > 0) {
//printf("%d: %s\n", strlen(buffer), buffer);
if( strcmp(buffer, END_FLAG) == 0){
break;
}
fwrite(buffer, sizeof(char), n, fp);
// buffer 清零
memset(buffer, 0, BUFFSIZE);
}
printf("transport finish\n");
printf("_______________________\n");
// 关闭文件
fclose(fp);
}
// 关闭连接新建套接字
close(clie_fd);
printf("===connect close %d===\n", cnt);
}
}
// 关闭服务器套接字
close(serv_fd);
return 0;
}
2.客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 1111 // 设置端口号
#define IP "127.0.0.1" // 设置ip
#define BUFFSIZE 1024 // 设置缓冲区大小
#define FILE_BUFFSIZE 100
#define END_FLAG "end"
int main(int argc, char *argv[]) {
// 创建套接字,采用 流格式套接字 SOCK_STREAM
int clie_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in clie_addr;
// 地址结构清零
memset(&clie_addr, 0, sizeof(clie_fd));
// 地址结构赋值
clie_addr.sin_family = AF_INET;
clie_addr.sin_port = htons(PORT);
clie_addr.sin_addr.s_addr = inet_addr(IP);// 指定ip
//inet_pton(AF_INET, IP, &(addr.sin_addr.s_addr));
//
char filename[FILE_BUFFSIZE];
char buffer[BUFFSIZE];
// 将 filename、buffer 清零
memset(filename, 0, sizeof(filename));
memset(buffer, 0, sizeof(buffer));
printf("=====================\n");
if (connect(clie_fd, (struct sockaddr*)&clie_addr, sizeof(clie_addr)) < 0) {
printf("error\n");
}
else {
int cnt = 0;
char file_name_t[FILE_BUFFSIZE][FILE_BUFFSIZE];
printf("===connect success===\n");
if( argc > 1 ){// 表示参数来自程序运行的参数
int i = 0;
for( i = 1;i < argc; i ++)
{
strcpy(file_name_t[cnt ++], argv[i]);
}
}
//输入要传送的文件名,文件确保在根目录下
else{
printf("input filename:");
fgets(filename, FILE_BUFFSIZE, stdin); // 输入遇到回车结束
// 将字符串最后的'\n'去掉
filename[strlen(filename) - 1] = '\0';
// 分割字符串
char *p = strtok(filename, " ");
while(p){
//printf("&> %s\n", p);
strcpy(file_name_t[cnt ++], p);
p = strtok(NULL, " ");
}
}
for(int i = 0; i < cnt; i ++){
if( strcmp(file_name_t[i], "") == 0 ){
continue;
}
FILE* fp = fopen(file_name_t[i], "r"); // 以读的方式打开文件
if (fp == NULL) {
printf("%s :file not exist\n", file_name_t[i]);
printf("$> %s\n\n", END_FLAG);
continue;
}
else {
//先传送文件名,后传送文件内容
printf("transport start\n");
send(clie_fd, file_name_t[i], sizeof(file_name_t[i]), 0);
printf("$> %s\n", file_name_t[i]);
int n;
// 读取文件的内容并发送,直到文件发送内容发送完毕
while ( (n = fread(buffer, sizeof(char), BUFFSIZE, fp)) > 0 ) {
send(clie_fd, buffer, n, 0);
// printf("%s\n", buffer);
// 发送完后将buffer清零
memset(buffer, 0, sizeof(buffer));
}
printf("transport finish\n");
sleep(0.5);
send(clie_fd, END_FLAG, sizeof(END_FLAG), 0); // 发送结束标志
printf("$> %s\n\n", END_FLAG);
}
// 关闭文件
fclose(fp);
sleep(0.5);
}
send(clie_fd, END_FLAG, sizeof(END_FLAG), 0); // 发送结束标志
printf("$> %s\n", END_FLAG);
}
printf("=====================\n");
// 关闭套接字
close(clie_fd);
return 0;
}
实验结果:
传输前客户端根目录下保存着我们传输的文件 wct.txt