服务器
#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>//用于操作目录和文件的头文件,列出目录中的文件和子目录
#include<fcntl.h>//包含了文件控制操作相关的常量和函数,例如打开文件、关闭文件、读取文件等
#define MSG_TYPE_LOGIN 0//表示登录类型
#define MSG_TYPE_FILENAME 1//表示查询文件目录操作类型
#define MSG_TYPE_DOWNLOAD 2//表示下载文件操作类型
#define MSG_TYPE_UPLOAD 3//表示上传文件操作类型
#define MSG_TYPE_UPLOAD_DATA 4//表示上传文件数据类型
typedef struct msg
{
int type;//协议类型 0 表示登陆包 1.文件名传输包 2.文件下载包 ……
char fname[50];//存放文件名
char buffer[1024];//存放文件数据
int bytes;//这个字段用来记录传输文件时每个数据包实际的文件字节数
}MSG; //这个结构体会根据业务需求的不断变化,可能会增减新的字段。
//扣1 实现查询文件目录的函数
//根据网盘客户端的业务需求,客户端想要查看服务器这边目录下的文件信息
//因此服务器必须设置一个功能,把某个目录下的文件名信息全部获取出来发给客户端
//默认情况下服务器的目录我们默认设置为/home
void search_server_dir(int accept_socket)//因为函数里面调用了write函数,需要用到套接字
{
struct dirent* dir = NULL; //存储目录信息的变量
int res = 0; //存储实际发送信息的字节数
MSG info_msg = { 0 }; //定义信息的结构体并且初始化
info_msg.type = MSG_TYPE_FILENAME;//设置类型为查询文件目录
DIR* dp = opendir("/home/liujiajun");//打开目录并且把指针存在dp变量里面
if (NULL == dp)
{
perror("open dir error:");
return;
}
while (1)
{
dir = readdir(dp);//读取指定目录下的下一个目录项,并将其信息存储在 dir 结构体指针中,用于遍历读取文件
if (NULL == dir) //如果readdir函数返回是空值,全部目录读取完成
{
break;
}
if (dir->d_name[0] != '.')//把.隐藏文件过滤掉
{
// 清空 info_msg.fname,并将当前文件名拷贝到 info_msg.fname 中
memset(info_msg.fname, 0, sizeof(info_msg.fname));//每一次读目录之后都要把存放文件名的空间重新刷新
strcpy(info_msg.fname, dir->d_name);
// 使用 write 函数将 info_msg 结构体通过套接字发送给客户端
res = write(accept_socket, &info_msg, sizeof(MSG));
if (res < 0) {//发送失败
perror("send client error:");
return;
}
}
}
}
//扣2 实现下载文件的函数
//打开服务器中的某个文件,读取内容,并使用socket网络发送给客户端
//具体文件先定一个zhuizhui.txt写死,后面延伸的时候修改
void server_file_download(int accept_socket)
{
MSG file_msg = { 0 };//表示传文件内容消息的结构体定义
int res = 0;//实际读写字节数
int fd; //文件描述符 linux认为所有设备都是文件。对文件的打开,对设备的读写都可以使用文件描述符概念
fd = open("/home/liujiajun/hello/hello2", O_RDONLY);//这里的文件路径要结合自己的来修改
if (fd < 0)//表示文件打开失败,则返回失败的原因
{
perror("file open error:");
return;
}
file_msg.type = MSG_TYPE_DOWNLOAD;//设置消息类型为下载文件
strcpy(file_msg.fname, "hello2");//复制到客户端生成创建新文件的文件名
//在读取文件并把文件传到客户端 这个时候,MSG结构中的buffer就是存放文件的内容,但是一般来说文件都超过1024字节,所以要发送多个包。而且这个MSG结构中
while ((res = read(fd, file_msg.buffer, sizeof(file_msg.buffer))) > 0) //当read用于读取文件的时候,当文件读到末尾之后,res=0
{ //res就是实际读取文件的字节数
//file_msg.bytes也表示此次读的消息的字节数
file_msg.bytes = res;
res = write(accept_socket, &file_msg, sizeof(MSG));//把file_msg内容发送给客户端
if (res <= 0)
{
perror("server send file error:");
}
memset(file_msg.buffer, 0, sizeof(file_msg.buffer));//清缓存便于下一次读取
}
}
//服务器的多线程函数
void* thread_fun(void* arg) {
int acpt_socket = *(int*)arg;//定义一个套接字描述符,把形参传过来
int res;//实际读写字节数变量
char up_file_name[20] = { 0 };//文件名变量
int fd = -1;//文件描述符变量
MSG recv_msg = { 0 };//接受信息的结构体变量
printf("目录信息发送客户端完成!\n");
while (1) {
res = read(acpt_socket, &recv_msg, sizeof(MSG));
if (res == 0) {
printf("客户端已经断开\n");
break;
}
if (recv_msg.type == MSG_TYPE_FILENAME) {//客户端指定任务为查询文件目录
search_server_dir(acpt_socket);//调查询文件目录的函数
memset(&recv_msg, 0, sizeof(MSG));//清缓存
}
else if (recv_msg.type == MSG_TYPE_DOWNLOAD)//客户端指定任务为下载文件
{
server_file_download(acpt_socket);//调下载文件目录的函数
memset(&recv_msg, 0, sizeof(MSG));//清缓存
}
else if (recv_msg.type == MSG_TYPE_UPLOAD)//客户端指定任务为上传文件
{
//要从客户端传过来的数据包的文件名里面获取文件名信息,然后创建文件。默认创建的文件夹是在home目录下
strcpy(up_file_name, recv_msg.fname);
//然后在home目录下创建文件,名字暂时写死,
//O_CREAT 表示如果文件不存在则创建
//O_WRONLY 表示文件将以写入方式打开,允许你写入文件的内容。
//0666 表示文件所有者、文件组和其他用户都有读取和写入权限。
fd = open("/home/liujiajun/css.txt", O_CREAT | O_WRONLY, 0666);
if (fd < 0)//创建文件失败
{
perror("create up file error:");
}
}
else if (recv_msg.type == MSG_TYPE_UPLOAD_DATA)//客户端上传文件数据的线程函数传消息过来了
{
//将 recv_msg.buffer 中的数据写入由文件描述符 fd 标识的文件
//写入的字节数由 recv_msg.bytes 指定
write(fd, recv_msg.buffer, recv_msg.bytes);
if (recv_msg.bytes < sizeof(recv_msg.buffer))//说明读到最后一轮了,因为实际读取的字节数连消息的缓冲区都填不满
{
printf("client uploaded file: %s\n", recv_msg.fname);
close(fd);
}
memset(&recv_msg, 0, sizeof(MSG));//清缓存
}
memset(&recv_msg, 0, sizeof(MSG));//清缓存
}
}
int main() {
int server_socket;//这是一个唯一标识套接字的整数
int accept_socket;//创建一个存储接受到的客户端连接的套接字文件描述符。
int res = 0;//后续用到
MSG recv_msg = { 0 };//接受消息的结构体变量
pthread_t thread_id;//线程编号
char buffer[50] = { 0 };//定义缓冲区,用于暂时存储接收和发送的数据
//第一步创建套接字描述符
printf("开始创建TCP服务器\n");
server_socket = socket(AF_INET, SOCK_STREAM, 0);
/*
创建了一个套接字,并将其文件描述符存储在 server_socket 变量中
AF_INET 表示IPv4地址族
SOCK_STREAM: 这是套接字类型,表示创建的套接字将使用面向连接的TCP协议
0: 这是套接字的协议参数,通常设置为0
*/
if (server_socket < 0) {
perror("socket create failed:");
return 0;
}
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));
//将服务器套接字server_socket绑定到指定的 IP 地址和端口号
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("server bind error:");
return 0;
}
//将服务器套接字设置为监听模式,以便它可以接收客户端的连接请求
//10 是服务器套接字可以同时处理的等待连接的最大客户端连接数
if (listen(server_socket, 10) < 0) {
perror("server listen error:");
return 0;
}
printf("TCP服务器准备完成,等待客户端的连接\n");
//服务器将持续接收和发送数据,直到手动停止程序。
while (1)
{
//这行代码用于接受客户端的连接请求,并返回一个新的套接字 accept_socket
accept_socket = accept(server_socket, NULL, NULL);
printf("有客户端连接到服务器!\n");
//创建一个线程
//thread_id是线程标识符
//thread_fun 是处理客户端请求的线程函数。
//&accept_socket 是传递给线程函数的参数,其中包含与客户端通信的套接字。
pthread_create(&thread_id, NULL, thread_fun, &accept_socket);
}
return 0;
}
标签:文件,res,服务器端,网盘,MSG,file,Linux,msg,include
From: https://www.cnblogs.com/CS-liujiajun/p/17750982.html