Linux下C语言实现TCP文件传输 - Go语言中文社区

Linux下C语言实现TCP文件传输


开发环境:

Linux,GCC

功能介绍:

客户端和服务端的TCP文件传输,客户端可以上传文件到服务端,也可以从服务端下载文件,还可以查看和修改服务端的工作目录(临时),查看客户端当前目录。

代码下载:

码云:传送门    GitHub:传送门

效果图参考页尾,那么话不多说,直接上码(只有部分,完整请访问码云

服务端:server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include "tools.h"

#define pf printf

char cmd[20] = {};

// 开始运行
void *start_run(void *arg);
// 客户端上传
void c_up(int *clifd);
// 客户端下载
void c_down(int *clifd);
// 返回文件列表
void c_list(int *clifd);

typedef struct LS
{
	char mode[15];	// 文件的模式
	int dir_num;	// 是否目录或目录中包含目录的数量
	char user[20];	// 文件的用户名
	char group[20]; // 文件的组名
	long size;		// 文件的字节数
	char time[30];	// 文件的最后修改时间
	int st_mode;	// 文件类型和权限
	char name[20];	// 文件名
} LS;

// 主函数
int main()
{
	pf("服务器创建socket...n");
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
		perror("socket");
		return -1;
	}

	pf("准备地址...n");
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	// 端口 IP 自行修改
	addr.sin_port = htons(60000);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	socklen_t len = sizeof(addr);

	pf("绑定socket与地址...n");
	if (bind(sockfd, (struct sockaddr *)&addr, len))
	{
		perror("bind");
		return -1;
	}

	pf("设置监听...n");
	if (listen(sockfd, 5))
	{
		perror("listen");
		return -1;
	}

	pf("等待客户端连接...n");
	for (;;)
	{
		struct sockaddr_in addrcli = {};
		// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
		int *clifd = (int*)malloc(sizeof(int));
		*clifd = accept(sockfd, (struct sockaddr *)&addrcli, &len);
		if (0 > *clifd)
		{
			perror("accept");
			continue;
		}

		pthread_t pid;
		// 创建线程函数 int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
		//第一个参数为指向线程标识符的指针。
		//第二个参数用来设置线程属性。
		//第三个参数是线程运行函数的地址。
		//最后一个参数是运行函数的参数。
		pthread_create(&pid, NULL, start_run, (void *)clifd);
	}

	return 0;
}

// 开始运行
void *start_run(void *arg)
{
	int *clifd = (int *)arg;
	char up[20] = "我想上你";
	char down[20] = "我想下你";
	char see[20] = "我想看你";
	char quit[20] = "我要走了";
	int c_size = 0;
	for (;;)
	{
		c_size = read(*clifd, cmd, sizeof(cmd));
		
		if (strcmp(up, cmd) == 0)
		{
			pf("收到客户端的上传指令n");
			c_up(clifd);
			memset(cmd, 0, 20);
		}
		else if (strcmp(down, cmd) == 0)
		{
			pf("收到客户端的下载指令n");
			c_down(clifd);
			memset(cmd, 0, 20);
		}
		else if (strcmp(see, cmd) == 0)
		{
			pf("收到客户端的目录指令n");
			c_list(clifd);
			memset(cmd, 0, 20);
		}
		else if (strcmp(quit, cmd) == 0)
		{
			pf("收到服务端的退出指令n");
			pthread_exit(0);
			return (void *)NULL;
		}
		
	}
	//char *str = "我死了";
	//pthread_exit(str);
}

// 上传
void c_up(int *clifd)
{
	int flag = 0;
	int r_size = 0;
	int w_size = 0;
	char buf[1024] = {};

	w_size = write(*clifd, "success", 8);
	read(*clifd, buf, 10);
	if(strncmp(buf, "error", 10) == 0)
	{
		printf("收到客户端返回error,接收终止n");
		return;
	}
	else if(strncmp(buf, "success", 10) == 0)
	{
		printf("收到客户端返回success,继续接收n");
	}
	else
	{
		printf("收到客户端异常数据:%s,接收终止n", buf);
		return;
	}
	

	char filename[50] = {};
	memset(filename, 0, sizeof(filename));
	int f_size = read(*clifd, filename, sizeof(filename));
	pf("收到文件名:%sn", filename);
	usleep(100000);
	w_size = write(*clifd, "success", 8);
	pf("发送success给客户端n", r_size);

	int fd = open(filename, O_CREAT | O_RDWR, 0777);

	do
	{
		memset(buf, 0, sizeof(buf));
		r_size = read(*clifd, buf, sizeof(buf));
		pf("[收到字节数:%d ", r_size);

		w_size = write(fd, buf, r_size);
		pf("写入文件字节数:%d ", w_size);

		usleep(10000);
		
		w_size = write(*clifd, "success", 8);
		pf("发送success给客户端 ]n");

		flag++;
	} while (r_size == 1024);

	sleep(1);

	if (flag > 0)
	{
		char result[20] = "success";
		pf("    文件传输完毕 返回客户端successnn");
		write(*clifd, result, strlen(result) + 1);
	}
	else
	{
		char result[20] = "error";
		pf("    文件传输失败 返回客户端errornn");
		write(*clifd, result, strlen(result) + 1);
	}
	close(fd);
	return;
}

// 下载
void c_down(int *clifd)
{
	DIR *dir;
	dir = opendir(".");
	char list[1024] = {};
	struct dirent *dirent;
	int r_size = 0;
	int w_size = 0;
	char buf[1024] = {};
	char buf2[20] = {};
	char filename[50] = {};
	char filename2[51] = {};

	usleep(10000);
	w_size = write(*clifd, "success", 8);

	usleep(10000);

	// 获取目录下所有文件名
	while ((dirent = readdir(dir)) != NULL)
	{
		strcat(list, dirent->d_name);
		strcat(list, " ");
	}
	pf("当前目录列表:%sn", list);
	pf("strlen(list):%dn", strlen(list));
	int l_size = write(*clifd, list, strlen(list)+1);
	pf("发送当前下载目录列表给客户端n");

	pf("等待接收文件名...n");
	int f_size = read(*clifd, filename, sizeof(filename));
	//pf("filename:%sn", filename);
	strncpy(filename2, filename, 50);
	strcat(filename2, " ");
	if (strstr(list, filename2) == NULL || strncmp(filename2, " ", 1) == 0 || strncmp(filename2, "  ", 2) == 0)
	{
		char result[6] = "error";
		pf("文件:%s 不存在,下载终止n", filename);

		write(*clifd, result, strlen(result));
		return;
	}
	else
	{
		char result[8] = "success";
		pf("文件:%s 存在,开始传输文件内容n", filename);
		write(*clifd, result, strlen(result));

		int fd = open(filename, O_RDONLY);

		//设置文件读写位置为文件尾部
		lseek(fd, 0, SEEK_END);
		// 获取文件字节数(尾部位置)
		off_t end_pos = lseek(fd, 0, SEEK_CUR);
		//pf("end_pos:%dn", end_pos);
		//设置文件读写位置为文件头部
		lseek(fd, 0, SEEK_SET);

		usleep(1000000);

		do
		{
			memset(buf, 0, sizeof(buf));
			r_size = read(fd, buf, sizeof(buf));
			pf("[读取文件字节数:%d ", r_size);
			w_size = write(*clifd, buf, r_size);
			pf("发送字节数:%d ", w_size);
			read(*clifd, result, sizeof(result));
			if(strncmp(result, "success", 10) == 0)
			{
				pf("成功收到客户端端返回的success]n");
			}
			usleep(10000);

			off_t cur_pos = lseek(fd, 0, SEEK_CUR);
			//pf("cur_pos:%dn", cur_pos);
			if(cur_pos == end_pos && w_size == 1024)
			{
				char end[1] = "";
				pf("[读取文件字节数:1 ");
				w_size = write(*clifd, end, sizeof(end));
				pf("发送字节数:%d ", w_size);
				read(*clifd, buf2, sizeof(buf2));
				
				if(strncmp(buf2, "success", 10) == 0)
				{
					pf("成功收到客户端端返回的success]n");
				}
				else
				{
					pf("收到客户端返回的异常数据:%s]n", buf2);
				}
				break;
			}
		} while (r_size == 1024);
		usleep(1000000);
		pf("    文件:%s 发送完毕n", filename);
		close(fd);
	}

	return;
}

// 文件列表
void c_list(int *clifd)
{
	DIR *dir;
	dir = opendir(".");
	char list[1024] = {};
	struct dirent *dirent;
	while ((dirent = readdir(dir)) != NULL)
	{
		strcat(list, dirent->d_name);
		strcat(list, " ");
	}

	int l_size = write(*clifd, list, strlen(list) + 1);

	memset(list, 0, 1024);

	char dirname[20] = {};
	int d_size = read(*clifd, dirname, sizeof(dirname));
	pf("收到客户端的数据:%sn", dirname);

	if(strncmp(dirname, "...", 20) == 0)
	{
		closedir(dir);
		return;
	}

	if (strncmp(dirname, ".", 20) == 0)
	{
		dir = opendir(".");
		while ((dirent = readdir(dir)) != NULL)
		{
			strcat(list, dirent->d_name);
			strcat(list, " ");
		}
		l_size = write(*clifd, list, strlen(list) + 1);
	}
	else if (strncmp(dirname, "..", 20) == 0)
	{
		chdir("..");
		dir = opendir(".");
		while ((dirent = readdir(dir)) != NULL)
		{
			strcat(list, dirent->d_name);
			strcat(list, " ");
		}
		l_size = write(*clifd, list, strlen(list) + 1);
	}
	else
	{
		int re = chdir(dirname);
		if (re == -1)
		{
			char result[20] = "目录名错误";
			int err = write(*clifd, result, strlen(result) + 1);
		}
		else
		{
			dir = opendir(".");
			while ((dirent = readdir(dir)) != NULL)
			{
				strcat(list, dirent->d_name);
				strcat(list, " ");
			}
			l_size = write(*clifd, list, strlen(list) + 1);
		}
	}
	closedir(dir);
	return;
}

客户端:client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include "tools.h"

#define pf printf

int sockfd = 0;

typedef struct LS
{
	char mode[15];	// 文件的模式
	int dir_num;	// 是否目录或目录中包含目录的数量
	char user[20];	// 文件的用户名
	char group[20]; // 文件的组名
	long size;		// 文件的字节数
	char time[30];	// 文件的最后修改时间
	int st_mode;	// 文件类型和权限
	char name[20];	// 文件名
} LS;

// 菜单
void menu(void);
// 上传
void upload(void);
// 下载
void download(void);
// 显示服务器目录和文件
void s_list(void);
// 显示客户端目录和文件
void c_list(void);
// 退出程序
void quit(void);

// 主函数
int main()
{
	pf("服务器创建socket...n");
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (0 > sockfd)
	{
		perror("socket");
		return -1;
	}

	pf("准备地址...n");
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	// 端口自己修改
	addr.sin_port = htons(60000);
	// IP自行修改
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	socklen_t len = sizeof(addr);

	pf("绑定连接服务器...n");
	if (connect(sockfd, (struct sockaddr *)&addr, len))
	{
		perror("connect");
		return -1;
	}

	menu(); // 加载菜单

	close(sockfd);

	return 0;
}

// 加载菜单
void menu(void)
{
	for (;;)
	{
		system("clear");
		pf("*** 局域网文件传输客户端 ***n");
		pf("     1、上传n");
		pf("     2、下载n");
		pf("     3、查看/修改服务端目录n");
		pf("     4、查看客户端目录n");
		pf("     0、退出n");
		pf("--------------------------n");
		// 我的自定义函数 在tools.c里面 get_cmd
		switch (get_cmd('0', '4'))
		{
			case '1':
				upload();
				break;
			case '2':
				download();
				break;
			case '3':
				s_list();
				break;
			case '4':
				c_list();
				break;
			case '0':
				quit();
				return;
		}
	}
}

// 上传
void upload(void)
{
	char up[20] = "我想上你";
	write(sockfd, up, strlen(up) + 1);

	// 打印当前目录文件
	c_list();

	int r_size = 0;
	int w_size = 0;
	char buf[1024] = {};
	char buf2[20] = {};
	r_size = read(sockfd, buf, sizeof(buf));
	if(strncmp(buf, "success", 10) != 0)
	{
		pf("收到服务端异常数据n");
		getch();
		return;
	}

	char pathname[100] = {};
	char *filename = malloc(50);
	memset(filename, 0, 50);
	while(1)
	{
		pf("请输入文件名:");
		get_str(pathname, 100);
		if(!strncmp(pathname, "..", 3) || !strncmp(pathname, ".", 3))
		{
			pf(".或..不是普通文件,请重新输入!n");
			continue;
		}
		break;
	}	

	int fd = open(pathname, O_RDONLY);

	struct stat stat = {};
	int fs = fstat(fd, &stat);
	long file_size = 0;
	file_size = stat.st_size;

	if (fd == -1)
	{
		pf("文件不存在n");
		write(sockfd, "error", 6);
		getch();
	}
	else
	{
		write(sockfd, "success", 8);
		usleep(100000);

		if (strrchr(pathname, '/') == NULL)
		{
			strncpy(filename, pathname, 50);
		}
		else
		{
			filename = strrchr(pathname, '/');
			filename += 1;
		}

		pf("发送文件名:%s 至服务端n", filename);
		write(sockfd, filename, strlen(filename) + 1);

		usleep(100000);

		memset(buf, 0, sizeof(buf));
		r_size = read(sockfd, buf, sizeof(buf));
		if(strncmp(buf, "success", 10) != 0)
		{
			pf("收到服务端异常数据n");
			getch();
			return;
		}
		else
		{
			pf("收到服务端返回successn");
		}
		
		sleep(1);

		//设置文件读写位置为文件尾部
		lseek(fd, 0, SEEK_END);
		// 获取文件字节数(尾部位置)
		off_t end_pos = lseek(fd, 0, SEEK_CUR);
		//pf("end_pos:%dn", end_pos);
		//设置文件读写位置为文件头部
		lseek(fd, 0, SEEK_SET);

		do
		{
			//pf("  进入while循环...n");
			r_size = read(fd, buf, 1024);

			pf("[读取文件字节数:%d ", r_size);

			w_size = write(sockfd, buf, r_size);
			pf("发送字节数:%d ", w_size);

			read(sockfd, buf2, sizeof(buf2));
			if(strncmp(buf2, "success", 10) == 0)
			{
				pf("成功收到服务端返回的success]n");
			}
			usleep(10000);

			off_t cur_pos = lseek(fd, 0, SEEK_CUR);
			//pf("cur_pos:%dn", cur_pos);
			if(cur_pos == end_pos && w_size == 1024)
			{
				char end[1] = "";
				pf("[读取文件字节数:1 ");
				w_size = write(sockfd, end, sizeof(end));
				pf("发送字节数:%d ", w_size);
				read(sockfd, buf2, sizeof(buf2));
				
				if(strncmp(buf2, "success", 10) == 0)
				{
					pf("成功收到服务端返回的success]n");
				}
				else
				{
					pf("收到服务端返回的异常数据:%s]n", buf2);
				}
				break;
			}
		} while (r_size == 1024);

		close(fd);

		char result[20] = {};
		read(sockfd, result, sizeof(result));
		if(strncmp(buf2, "success", 10) == 0)
		{
			pf("成功收到服务端返回值:%s,服务器接收文件成功n", result);
		}
		else if(strncmp(buf2, "error", 10) == 0)
		{
			pf("成功收到服务端返回值:%s,服务器接收文件异常n", result);
		}
		else
		{
			pf("收到服务端返回值:%s,数据异常n", result);
		}
			
		getch();
	}

	return;
}

// 下载
void download(void)
{
	int r_size = 0;
	int w_size = 0;
	char buf[1024] = {};
	char filename[50] = {};
	char list[1024] = {};
	char down[20] = "我想下你";
	write(sockfd, down, strlen(down) + 1);

	r_size = read(sockfd, buf, sizeof(buf));
	if(strncmp(buf, "success", 10) != 0)
	{
		pf("收到服务端异常数据n");
		getch();
		return;
	}
	else
	{
		pf("服务端成功接收命令n");
	}

	read(sockfd, list, sizeof(list));
	pf("服务端目录列表:%sn", list);

	usleep(1000);

	while(1)
	{
		pf("请输入要下载的文件名:");
		get_str(filename, 50);
		if(!strncmp(filename, "..", 3) || !strncmp(filename, ".", 3))
		{
			pf(".或..不是普通文件,无法下载,请重新输入!n");
			continue;		
		}
		break;
	}
	write(sockfd, filename, strlen(filename) + 1);

	char result[20] = {};
	read(sockfd, result, sizeof(result));
	if(strncmp(result, "success", 8) == 0)
	{
		pf("收到服务端发送的数据:%s 文件准备下载n", result);
	}
	else if(strncmp(result, "error", 8) == 0)
	{
		pf("收到服务端发送的数据:%s 文件不存在n", result);
		getch();
		return;
	}
	else
	{
		pf("收到服务端发送的数据:%s 数据异常,下载终止n", result);
		getch();
		return;
	}

	int fd = open(filename, O_CREAT | O_RDWR, 0777);

	do
	{
		usleep(500);
		memset(buf, 0, sizeof(buf));
		r_size = read(sockfd, buf, sizeof(buf));
		pf("[收到字节数:%d ", r_size);
		w_size = write(fd, buf, r_size);
		pf("写入文件字节数:%d ", w_size);
		w_size = write(sockfd, "success", 8);
		pf("发送success给服务端]n");
	} while (r_size == 1024);
	usleep(1000000);
	pf("    文件:%s 下载完毕n", filename);
	close(fd);
	getch();
	return;
}

// 服务端文件列表
void s_list(void)
{
	char see[20] = "我想看你";
	write(sockfd, see, strlen(see) + 1);

	char list[1024] = {};
	read(sockfd, list, sizeof(list));
	pf("服务端目录列表: %sn", list);

	pf("输入cd+空格+目录,修改服务器工作目录,否则返回上一级n");
	char cmd[50] = {};
	get_str(cmd, 50);
	if (strstr(cmd, "cd ") == NULL)
	{
		pf("非cd指令,按任意键返回主界面n");
		snprintf(cmd, 20, "...");
		write(sockfd, cmd, strlen(cmd));
		getch();
	}
	else
	{
		char *dir = malloc(20);
		dir = strrchr(cmd, ' ');
		dir += 1;
		write(sockfd, dir, strlen(dir) + 1);

		read(sockfd, list, sizeof(list));
		pf("服务端目录列表: %sn", list);
		getch();
	}
	return;
}

// 客户端文件列表
void c_list(void)
{
	pf("当前目录列表:");
	DIR *dir;
	dir = opendir(".");
	struct dirent *dirent;
	while ((dirent = readdir(dir)) != NULL)
	{
		pf(" %s ", dirent->d_name);
	}
	pf("n");
	closedir(dir);
	getch();
	return;
}

// 退出程序
void quit(void)
{
	char buf[100] = {};
	pf("告知服务端,我要走了");
	char quit[20] = "我要走了";
	write(sockfd, quit, strlen(quit) + 1);

	usleep(10000);

	pf("程序退出n");
	return;
}

效果图:

1、首先我们 make 编译

2·然后我们把客户端放到1这个文件夹中,为了方便测试

接着我们运行服务端和客户端

3、我们先试试上传文件

可以看到我们的文件上传成功了,因为篇幅比较大,就简要展示了。

4、那么我们再试试下载功能

5、最后测试下我们的目录功能

我们将服务端的工作目录修改到上一级目录(也就是客户端同级目录)

指令要求(cd+空格+路径),那么我们就输入cd ..

看下效果

6、多线程

多线程奔溃问题已经做了相应处理,简单测试暂无问题,不过内存泄露倒是个问题0.0

那么功能就介绍完毕了,希望能给大家带来帮助(程序可能有些不足之处,如有建议,望赐教)

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Ikaros_521/article/details/99120280
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-04-11 06:48:02
  • 阅读 ( 1139 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢