1.题型
笔者近期讲解了如下的题:
基于TCP实现服务器和客户端通信,包括但不限于如下功能:
- 客户端文件上传
- 服务器文件下载
- 服务器目录查看
- 客户端主动退出
2.思路
- 将四个基本功能通过客户端的指令进行选择
- 服务器和客户端各自匹配对应的指令
- 将四个基本功能封装成一个API函数
- 服务器和客户端代码逻辑组合
- 服务器使用多线程并发服务器
3.服务器核心功能实现
客户端连接线程管理函数
void *thread_client_manage(void *arg)
{
int ret;
char buf[BUFSIZE];
client_info_in info = *(client_info_in *)arg; //结构体变量整体赋值
pthread_detach(pthread_self()); //将本线程分离 -- 在线程结束直接有系统回收空间
while(1){
//有客户端连接 先发送操作命令到客户端
my_diy_send(info.s_fd, buf, BUFSIZE, "\r\n\
*********************************************\r\n\
*******欢迎访问服务器,请输入您的操作指令********\r\n\
*** 下载文件(download files): fd ***\r\n\
*** 上传文件(upload files): fu ***\r\n\
*** 查看目录(check the directory): dir ***\r\n\
*** 退出连接(exit the connection): exit ***\r\n\
*********************************************");
printf("Waiting for the client to operate\r\n");
//处理客户端事物
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle;
}
printf("Start processing client requests\r\n");
if(strstr(buf, "fd") != NULL){
//客户端下载
printf("The client will download the file\r\n");
ret = file_download_client( arg );
if(ret < 0){
goto exit_handle;
}
}else if(strstr(buf, "fu") != NULL){
//客户端上传
printf("The client will upload the file\r\n");
ret = file_upload_client(arg);
if(ret < 0){
goto exit_handle;
}
}else if(strstr(buf, "dir") != NULL){
printf("The client whill check the directory\r\n");
ret = directory_check_client(arg);
if(ret < 0){
goto exit_handle;
}
}else if(strstr(buf, "exit") != NULL){
//退出连接
printf("The client exits the connection\r\n");
goto exit_handle_0;
}else{
goto exit_handle;
}
}
exit_handle:
printf("An error has occurred and the client is about to be shut down");
exit_handle_0:
close(info.s_fd);
pthread_exit(NULL); //客户端断开后,销毁本线程
}
客户端下载API函数:
int file_download_client(void *arg)
{
int ret;
char buf[BUFSIZE];
char path[BUFSIZE];
struct stat file_stat;
FILE *fp;
client_info_in info = *(client_info_in *)arg;
file_transfer file_transfer_t;
//提示客户端发送文件名
my_diy_send(info.s_fd, buf, BUFSIZE, "Command parsing succeeded. Please send the file name you want to download");
//获取下载的文件名
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle;
}
//将获取的文件名,补充路径 ./
snprintf(path, 2+strlen(buf),"%s%s", DOWNLOAD_DIR_DEF, buf);
printf("path = %s\r\n", path);
//查找该文件是否存在
fp = fopen(path, "rb"); //二进制流格式只读打开
if(fp == NULL){
my_diy_send(info.s_fd, buf, strlen(buf), "file is not exist");
goto exit_handle; //退出下载
}
//文件存在
stat(path, &file_stat); //获取文件信息
memset(buf, 0, BUFSIZE); //清除buf中的不需要的内容
sprintf(buf, "%s size = %ld", path, file_stat.st_size);
printf("file info:%s\r\n", buf);
send(info.s_fd, buf, strlen(buf), 0); //发送文件信息
//等待客户端确认下载
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle_2;
}
printf("buf = %s\r\n", buf);
if (((strstr(buf, "yes") == NULL) && (strstr(buf, "YES") == NULL)) || (strstr(buf, "no") != NULL)){
//客户端确认失败
printf("The client refused to download\r\n");
my_diy_send(info.s_fd, buf, strlen(buf), "The client refused to download");
goto exit_handle_2;
}
//确认成功 开始发送文件
printf("The client confirms success and is about to start sending data\r\n");
my_diy_send(info.s_fd, buf, BUFSIZE, "You have confirmed downloading the file, and the transfer is about to begin");
usleep(100); //目的 -- 防止连续两次send相互影响
memset(&file_transfer_t, 0, sizeof(file_transfer_t));
file_transfer_t.total_size = file_stat.st_size; //获取文件总大小
while(1)
{
//读文件 --
memset(buf, 0, BUFSIZE);
file_transfer_t.cur_len = fread(buf, 1, BUFSIZE, fp);
//发送文件
send(info.s_fd, buf, file_transfer_t.cur_len, 0); //发送文件 长度按照实际读取的文件长度为准
file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
file_transfer_t.percent = (file_transfer_t.already_len * 100)/file_transfer_t.total_size;
printf("file download schdule:%d%%\r\n", file_transfer_t.percent);
if(file_transfer_t.already_len >= file_transfer_t.total_size){
//传输完成
printf("server transfer finish\r\n");
//接收客户端传输完成信息
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle_2;
}
break;
}
}
return 0;
exit_handle_2:
fclose(fp);
exit_handle:
return -1;
}
客户端上传API函数:
int file_upload_client(void *arg)
{
char buf[BUFSIZE];
char *p;
FILE *fp;
int ret;
client_info_in info = *(client_info_in *)arg;
file_transfer file_transfer_t;
//向客户端发送等待上传信息
my_diy_send(info.s_fd, buf, BUFSIZE, "After preparation, wait for the file to be uploaded!");
//等待用户上传 文件名及文件信息
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle;
}
//回显文件名 并解析文件大小
p = strstr(buf, "size="); //找到size位置
p += strlen("size="); //偏移到size后的尺寸位置
memset(&file_transfer_t, 0, sizeof(file_transfer_t));
file_transfer_t.total_size = atoi(p); //转换为文件大小
snprintf(file_transfer_t.filename, p-buf-strlen("size="), "%s", buf); //文件名
// printf("buf:%s\r\n", buf); //打印输出
my_send(info.s_fd, buf); //回显到客户端
//由客户端 确认 文件上传
ret = my_recv(info.s_fd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle;
}
if(((strstr(buf, "yes") == NULL) || (strstr(buf, "YES") == NULL))
&& ((strstr(buf, "no") != NULL) || (strstr(buf, "NO") != NULL))){
printf("The client cancelled the upload\r\n");
goto over;
}
//创建客户端上传操作文件
sprintf(file_transfer_t.path, "%s%s", UPLOAD_DIR_DEF, file_transfer_t.filename);//合成文件路径
fp = fopen(file_transfer_t.path, "wb");
if(fp == NULL){
perror("fopen");
goto exit_handle_1;
}
printf("Waiting for the upload\r\n");
//准备接收用户上传文件到服务器
while(1){
//接收客户端数据
file_transfer_t.cur_len= my_recv_not_print(info.s_fd, buf, BUFSIZE);
if(file_transfer_t.cur_len < 0){
goto exit_handle_1;
}
//写入文件 - 写入的数据长度以实际接收的长度为准
ret = fwrite(buf, 1, file_transfer_t.cur_len, fp);
file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
file_transfer_t.percent = (file_transfer_t.already_len * 100) / file_transfer_t.total_size;
printf("file download schdule:%d%%\r\n", file_transfer_t.percent);
if (file_transfer_t.already_len >= file_transfer_t.total_size)
{
//传输完成
printf("The file upload finish\r\n");
fclose(fp);
my_send(info.s_fd, "upload over");
usleep(100);//当退出本函数,会继续发送数据,需要适当延时区分两次 send
break;
}
}
over:
return 0;
exit_handle_1:
fclose(fp);
exit_handle:
return -1;
}
客户端查看服务器目录函数
int directory_check_client(void *arg)
{
int files = 0, i = 0;
DIR *pdir;
char buf[BUFSIZE] = {0};
struct dirent *dirent_t;
char filenames[100][BUFSIZE] = {0};
client_info_in info = *(client_info_in *)arg;
//打开目录 - 默认路径
pdir = opendir(UPLOAD_DIR_DEF);
if(pdir == NULL){
perror("opendir");
goto exit_handle;
}
//获取文件名
dirent_t = readdir(pdir);
if(dirent_t == NULL){
perror("readdir");
goto exit_handle_1;
}
while((dirent_t != NULL) && (files < 1024)){
sprintf(filenames[files++], "%s", dirent_t->d_name); //保存文件名
// printf("%02d:%s\r\n", files-1, dirent_t->d_name);
dirent_t = readdir(pdir); //继续 读取目录
}
//关闭目录
closedir(pdir);
//发送目录
printf("files = %d\r\n", files);
memset(buf, '\0', BUFSIZE);
for(i = 0; i < files; i++){
//如果空间超过BUFSIZE,不再整合文件名 - 视情况优化
if(strlen(buf) + strlen(filenames[i]) > BUFSIZE){
break;
}
strcat(buf, filenames[i]);
strcat(buf, "\r\n");
}
my_send(info.s_fd, buf);
usleep(100);//当退出本函数,会继续发送数据,需要适当延时区分两次 send
return 0;
exit_handle_1:
closedir(pdir);
exit_handle:
return -1;
}
4.客户端核心功能实现
操作指令识别函数
void *pthread_communication(void *arg)
{
char buf[BUFSIZE];
int ret;
int sockfd = *(int *)arg;
while (1)
{
//连接上服务器后 - 服务器先发送对应的操作命令到客户端
my_recv(sockfd, buf, BUFSIZE);
//输入服务器操作选项
my_diy_input_send(sockfd, buf, BUFSIZE, "Please enter action options to the server:");
if (strstr(buf, "fd") != NULL)
{
//文件下载服务
ret = file_download((void *)&sockfd);
if(ret < 0){
goto exit_handle;
}
}else if (strstr(buf, "fu") != NULL)
{
//文件上传服务
ret = file_upload((void *)&sockfd);
if(ret < 0){
goto exit_handle;
}
}else if (strstr(buf, "dir") != NULL)
{
//文件目录服务
ret = directory_check((void *)&sockfd);
if(ret < 0){
goto exit_handle;
}
}else if(strstr(buf, "exit") != NULL)
{
//退出连接
printf("About to disconnect\r\n");
goto exit_handle_0;
}else{
goto exit_handle;
}
}
exit_handle:
printf("An error has occurred and the system is about to exit");
exit_handle_0:
close(sockfd);
pthread_exit(NULL); //客户端断开后,销毁本线程
}
文件下载API函数
int file_download(void *arg)
{
char buf[BUFSIZE];
char path[BUFSIZE];
int ret;
char *p;
FILE *fp;
file_transfer file_transfer_t;
int sockfd = *(int *)arg;
//读取服务器提示信息
ret = my_recv(sockfd, buf, BUFSIZE);
if (ret == 0){
goto exit_handle;
}
//终端获取文件名 并 发送到服务器
my_diy_input_send(sockfd, buf, BUFSIZE, "Please enter the file name you want to download:");
//文件存在 -- 存在服务器会发送文件信息 -- 文件大小
ret = my_recv(sockfd, buf, BUFSIZE);
if (ret == 0){
goto exit_handle;
}
if (strstr(buf, "file is not exist") != NULL){
goto exit_handle;
}
//保存下文件的大小
p = strstr(buf, "size");
p += strlen("szie = "); // S->C:./test.c size = 3480
memset(&file_transfer_t, 0, sizeof(file_transfer_t));//清空传输文件信息结构体
file_transfer_t.total_size = atoi(p); //将字符串转换为整数
//客户端下载 选择 - 保存的路径
memset(buf, 0, BUFSIZE);
printf("please choose your path to save the file:\r\n");
read(STDIN_FILENO, buf, sizeof(buf));
snprintf(path, strlen(buf)+strlen(DOWNLOAD_DIR_DEF), "%s%s", DOWNLOAD_DIR_DEF, buf); //仅提取字符串中的有效字符数据到path中
printf("The file save path: %s\r\n", path);
fp = fopen(path, "wb");
if (fp == NULL){
perror("fopen");
goto exit_handle_2;
}
//客户端需要回复 - yes 进行确认下载
my_diy_input_send(sockfd, buf, BUFSIZE, "file download, yes or no?");
if (((strstr(buf, "yes") == NULL) && (strstr(buf, "YES") == NULL)) || (strstr(buf, "no") != NULL)){
printf("Client cancle download\r\n");
send(sockfd, "Client cancle download", strlen(buf), 0);
}
//接收服务器确认信息
ret = my_recv(sockfd, buf, BUFSIZE);
if (ret == 0){
goto exit_handle;
}
while (1)
{
//接收下载文件
file_transfer_t.cur_len = my_recv_not_print(sockfd, buf, BUFSIZE);
if (ret < 0)
{
goto exit_handle_2;
}
//写入文件中
ret = fwrite(buf, 1, file_transfer_t.cur_len, fp);
file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
file_transfer_t.percent = (file_transfer_t.already_len * 100) / file_transfer_t.total_size;
printf("file download schdule:%d%%\r\n", file_transfer_t.percent);
if (file_transfer_t.already_len >= file_transfer_t.total_size)
{
//传输完成
printf("file download finish\r\n");
fclose(fp);
send(sockfd, "download over", strlen("download over"), 0);
break;
}
}
return 0;
exit_handle_2:
fclose(fp);
exit_handle:
return -1;
}
文件上传API函数
int file_upload(void *arg)
{
int ret, pos;
int i = 0;
DIR *pdir;
FILE *fp;
struct stat fileinfo_t;
struct dirent *dirent_t;
file_transfer file_transfer_t;
char filenames[100][BUFSIZE] = {0};
char buf[BUFSIZE] = {0};
int sockfd = *(int *)arg;
//读取服务器提示上传信息
ret = my_recv(sockfd, buf, BUFSIZE);
if(ret < 0){
goto exit_handle;
}
//查看本目录文件
printf("The file list is as follows. Select a file to be uploaded");
pdir = opendir(UPLOAD_DIR_DEF);
dirent_t = readdir(pdir);
while((dirent_t != NULL) && (i < 1024)){
sprintf(filenames[i], "%s", dirent_t->d_name); //保存文件名
printf("%02d:%s\r\n", i++, dirent_t->d_name);
dirent_t = readdir(pdir); //继续 读取目录
}
closedir(pdir);
//选择一个需要上传的文件 并 附加文件信息(size)
my_diy_input(buf, BUFSIZE, "Enter the corresponding file number to upload");
//打开文件获取信息
pos = atoi(buf);
sprintf(buf, "%s%s", UPLOAD_DIR_DEF, filenames[pos]);//按照文件名合成文件路径打开
printf("path:%s\r\n", buf);
fp = fopen(buf, "rb");
if(fp == NULL){
perror("fopen");
goto exit_handle;
}
ret = stat(buf, &fileinfo_t);
if(ret < 0){
perror("stat");
goto exit_handle_1;
}
//和服务器确认上传文件信息
sprintf(buf, "%s size=%ld", filenames[pos], fileinfo_t.st_size);//合成上传确认信息,带上文件大小-计算下载进度用
file_transfer_t.total_size = fileinfo_t.st_size;
// printf("%s \r\n", buf);
my_diy_send(sockfd, buf);
//等待服务器回显文件信息
ret = my_recv(sockfd, buf, BUFSIZE);
if(ret < 0){
goto exit_handle_1;
}
//和服务器确认上传
my_diy_input_send(sockfd, buf, BUFSIZE, "Whether to upload?(yes/no)");
if(((strstr(buf, "yes") == NULL) || (strstr(buf, "YES") == NULL))
&& ((strstr(buf, "no") != NULL) || (strstr(buf, "NO") != NULL))){
printf("The client cancelled the upload\r\n");
goto exit_handle_1;
}
//开始上传文件
while(1){
//读文件 --
memset(buf, 0, BUFSIZE);
file_transfer_t.cur_len = fread(buf, 1, BUFSIZE, fp);
//发送文件
send(sockfd, buf, file_transfer_t.cur_len, 0); //发送文件 长度按照实际读取的文件长度为准
file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
file_transfer_t.percent = (file_transfer_t.already_len * 100)/file_transfer_t.total_size;
printf("file upload schdule:%d%%\r\n", file_transfer_t.percent);
if(file_transfer_t.already_len >= file_transfer_t.total_size){
//传输完成
printf("client transfer finish\r\n");
//接收服务器传输完成信息
ret = my_recv(sockfd, buf, BUFSIZE);
if(ret == 0){
goto exit_handle_1;
}
// sleep(1);
usleep(100);//当退出本函数,会继续接收服务器数据,需要适当延时区分两次 recv
break;
}
}
return 0;
exit_handle_1:
fclose(fp);
exit_handle:
return -1;
}
目录查看API函数
int directory_check(void *arg)
{
int ret;
char buf[BUFSIZE] = {0};
int sockfd = *(int *)arg;
//等待服务器发送服务器目录 - BUFSIZE-1024范围可能超出,视情况优化
ret = my_recv(sockfd, buf, BUFSIZE);
if (ret == 0){
return -1;
}
return 0;
}
5.功能实现
客户端执行流程:
yao@yvm:network$ ./tcp_c_download 192.168.1.227 9999
Connecting to the server successfully
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files): fd ***
*** 上传文件(upload files): fu ***
*** 查看目录(check the directory): dir ***
*** 退出连接(exit the connection): exit ***
*********************************************
Please enter action options to the server:
fu
S->C:After preparation, wait for the file to be uploaded!
The file list is as follows. Select a file to be uploaded00:10-sale_ticket.c
01:05-mutex.c
02:..
03:09-sem_mutex.c
04:test.c
05:07-cond.c
06:08-sem.c
07:03-pthread_clean.c
08:r3.c
09:r2.c
10:02-pthread.c
11:tcp_c_download
12:.
13:04-no_mutex.c
14:11-mycp.c size
15:06-while.c
16:01-getthreadid.c
17:11-mycp.c
18:a.out
19:mycp
20:r.c
Enter the corresponding file number to upload
10
path:../thread/02-pthread.c
S->C:02-pthread.c size=1172
Whether to upload?(yes/no)
yes
file upload schdule:87%
file upload schdule:100%
client transfer finish
S->C:upload over
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files): fd ***
*** 上传文件(upload files): fu ***
*** 查看目录(check the directory): dir ***
*** 退出连接(exit the connection): exit ***
*********************************************
Please enter action options to the server:
dir
S->C:tcp_client
08-tcp_client_download.c
tcp_sever
04-tcp_client_recv_send.c
..
test-c
06-udp_client.c
udp_server
05-udp_server.c
udp_client
02-pthread.c
tcp_c_download
.
09-opendir.c
tcp_server
02-tcp_client.c
03-tcp_multi_pthread_server.c
tcp_s_download
test
01-tcp_server.c
a.out
07-tcp_server_download.c
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files): fd ***
*** 上传文件(upload files): fu ***
*** 查看目录(check the directory): dir ***
*** 退出连接(exit the connection): exit ***
*********************************************
Please enter action options to the server:
fd
S->C:Command parsing succeeded. Please send the file name you want to download
Please enter the file name you want to download:
03-tcp_multi_pthread_server.c
S->C:./03-tcp_multi_pthread_server.c size = 3453
please choose your path to save the file:
test_1.c
The file save path: ./test_1.c
file download, yes or no?
yes
S->C:You have confirmed downloading the file, and the transfer is about to begin
file download schdule:29%
file download schdule:59%
file download schdule:88%
file download schdule:100%
file download finish
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files): fd ***
*** 上传文件(upload files): fu ***
*** 查看目录(check the directory): dir ***
*** 退出连接(exit the connection): exit ***
*********************************************
Please enter action options to the server:
exit
About to disconnect
服务器执行流程
yao@yvm:network$ ./tcp_s_download
server waiting client……
client information:
client socket fd = 0
client inet:144.216.23.244
client port = 0
server waiting client……
Waiting for the client to operate
C->S:fu
Start processing client requests
The client will upload the file
C->S:02-pthread.c size=1172
C->S:yes
Waiting for the upload
file download schdule:87%
file download schdule:100%
The file upload finish
Waiting for the client to operate
C->S:dir
Start processing client requests
The client whill check the directory
files = 22
Waiting for the client to operate
C->S:fd
Start processing client requests
The client will download the file
C->S:03-tcp_multi_pthread_server.c
path = ./03-tcp_multi_pthread_server.c
file info:./03-tcp_multi_pthread_server.c size = 3453
C->S:yes
buf = yes
The client confirms success and is about to start sending data
file download schdule:29%
file download schdule:59%
file download schdule:88%
file download schdule:100%
server transfer finish
C->S:download over
Waiting for the client to operate
C->S:exit
Start processing client requests
The client exits the connection
源码下载: Linux服务器和客户端文件传输源码-Linux文档类资源-CSDN文库https://download.csdn.net/download/m0_51540567/86400299