网盘UI界面
大致的逻辑是这样的,定义一个函数,清空当前屏幕然后print界面内容
void net_disk_ui()
{
//清空屏幕并且打印UI界面
system("clear");
printf("=========================TCP网盘程序=================================\n");
printf("=========================功能菜单=================================\n");
printf("\t\t\t1、查询文件\n");
printf("\t\t\t2、下载文件\n");
printf("\t\t\t3、上传文件\n");
printf("\t\t\t0、退出系统\n");
printf("=====================================================================\n");
printf("请选择你要执行的操作: ");
}
注意system("clear");函数的调用需要头文件
#include<stdlib.h>
写完net_disk_ui函数之后我们在连接客户端之后调用一下即可
……
printf("客户端连接服务器成功!\n");
net_disk_ui();
……
查询功能的实现
查询功能:客户端按下1这个字符,来获取服务器端默认目录下的所有文件信息,并且把这些文件信息传递给客户端,客户端显示出来(文件信息包括文件名)
但是我们这一节先不管按下1的过程,我们实现的是一打开服务器,服务器就执行函数来查询文件并且发送给客户端(按下1的之后再实现)
服务器查询文件逐条打印
我们在服务器定义一个函数search_server_dir
来读取服务器文件目录下(默认设置为home目录 )的文件名,并且发给客户端
我们需要如下函数:
-
opendir
:这是一个用于打开目录的系统调用。它接受一个目录路径作为参数,然后返回一个指向DIR
结构的指针,该结构用于表示目录流。如果打开失败,它将返回NULL
。 -
readdir
:这是一个用于读取目录中的文件和子目录的库函数。它接受一个指向DIR
结构的指针,并返回一个指向struct dirent
结构的指针,该结构包含了目录项的信息,包括文件名。每次调用readdir
,它返回下一个目录项,直到目录中的所有项都被读取完毕,此时返回NULL
void search_server_dir(int accept_socket)
{
DIR* dp = opendir("/home/liujiajun");//定义一个变量表示文件的路径地址
if (NULL == dp)//如果打开目录失败
{
perror("open dir error:");
return;
}
struct dirent* dir = NULL;//定义一个变量表示读到的内容地址
while (1)//不停读取目录文件
{
dir = readdir(dp);
if (NULL == dir) //如果readdir函数返回是空值,全部目录读取完成
{
break;
}
if (dir->d_name[0] != '.')//把.隐藏文件过滤掉
{
printf("name=%s\n", dir->d_name);
}
}
}
写完之后在服务器启动之前来调用这个函数
……
int server_socket;//这是一个唯一标识套接字的整数
server_socket = socket(AF_INET, SOCK_STREAM, 0);
search_server_dir();
printf("开始创建TCP服务器\n");
……
消息的结构化+服务器内容传递给客户端的实现
接下来要做的就是把服务器的内容传递给客户端,但是传统的信息传递用的是字符串,我们如果把所有的文件名都拼接成字符串然后传递的话不是很方便,我们可以在服务器和客户端中都定义一个结构体MSG用来表示传递的信息(注意服务器和客户端的结构体的内容要一致)
#define MSG_TYPE_LOGIN 0
#define MSG_TYPE_FILENAME 1//宏定义表示类型增加可读性
typedef struct msg {
int type;//协议类型: 0 表示登录协议包,1表示文件名传输包
int flag;//后面用到
char buffer[128];//存放除了文件名以外的内容
char fname[50];//如果type是1(上面用宏定义增加可读性),就是文件名传输包,那么fname里面就存放文件名
}MSG;//这个结构体会根据业务需求而不断变化,结构体后面可能会添加新的字段
原来文件名只是在服务器中打开然后直接printf,并没有整理成信息传递给客户端,所以我们要在服务器之前的search_server_dir()中增加一些内容把信息放到MSG结构体里面。修改后的代码如下,新增的部分添加了注释
void search_server_dir(int accept_socket)//因为函数里面调用了write函数,需要用到套接字
{
//opendir是打开Linux目录的api函数
struct dirent* dir = NULL;
int res = 0;//存储实际发送信息的字节数
MSG info_msg = { 0 };//定义信息的结构体并且初始化
info_msg.type = MSG_TYPE_FILENAME;//设置文件类型
DIR* dp = opendir("/home/liujiajun");
if (NULL == dp)
{
perror("open dir error:");
return;
}
while (1)
{
dir = readdir(dp);
if (NULL == dir) //如果readdir函数返回是空值,全部目录读取完成
{
break;
}
if (dir->d_name[0] != '.')//把.隐藏文件过滤掉
{
printf("name=%s\n", dir->d_name);
memset(info_msg.fname, 0, sizeof(info_msg.fname);//每一次输出之后都要把存放文件名的空间重新刷新
strcpy(info_msg.fname, dir->d_name);//把每一个文件名拷贝到info_msg结构体里面,通过socket发送出去
res = write(accept_socket, &info_msg, sizeof(MSG));//把信息发送给客户端
if (res < 0) {
perror("send client error:");
return ;
}
}
}
}
因为现在要把套接字发送出去,所以调用这个函数的地方也要改一下,不能在服务器启动之前就直接调用,我们改成在线程函数里面调用
void* thread_fun(void* arg) {
int acpt_socket = *(int*)arg;
int res;
char buffer[50];
search_server_dir(acpt_socket);/*这里调用发送函数!!!!*/
printf("目录信息发送给客户端完成\n");
while (1) {
res = read(acpt_socket, buffer, sizeof(buffer));
printf("client read %s\n", buffer);
//向accept_socket写入buffer中数据,写的数据的字节数为res
write(acpt_socket, buffer, res);
memset(buffer, 0, sizeof(buffer));//缓冲区清零,便于接收下一次的数据
}
}
我们服务器修改了,客户端也要修改一下代码来接收数据,增加的代码如下:
int res = 0;
MSG recv_msg = { 0 };/*定义接受的结构体*/
while (1) {
res = read(client_socket, &recv_msg, sizeof(MSG));//从服务器接受数据
if (recv_msg.type == MSG_TYPE_FILENAME) {//如果是接收文件的话,就把文件名打印出来并且清接收的缓存
printf("server path filename=%s\n", recv_msg.fname);
memset(&recv_msg, 0, sizeof(MSG));
}
}
//并且把原来的接受信息的代码注释掉
/*现在改用MSG来接受数据,这部分代码就不用啦!
//我们用户在客户端终端连续输入字符串,回车表示把数据发送出去
int res = 0;
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
res = write(client_socket, buffer, sizeof(buffer));
printf("send bytes=%d\n", res);
memset(buffer, 0, sizeof(buffer));
res = read(client_socket, buffer, sizeof(buffer));
printf("recv from server info:%s\n", buffer);
memset(buffer, 0, sizeof(buffer));
}
*/
补充内容:如果运行时出现了报错Address already in use如何解决
如果我们服务器程序退出之后,然后又立刻打开服务器程序,就是报这个错,这是因为ip地址和端口号是系统资源,必须把它设置为端口号可以重复使用
在服务器中增加下面的代码即可解决该问题
//如果运行时出现了报错Address already in use如何解决
int optvalue = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optvalue, sizeof(optvalue));
上述代码的解释:
-
int optvalue = 1;
:整数变量optvalue
将用于设置SO_REUSEADDR选项的值。 -
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optvalue, sizeof(optvalue));
:这一行代码使用setsockopt
函数来设置套接字选项。下面是各个参数的解释:-
server_socket
:这是一个套接字描述符,表示你想要设置选项的套接字。 -
SOL_SOCKET
:这是套接字选项的级别,它表示我们要设置的选项属于通用套接字选项。SO_REUSEADDR是通用选项之一。 -
SO_REUSEADDR
:这是要设置的具体选项,表示允许重用本地地址。这个选项的作用是,在套接字关闭后,允许其他套接字快速绑定到相同的本地地址,而不会出现"Address already in use"错误。 -
&optvalue
:这是一个指向存储选项值的内存位置的指针,这里是optvalue
变量的地址。1表示启用套接字重用 -
sizeof(optvalue)
:这是指定存储选项值的内存块的大小,通常使用sizeof
操作符来获取变量的大小。
-
全部代码如下
服务器部分
#include<cstdio>//C++标准库的头文件
#include<unistd.h>//Unix标准头文件
#include<sys/types.h>//这个头文件定义了各种系统相关的数据类型
#include<sys/socket.h>//这个头文件用于网络编程,包含了与套接字(socket)相关的函数和数据结构的声明
#include<arpa/inet.h>//通常用于处理IP地址和套接字地址的转换
#include<string.h>//字符串头文件,因为后面有用到memset
#include<pthread.h>//线程相关
#include<stdlib.h>
#include<dirent.h>
#define MSG_TYPE_LOGIN 0
#define MSG_TYPE_FILENAME 1//宏定义表示类型增加可读性
typedef struct msg {
int type;//协议类型: 0 表示登录协议包,1表示文件名传输包
int flag;//后面用到
char buffer[128];//存放除了文件名以外的内容
char fname[50];//如果type是1,就是文件名传输包,那么fname里面就存放文件名
}MSG;//这个结构体会根据业务需求而不断变化,结构体后面可能会添加新的字段
void search_server_dir(int accept_socket)//因为函数里面调用了write函数,需要用到套接字
{
//opendir是打开Linux目录的api函数
struct dirent* dir = NULL;
int res = 0;//存储实际发送信息的字节数
MSG info_msg = { 0 };//定义信息的结构体并且初始化
info_msg.type = MSG_TYPE_FILENAME;//设置文件类型
DIR* dp = opendir("/home/liujiajun");
if (NULL == dp)
{
perror("open dir error:");
return;
}
while (1)
{
dir = readdir(dp);
if (NULL == dir) //如果readdir函数返回是空值,全部目录读取完成
{
break;
}
if (dir->d_name[0] != '.')//把.隐藏文件过滤掉
{
printf("name=%s\n", dir->d_name);
memset(info_msg.fname, 0, sizeof(info_msg.fname));//每一次输出之后都要把存放文件名的空间重新刷新
strcpy(info_msg.fname, dir->d_name);//把每一个文件名拷贝到info_msg结构体里面,通过socket发送出去
res = write(accept_socket, &info_msg, sizeof(MSG));//把信息发送给客户端
if (res < 0) {
perror("send client error:");
return ;
}
}
}
}
void* thread_fun(void* arg) {
int acpt_socket = *(int*)arg;
int res;
char buffer[50] = { 0 };
search_server_dir(acpt_socket);//调用发送函数
printf("目录信息发送给客户端完成\n");
while (1) {
res = read(acpt_socket, buffer, sizeof(buffer));
printf("client read %s\n", buffer);
//向accept_socket写入buffer中数据,写的数据的字节数为res
write(acpt_socket, buffer, res);
memset(buffer, 0, sizeof(buffer));//缓冲区清零,便于接收下一次的数据
}
}
int main() {
//创建一个套接字描述符
int server_socket;//这是一个唯一标识套接字的整数
server_socket = socket(AF_INET, SOCK_STREAM, 0);
/*
创建了一个套接字,并将其文件描述符存储在 server_socket 变量中
AF_INET 表示IPv4地址族
SOCK_STREAM: 这是套接字类型,表示创建的套接字将使用面向连接的TCP协议
0: 这是套接字的协议参数,通常设置为0
*/
printf("开始创建TCP服务器\n");
struct sockaddr_in server_addr;//存储套接字信息的变量
server_addr.sin_family = AF_INET;//指定了地址族为 AF_INET
server_addr.sin_addr.s_addr = INADDR_ANY;//表示服务器将接受来自任何可用网络接口的连接请求
server_addr.sin_port = htons(6666);//端口号不可以直接用数字赋值,htons将主机字节序(通常是小端字节序)的端口号转换为网络字节序(大端字节序)
//如果运行时出现了报错Address already in use如何解决
int optvalue = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optvalue, sizeof(optvalue));
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("server bind error:");
return 0;
}
if (listen(server_socket, 10) < 0) {
perror("server listen error:");
return 0;
}
//printf("~~~~~~~~~~~~~\n");
printf("TCP服务器准备完成,等待客户端的连接\n");
//printf("~~~~~~~~~~~~~\n");
int accept_socket;//创建一个存储接受到的客户端连接的套接字文件描述符。
int res = 0;//后续用到
char buffer[50] = { 0 };//定义缓冲区,用于暂时存储接收和发送的数据
pthread_t thread_id;//线程编号
while (1) //服务器将持续接收和发送数据,直到手动停止程序。
{
accept_socket = accept(server_socket, NULL, NULL);
printf("有客户端连接到服务器!\n");
//创建一个线程
//第三个参数是执行线程的函数
pthread_create(&thread_id, NULL, thread_fun, &accept_socket);
//read函数就是接受客户端发来的数据,存储到buffer里面,返回值表示实际上从accept_socket那边读取到的字节数
res = read(accept_socket, buffer, sizeof(buffer));
printf("client read %s\n", buffer);
//向accept_socket写入buffer中数据,写的数据的字节数为res
write(accept_socket, buffer, res);
memset(buffer, 0, sizeof(buffer));//缓冲区清零,便于接收下一次的数据
}
return 0;
}
客户端部分:
#include<cstdio>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#define MSG_TYPE_LOGIN 0
#define MSG_TYPE_FILENAME 1//宏定义表示类型增加可读性
typedef struct msg {
int type;//协议类型: 0 表示登录协议包,1表示文件名传输包
int flag;//后面用到
char buffer[128];//存放除了文件名以外的内容
char fname[50];//如果type是1,就是文件名传输包,那么fname里面就存放文件名
}MSG;//这个结构体会根据业务需求而不断变化,结构体后面可能会添加新的字段
void net_disk_ui()
{
//清空屏幕并且打印UI界面
system("clear");
printf("=========================TCP网盘程序=================================\n");
printf("=========================功能菜单=================================\n");
printf("\t\t\t1、查询文件\n");
printf("\t\t\t2、下载文件\n");
printf("\t\t\t3、上传文件\n");
printf("\t\t\t0、退出系统\n");
printf("=====================================================================\n");
printf("请选择你要执行的操作: ");
}
int main() {
int client_socket;
int res = 0;
MSG recv_msg = { 0 };/*定义接受的结构体*/
char buffer[50] = { 0 };//创建缓冲区,后面会用到
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("client socket failed:");
return 0;
}
struct sockaddr_in server_addr;// server_addr,用于存储套接字的地址信息。
server_addr.sin_family = AF_INET;//AF_INET 表示IPv4地址族
server_addr.sin_addr.s_addr = inet_addr("192.168.43.128");//这里填Ubantu虚拟机的网卡IP地址,如果服务器和客户端在同一台机子上,则IP地址可以写成127.0.0.1
server_addr.sin_port = htons(6666);//端口号赋值
//创建好套接字之后,通过connect连接到服务器
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect error!");
return 0;
}
printf("客户端连接服务器成功!\n");
net_disk_ui();
while (1) {
res = read(client_socket, &recv_msg, sizeof(MSG));//从服务器接受数据
if (recv_msg.type == MSG_TYPE_FILENAME) {//如果是接收文件的话,就把文件名打印出来并且清接收的缓存
printf("server path filename=%s\n", recv_msg.fname);
memset(&recv_msg, 0, sizeof(MSG));
}
}
/*现在改用MSG来接受数据,这部分代码就不用啦!
//我们用户在客户端终端连续输入字符串,回车表示把数据发送出去
int res = 0;
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
res = write(client_socket, buffer, sizeof(buffer));
printf("send bytes=%d\n", res);
memset(buffer, 0, sizeof(buffer));
res = read(client_socket, buffer, sizeof(buffer));
printf("recv from server info:%s\n", buffer);
memset(buffer, 0, sizeof(buffer));
}
*/
return 0;
}
运行结果如下
服务器:
客户端;
标签:界面,socket,buffer,网盘,server,int,UI,printf,sizeof From: https://www.cnblogs.com/CS-liujiajun/p/17745045.html