【C-项目】网盘(一期,线程池版)

ops/2024/9/19 17:10:04/ 标签: c语言, 服务器

网盘二期



概述

登录服务器后,即可浏览服务器的文件系统。通过命令上传或下载文件。


服务器使用线程池技术

  • 创建两个进程,主进程负责接收退出信号,子进程负责管理线程池。
  • 子进程中的主线程 (包工头):监听客户端请求,线程池中的线程请求,退出管道请求
    • 有客户端连接 (来活啦,哪个闲着呢赶紧干活),将其封装成任务,放入任务队列,通知线程池中的空闲线程处理任务
    • 退出管道就绪 (所有人,准备下班!),通知所有线程下班,将退出标志置位1,唤醒所有线程
  • 线程池中的线程 (工人)
    • 阻塞在条件变量上,等待任务到来 (摸鱼摸鱼)
    • 有任务到来,从条件变量上醒来 (老大发消息了,看看是啥东西)
    • 判断退出标志是否被置位,如果是,退出线程 (下班下班)
    • 处理任务 (干干干)
    • 任务结束,清理任务 (完活,继续摸鱼)


一期功能


客户端可以使用的命令

命令解析
ls显示当前工作目录的所有文件
pwd显示当前工作目录
cd [dir]切换工作目录
rm [filename]删除当前目录下的文件
mkdir [dir]创建一个新目录
puts [filename]上传文件
gets [filename]下载文件


启动


启动服务器

1、在服务器bin目录下使用Makefile,生成可执行文件

w@Ubuntu20:bin $ make

2、启动服务器

w@Ubuntu20:bin $ ./server ../conf/server.conf

Makefile

OBJS:=$(wildcard ../src/*.c)
server:$(OBJS)gcc $^ -o $@ -I../include -lpthread
clean:rm server
  • -I ../include:指定头文件路径
  • -lpthread:链接线程库


启动客户端

1、在客户端的bin的目录下使用Makefile,生成可执行文件

w@Ubuntu20:bin $ make

2、启动客户端

w@Ubuntu20:bin $ ./client client.conf

Makefile

OBJS:=$(wildcard ../src/*.c)
client:$(OBJS)gcc $^ -o $@ -I../include
clean:rm client


目录设计

服务器

  • bin:存放二进制文件
  • conf:存放配置文件
  • include:存放头文件
  • src:存放源文件
w@Ubuntu20:bin $ tree ..
..
├── bin
│   ├── Makefile
│   └── server
├── conf
│   └── server.conf
├── include
│   ├── func.h
│   ├── head.h
│   ├── task_queue.h
│   └── thread_pool.h
└── src├── disk_conf.c├── disk_func.c├── disk_handle.c├── disk_server.c├── epoll_ctl.c├── task_queue.c├── tcp_init.c└── thread_pool.c4 directories, 15 files

客户端

w@Ubuntu20:bin $ tree ..
..
├── bin
│   ├── client
│   └── Makefile
├── conf
│   └── client.conf
├── include
│   ├── func.h
│   └── head.h
└── src├── disk_client.c├── disk_conf.c├── disk_func.c└── tcp_connect.c4 directories, 9 files


配置文件


服务器配置文件 server.conf

存放服务器ip地址,服务器port端口,线程池中的线程数量

根据实际情况自行更改

server_ip = 192.168.160.129
server_port = 2000
thread_num = 5

客户端配置文件 client.conf

存放服务器ip地址,服务器port端口

根据实际情况自行更改

server_ip = 192.168.160.129
server_port = 2000


传输文件

使用自定义协议传输:先发送数据长度,再发送数据内容



使用类型

//接收发送缓冲区大小
#define BUF_SIZE 1024
//命令中最多有几个参数
#define MAX_WORDNUM 8
//命令一个参数的最大长度
#define MAX_WORDLEN 30//套接字类型
typedef int socket_t;//通讯类型
typedef struct {socket_t  _sess_fd ;             //对端套接字char _sess_buf[BUF_SIZE] ;  //缓冲区
}Session_t, *pSession_t ;//命令类型
typedef struct {int _argc;       //命令中有几个参数char _cmd[MAX_WORDNUM][MAX_WORDLEN];
}Cmd_t, *pCmd_t;//传输文件协议:小货车
typedef struct {int _data_len;//货车头,表示数据长度char _data[BUF_SIZE];//火车车厢,表示数据
}Truck_t;


服务器


主流程搭建


  1. 父进程---------------------->用来退出程序

    1. 注册SIGCHLD信号(信号到来时,通知子进程退出)
    2. 等待回收子进程资源
    3. 退出程序
  2. 子进程---------------------->管理线程池

    1. 创建一个服务器套接字,开始监听客户端的连接
    2. 创建并启动线程池
    3. 使用epoll管理服务器套接字和退出管道
    4. 监听epfd
      1. 如果有新客户连接,将其加入任务队列,通知线程池处理
      2. 如果收到父进程发来的退出消息,将退出标志置位,通知线程池退出,关闭线程池,销毁线程池,退出程序


工作线程

每个子线程的具体工作

void *child_handle(void *)

参数:任务队列地址(有客户端的套接字)

功能:处理任务(回应客户端的发来的命令)


  1. 循环运行

    1. 如果退出标志被置位,退出线程

    2. 从任务队列中取一个任务

    3. 循环处理任务(接收客户端发来的命令,直到客户退出)

      1. 先接受数据长度(如果为0表示客户退出,break)

      2. 再接收数据到缓冲区

      3. 从缓冲区中取出命令

      4. 如果是命令合法就执行,如果不合法就跳过



命令


ls

  • 功能:发送当前目录下所有文件信息

  • 函数:int do_ls(pSession_t ps);

  • 输入:通信类型(客户端套接字,缓冲区)

  • 实现

    1. 生成一个当前目录的目录流
    2. 循环读取目录流中的每一个目录项
      1. 根据目录项的信息,拼接一条格式化(比如文件类型、文件名、文件大小)后的文件信息
      2. 发送给客户端(先发信息长度,再发信息)
    3. 所有信息发完后,再发送一个结束标识符,告诉客户端命令已完成
    4. 关闭目录流

pwd

  • 功能:发送当前目录

  • 函数:int do_pwd(pSession_t ps);

  • 输入:通信类型(客户端套接字,缓冲区)

  • 实现

    1. 利用getcwd接口获取信息
    2. 发送给客户端(先发信息长度,再发信息)

cd

  • 功能:切换工作目录

  • 函数:int do_cd(pSession_t ps, pCmd_t pcmd);

  • 输入:通信类型(客户端套接字,缓冲区),命令类型

  • 实现

    1. 得到目标目录,就是命令的第二个参数
    2. 切换到目标目录
    3. 回应客户端,将切换后的目录发送过去(先发信息长度,再发信息)

rm

  • 功能:删除文件

  • 函数:int do_rm(pSession_t ps, pCmd_t pcmd);

  • 输入:通信类型(客户端套接字,缓冲区),命令类型

  • 实现

    1. 得到待删除文件,就是命令的第二个参数
    2. 在当前目录下查找是否存在
      1. 如果存在,删除文件,回应客户端删除成功(先发信息长度,再发信息)
      2. 如果不存在,回应客户端删除失败(先发信息长度,再发信息)

mkdir

  • 功能:创建一个新目录

  • 函数:int do_mkdir(pSession_t ps, pCmd_t pcmd);

  • 输入:通信类型(客户端套接字,缓冲区),命令类型

  • 实现

    1. 得到新目录名字,就是命令的第二个参数
    2. 查找此目录是否存在
      1. 如果存在,回应客户端创建失败(先发信息长度,再发信息)
      2. 如果不存在,创建目录,回应客户端创建成功(先发信息长度,再发信息)

gets

  • 功能:下载文件(服务器是下载,客户端是上传)

  • 函数:int do_gets(pSession_t ps, pCmd_t pcmd);

  • 输入:通信类型(客户端套接字,缓冲区),命令类型

  • 实现

    1. 得到文件名字,就是命令的第二个参数
    2. 先接传输标志,如果是1就继续,如果是0就退出程序
    3. 下载文件
      1. 创建一个同名文件
      2. 循环接收文件内容
        1. 先接收数据长度(如果为0表示数据传输完毕,退出)
        2. 根据长度,接收数据
        3. 将数据写入文件
      3. 关闭文件

puts

  • 功能:上传文件(服务器是上传,客户端是下载)

  • 函数:int do_puts(pSession_t ps, pCmd_t pcmd);

  • 输入:通信类型(客户端套接字,缓冲区),命令类型

  • 实现

    1. 得到文件名字,就是命令的第二个参数
    2. 判断该文件类型,如果是普通文件就发送传输标志1,如果不是就发送传输标志0
    3. 如果传输标志是0,退出程序
    4. 上传文件
      1. 打开待上传文件
      2. 循环上传文件内容
        1. 从文件中读取数据到缓冲区(如果返回值为0,表示文件全部读完,break)
        2. 根据读取的字节数量,发送数据(如果返回值为-1,表示对端断开,return -1)
      3. 文件发完后,发送结束标识符,通知对端传输完毕
      4. 关闭文件


客户端


主流程搭建

  1. 从配置文件中取出:服务器ip地址,port端口号
  2. 连接服务器
  3. 循环从stdin中读取命令
    1. stdin中读取一行数据(如果读到quit,结束程序)

    2. 将这一行数据,格式化成命令格式

    3. 判断命令是否合法

      1. 如果不合法,跳过此次循环
      2. 如果合法,将命令发送给服务器
    4. 等待服务器返回数据

      1. 如果命令是cdpwdrmmkdir

        1. 先接数据长度,再接数据,打印命令结果
      2. 如果命令是ls

        1. 循环接收数据
          1. 先接数据长度(如果为0,表示传输完毕,break)
          2. 再接数据,打印命令结果
      3. 如果命令是gets

        1. 下载文件
        2. 先接数据长度,再接数据,打印命令结果
      4. 如果命令是puts

        1. 上传文件
        2. 先接数据长度,再接数据,打印命令结果



代码


服务器代码(4个头文件,8个源文件)


head.h

#ifndef __HEAD_H__
#define __HEAD_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>//检查系统调用返回值
#define EXIT_CHECK(ret, num, msg) if (ret == num) {\do {\perror(msg);\exit(-1);\}while(0); }#endif

func.h

#ifndef __FUNC_H__
#define __FUNC_H__//接收发送缓冲区大小
#define BUF_SIZE 1024
//命令中最多有几个参数
#define MAX_WORDNUM 8
//命令一个参数的最大长度
#define MAX_WORDLEN 30//套接字类型
typedef int socket_t;//通讯类型
typedef struct {socket_t  _sess_fd ;             //对端套接字char _sess_buf[BUF_SIZE] ;  //缓冲区
}Session_t, *pSession_t ;//命令类型
typedef struct {int _argc;       //命令中有几个参数char _cmd[MAX_WORDNUM][MAX_WORDLEN];
}Cmd_t, *pCmd_t;//传输文件协议:小货车
typedef struct {int _data_len;//货车头,表示数据长度char _data[BUF_SIZE];//火车车厢,表示数据
}Truck_t;void conf_get(char *conf, char *ip, int *port, int *thread_num); //从配置文件中获取参数
socket_t tcp_init(char *ip, int port); //返回一个正在监听的tcp类型的服务器套接字
void *child_handle(void* p); //子线程的具体工作
int upload(int fd_socket, char *filename); //上传文件
int download(int fd_socket, char *filename); //下载文件
int epoll_add(int fd, int epfd);   //将fd加入epfd
int epoll_del(int fd, int epfd);   //将fd从epfd里删除
void print_cmd(pCmd_t pcmd); //打印命令
#endif


thread_pool.h

#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__#include "head.h"
#include "task_queue.h"//线程池
typedef struct {int _thread_num; //子线程数量pthread_t *_pthid;  //子线程数组TaskQueue_t _que;   //任务队列
}ThreadPool_t, *pThreadPool_t;int init_ThreadPool(pThreadPool_t pPool, int thread_num);   //初始化线程池
int boot_ThreadPool(pThreadPool_t pPool);   //启动线程池
int close_ThreadPool(pThreadPool_t pPool);  //关闭线程池
int destory_ThreadPool(pThreadPool_t pPool);                //销毁线程池#endif

task_queue.h

#ifndef __TASK_QUEUE_H__
#define __TASK_QUEUE_H__#include "head.h"
#include "func.h"//任务队列节点
typedef struct TaskNode {socket_t _clifd; //客户端套接字struct TaskNode *_pNext;
}TaskNode_t, *pTaskNode_t;//任务队列
typedef struct TaskQueue {int _size;  //队列大小pTaskNode_t _pHead; //队头pTaskNode_t _pTail; //队尾pthread_cond_t _cond; //条件变量pthread_mutex_t _mutex; //互斥锁char _flag; //线程退出标志(0-不退出 1-退出)
}TaskQueue_t, *pTaskQueue_t;int init_TaskQueue(pTaskQueue_t pQueue); //初始化任务队列
int push_TaskQueue(pTaskQueue_t pQueue, pTaskNode_t pNew);//入队
int get_TaskNode(pTaskQueue_t pQueue, pTaskNode_t *ppGet);//得到队头元素,同时出队#endif


disk_server.c

#include "head.h"
#include "func.h"
#include "thread_pool.h"//信号处理函数,实现异步退出线程池
//父进程收到信号后,通知子进程处理
int exitpipe[2];
void sig_handle(int signum)
{printf("signal [%d] is comming!\n", signum);write(exitpipe[1], &signum, 4);
}int main(int argc, char *argv[]) 
{//参数:配置文件路径if (2 != argc) {fprintf(stderr, "Args error!\n");return -1;}//建立父子通信的管道,父进程写exitpipe[1],子进程读exitpipe[0]pipe(exitpipe);//父进程用来接收退出信号if (fork()) {printf("main pid: %d\n", getpid());close(exitpipe[0]);//注册退出信号signal(SIGUSR1, sig_handle);//回收子进程资源,退出程序wait(NULL);printf("The thread_pool was already closed, all child_thread were exited!\n");exit(0);}//子进程管理各种请求:客户端,线程池,退出管道printf("child pid: %d\n", getpid());close(exitpipe[1]);//从配置文件中拿到 服务器ip和port,线程数量char ip[16] = "";int port = 0;int thread_num = 0;conf_get(argv[1], ip, &port, &thread_num);printf("ip:%s, port:%d, thread_num:%d\n", ip, port, thread_num);//建立tcp监听socket_t fd_server = tcp_init(ip, port);EXIT_CHECK(fd_server, -1, "socket_init");printf("DiskServer[ip:%s, port:%d] boot...\n", ip, port);//建立线程池并启动ThreadPool_t pool;init_ThreadPool(&pool, thread_num);boot_ThreadPool(&pool);printf("The thread_pool is already boot!\n");//将服务器套接字,退出管道加入epoll管理int epfd = epoll_create(1);epoll_add(fd_server, epfd);epoll_add(exitpipe[0], epfd);//监听epfdint ready_fd_num = 0;struct epoll_event evs[2];socket_t fd_client;while (1) {ready_fd_num = epoll_wait(epfd, evs, 2, -1);for (int i = 0; i < ready_fd_num; ++i) {if (evs[i].data.fd == fd_server) {//有新客户连接, 加入任务队列,通知子线程fd_client = accept(fd_server, NULL, NULL);pTaskNode_t pNew = (pTaskNode_t)calloc(1, sizeof(TaskNode_t));pNew->_clifd = fd_client;pthread_mutex_lock(&pool._que._mutex);//加锁push_TaskQueue(&pool._que, pNew);//入队pthread_cond_signal(&pool._que._cond);//通知子线程处理pthread_mutex_unlock(&pool._que._mutex);//解锁}else if (evs[i].data.fd == exitpipe[0]) {//父进程发来退出通知, 读取管道int signum;read(exitpipe[0], &signum, 4);printf("receive signal [%d] , ready exit!\n", signum);//将退出标志置位,唤醒所有线程,让它们自行退出pool._que._flag = 1;pthread_cond_broadcast(&pool._que._cond);//关闭线程池,回收线程池资源close_ThreadPool(&pool);destory_ThreadPool(&pool);//退出程序exit(0);}}}return 0;
}

disk_conf.c

#include "head.h"//得到一行中'='符号后面的字符串
static void set_arg(char *line, char *arg)
{//将指针偏移到字符=char *ptr = strchr(line, '=');if (NULL == ptr) {fprintf(stderr, "conf_file is error!\n");exit(-1);}//= 后面的字符串strcpy(arg, ptr + 2);
}//从配置文件中获取参数
void conf_get(char *conf, char *ip, int *port, int *thread_num)
{FILE *fp = fopen(conf, "r");char line[128] = "";char buf[128] = "";//得到ipfgets(line, sizeof(line), fp);line[strlen(line) - 1] = '\0';set_arg(line, ip);//得到portmemset(buf, 0, sizeof(buf));memset(line, 0, sizeof(line));fgets(line, sizeof(line), fp);line[strlen(line) - 1] = '\0';set_arg(line, buf);*port = atoi(buf);//得到thead_nummemset(buf, 0, sizeof(buf));memset(line, 0, sizeof(line));fgets(line, sizeof(line), fp);line[strlen(line) - 1] = '\0';set_arg(line, buf);*thread_num = atoi(buf);fclose(fp);
}

disk_handle.c

#include "head.h"
#include "func.h"
#include "task_queue.h"//静态函数的可见域是本文件
//向客户端发送命令结果
static int do_ls(pSession_t ps);
static int do_pwd(pSession_t ps);
static int do_cd(pSession_t ps, pCmd_t pcmd);
static int do_rm(pSession_t ps, pCmd_t pcmd);
static int do_mkdir(pSession_t ps, pCmd_t pcmd);
static int do_puts(pSession_t ps, pCmd_t pcmd);
static int do_gets(pSession_t ps, pCmd_t pcmd);
//将文件类型从int转成char*
static void file_type(mode_t mode, char *type);//线程清理函数
void clean_func(void *p)
{pTaskQueue_t pQue = (pTaskQueue_t)p;pthread_mutex_unlock(&pQue->_mutex);
}//子线程的具体工作,回应客户端发来的命令
void *child_handle(void *p)
{//参数是任务队列pTaskQueue_t pQue = (pTaskQueue_t)p;pTaskNode_t pCur = NULL;  //定义一个任务节点,用来接收任务pSession_t ps = (pSession_t)calloc(1, sizeof(Session_t)); //定义一个通信类型,用来传递客户端套接字和缓冲区int data_len = -1;  //数据长度Cmd_t cmd; //定义一个命令类型,用来接命令int ret = -1;while (1) {//如果退出标志为1,退出线程if (1 == pQue->_flag) {printf("thread exit!\n");free(ps);ps = NULL;pthread_exit(NULL);}//从任务队列中取一个任务pthread_mutex_lock(&pQue->_mutex);pthread_cleanup_push(clean_func, pQue);//线程清理if (0 == pQue->_size) {pthread_cond_wait(&pQue->_cond, &pQue->_mutex);//等待任务}get_TaskNode(pQue, &pCur);//得到任务pthread_mutex_unlock(&pQue->_mutex);//处理任务ps->_sess_fd = pCur->_clifd;//设置客户端套接字printf("client connected...\n");while (1) {memset(ps->_sess_buf, 0, BUF_SIZE);memset(&cmd, 0, sizeof(Cmd_t));//先接收数据长度ret = recv(ps->_sess_fd, &data_len, sizeof(data_len), 0);if (0 == ret || 0 == data_len) {//对端已断开close(ps->_sess_fd);printf("client exit!\n");break;//此次任务结束,退出循环}//再接收具体的数据recv(ps->_sess_fd, ps->_sess_buf, data_len, 0);//从接收缓冲区中读取命令memcpy(&cmd, ps->_sess_buf, sizeof(Cmd_t));print_cmd(&cmd);//判断是什么命令if (!strcmp("cd", cmd._cmd[0])) {do_cd(ps, &cmd);}else if (!strcmp("pwd", cmd._cmd[0])) {do_pwd(ps);}else if (!strcmp("ls", cmd._cmd[0])) {do_ls(ps);}else if (!strcmp("rm", cmd._cmd[0])) {do_rm(ps, &cmd);}else if (!strcmp("mkdir", cmd._cmd[0])) {do_mkdir(ps, &cmd);}else if (!strcmp("puts", cmd._cmd[0])) {do_puts(ps, &cmd);}else if (!strcmp("gets", cmd._cmd[0])) {do_gets(ps, &cmd);}else {//非法命令continue;}}//释放任务free(pCur);pCur = NULL;pthread_cleanup_pop(1);//线程清理}
}//将文件类型从int转成char*
static void file_type(mode_t mode, char *type)
{if (S_ISREG(mode)) {strncpy(type, "-", 1);}else if (S_ISDIR(mode)) {strncpy(type, "d", 1);}else if (S_ISFIFO(mode)) {strncpy(type, "p", 1);}else {strncpy(type, "o", 1);}
}static int do_ls(pSession_t ps)
{//生成目录流DIR *dirp = opendir("./");if (NULL == dirp) {int flag = -1;  //目录流打开失败,发送-1作为标志send(ps->_sess_fd, &flag, sizeof(flag), 0);return -1;}else {struct dirent *dir_info;int data_len = -1;//循环读取目录项while ((dir_info = readdir(dirp)) != NULL) {if (!strncmp(".", dir_info->d_name, 1) || !strncmp("..", dir_info->d_name, 2)) {continue;}//获取目录项的信息struct stat stat_info;memset(&stat_info, 0, sizeof(stat_info));stat(dir_info->d_name, &stat_info);//获取文件类型char type[1] = "";file_type(stat_info.st_mode, type);//拼接此文件信息memset(ps->_sess_buf, 0, BUF_SIZE);sprintf(ps->_sess_buf, "%-2s%-20s   %10ldB", type, dir_info->d_name, stat_info.st_size);//发送此文件信息data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(data_len), 0);//先发数据长度send(ps->_sess_fd, ps->_sess_buf, data_len, 0);}//此目录文件已读完, 发送0作为标志data_len = 0;send(ps->_sess_fd, &data_len, sizeof(data_len), 0);}closedir(dirp);return 0;
}static int do_pwd(pSession_t ps)
{//获取当前工作目录memset(ps->_sess_buf, 0, BUF_SIZE);getcwd(ps->_sess_buf, BUF_SIZE);//回应客户端int data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, ps->_sess_buf, data_len, 0);return 0;
}static int do_cd(pSession_t ps, pCmd_t pcmd)
{//拿到目标目录char dir[128] = "";strcpy(dir, pcmd->_cmd[1]);//切换目录chdir(dir);//回应客户端getcwd(dir, sizeof(dir));int data_len = strlen(dir);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, dir, data_len, 0);return 0;
}static int do_rm(pSession_t ps, pCmd_t pcmd)
{//查看文件是否存在DIR *dirp = opendir("./");struct dirent *dir_cur;while ((dir_cur = readdir(dirp)) != NULL) {if (!strcmp(dir_cur->d_name, pcmd->_cmd[1])) {break;}}closedir(dirp);if (dir_cur) {//删除文件char cmd[256] = "";sprintf(cmd, "rm -rf %s", pcmd->_cmd[1]);system(cmd);//回应客户端sprintf(ps->_sess_buf, "file [%s] removed success!", pcmd->_cmd[1]);}else {//文件不存在sprintf(ps->_sess_buf, "file [%s] removed failed!", pcmd->_cmd[1]);}int data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, ps->_sess_buf, data_len, 0);return 0;
}static int do_mkdir(pSession_t ps, pCmd_t pcmd)
{//查看新目录是否存在DIR *dirp = opendir("./");struct dirent *dir_cur;while ((dir_cur = readdir(dirp)) != NULL) {if (!strcmp(dir_cur->d_name, pcmd->_cmd[1]) && S_ISDIR(dir_cur->d_type)) {break;}}closedir(dirp);if (NULL == dir_cur) {//创建目录mkdir(pcmd->_cmd[1], 0775);//回应客户端, 创建成功sprintf(ps->_sess_buf, "create dir [%s] succeed!", pcmd->_cmd[1]);}else {//目录已存在, 创建失败sprintf(ps->_sess_buf, "the [%s] is already exist!", pcmd->_cmd[1]);}int data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, ps->_sess_buf, data_len, 0);return 0;
}static int do_puts(pSession_t ps, pCmd_t pcmd)
{//客户端是上传,服务器是下载if (0 == download(ps->_sess_fd, pcmd->_cmd[1])) {sprintf(ps->_sess_buf, "file [%s] upload succeed!", pcmd->_cmd[1]);}else {sprintf(ps->_sess_buf, "file [%s] upload failed!", pcmd->_cmd[1]);}int data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, ps->_sess_buf, data_len, 0);return 0;
}static int do_gets(pSession_t ps, pCmd_t pcmd)
{//客户端是下载,服务器是发送if (0 == upload(ps->_sess_fd, pcmd->_cmd[1])) {sprintf(ps->_sess_buf, "file [%s] download succeed!", pcmd->_cmd[1]);}else {sprintf(ps->_sess_buf, "file [%s] download failed!", pcmd->_cmd[1]);}int data_len = strlen(ps->_sess_buf);send(ps->_sess_fd, &data_len, sizeof(int), 0);send(ps->_sess_fd, ps->_sess_buf, data_len, 0);return 0;
}

disk_func.c

#include "head.h"
#include "func.h"//上传文件
int upload(int fd_socket, char *filename)
{//查看文件类型是否正确DIR *dirp = opendir("./");struct dirent *dir_cur;int flag = -1; //是否传输标志, 0-不传,1-传while ((dir_cur = readdir(dirp)) != NULL) {if (!strcmp(dir_cur->d_name, filename)) {if (S_ISREG(dir_cur->d_type)) {//如果是普通文件, 传输flag = 1;send(fd_socket, &flag, sizeof(int), 0);}else {//如果不是普通文件,不传flag = 0;send(fd_socket, &flag, sizeof(int), 0);}break;}}closedir(dirp);if (0 == flag) {return -1;}//定义一个小货车,用来传输文件Truck_t truck;memset(&truck, 0, sizeof(Truck_t));int ret = -1;//根据文件名打开传输文件int fd_file = open(filename, O_RDONLY);EXIT_CHECK(fd_file, -1, "open");#if 0//发文件大小struct stat file_info;memset(&file_info, 0, sizeof(file_info));fstat(fd_file, &file_info);truck._data_len = sizeof(file_info.st_size);memcpy(truck._data, &file_info.st_size, truck._data_len);ret = send(fd_socket, &truck, sizeof(int) + truck._data_len, 0);EXIT_CHECK(ret, -1, "send_filesize");
#endif//发文件内容while (1) {memset(truck._data, 0, sizeof(truck._data));//读取文件truck._data_len = read(fd_file, truck._data, BUF_SIZE);if (0 == truck._data_len) {//传输完成,通知对端truck._data_len = 0;send(fd_socket, &truck._data_len, sizeof(int), 0);//关闭传输文件close(fd_file);return 0;}//发送ret = send(fd_socket, &truck, sizeof(int) + truck._data_len, 0);printf("send_len: %d\n", ret);if (-1 == ret) {//客户端异常断开,退出printf("client already break!\n");return -1;}}
}//下载文件
int download(int fd_socket, char *filename)
{//先接收传输标志,如果是1就下载,如果是0就退出                         int flag = -1; recv(fd_socket, &flag, sizeof(int), 0); if (0 == flag) {return -1; } //打开或创建一个文件int fd_file = open(filename, O_WRONLY | O_CREAT, 0600);EXIT_CHECK(fd_file, -1, "open");#if 0//接收文件大小int filesize = 0;recv(fd_socket, &filesize, sizeof(int), 0);printf("filesize: %d", filesize);
#endif//接收文件内容Truck_t truck;while (1) {memset(&truck, 0, sizeof(truck));//接收数据长度recv(fd_socket, &truck._data_len, sizeof(truck._data_len), 0);if (0 == truck._data_len) {//文件传输完毕break;}//接收数据内容recv(fd_socket, truck._data, truck._data_len, MSG_WAITALL);write(fd_file, truck._data, truck._data_len);}close(fd_file);return 0;
}//打印命令
void print_cmd(pCmd_t pcmd)
{printf("cmd: ");for (int i = 0; i < pcmd->_argc; ++i) {printf("%s ", pcmd->_cmd[i]);}printf("\n");
}

tcp_init.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>#define ERROR_CHECK(ret, num, msg) { if (ret == num) {\perror("msg");  return -1;} }//输入:服务器的ip地址,端口号
//输出:绑定了服务器ip和端口的,正在监听的套接字
int tcp_init(char *ip, int port)
{//生成一个tcp类型的套接字int sfd = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(sfd, -1, "ser_socket");//将端口号设置为可重用, 不用再等待重启时的TIME_WAIT时间int reuse = 1;setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));//给套接字绑定服务端ip和portstruct sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(struct sockaddr_in));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(ip);serverAddr.sin_port = htons(port);int ret = bind(sfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));ERROR_CHECK(ret, -1, "ser_bind");//将套接字设为监听模式,并指定最大监听数(全连接队列的大小)ret = listen(sfd, 10); ERROR_CHECK(ret, -1, "ser_listen");/* printf("[ip:%s, port:%d] is listening...\n", ip, port); */return sfd;
}

thread_pool.c

#include "head.h"
#include "func.h"
#include "thread_pool.h"
#include "task_queue.h"//初始化线程池
int init_ThreadPool(pThreadPool_t pPool, int thread_num)
{pPool->_boot = 0;pPool->_thread_num = thread_num;pPool->_pthid = (pthread_t*)calloc(thread_num, sizeof(pthread_t));init_TaskQueue(&pPool->_que);return 0;
}//启动线程池
int boot_ThreadPool(pThreadPool_t pPool)
{if (0 == pPool->_boot) {for (int i = 0; i < pPool->_thread_num; ++i) {pthread_create(pPool->_pthid + i, NULL, child_handle, &pPool->_que);}pPool->_boot = 1;}return 0;
}//关闭线程池
int close_ThreadPool(pThreadPool_t pPool) 
{if (1 == pPool->_boot) {for (int i = 0; i < pPool->_thread_num; ++i) {pthread_join(pPool->_pthid[i], NULL);}pPool->_boot = 1;}return 0;
}//销毁线程池资源
int destory_ThreadPool(pThreadPool_t pPool)
{for (int i = 0; i < pPool->_thread_num; ++i) {free(pPool->_pthid + i);}pPool->_boot = 0;return 0;
}

task_queue.c

#include "head.h"
#include "task_queue.h"//初始化任务队列
int init_TaskQueue(pTaskQueue_t pQueue)
{pQueue->_size = 0;pQueue->_pHead = pQueue->_pTail = NULL;pthread_cond_init(&pQueue->_cond, NULL);pthread_mutex_init(&pQueue->_mutex, NULL);pQueue->_flag = 0;return 0;
}//入队
int push_TaskQueue(pTaskQueue_t pQueue, pTaskNode_t pNew)
{if (NULL == pQueue->_pHead) {pQueue->_pHead = pQueue->_pTail = pNew;}else {pQueue->_pTail->_pNext = pNew;pQueue->_pTail = pNew;}++pQueue->_size;return 0;
}//得到队头元素,同时出队
int get_TaskNode(pTaskQueue_t pQueue, pTaskNode_t *ppGet)
{//没有元素if (0 == pQueue->_size) {return -1;}//有元素,取出,更新队头*ppGet = pQueue->_pHead;pQueue->_pHead = pQueue->_pHead->_pNext;//只有一个元素,更新队尾if (1 == pQueue->_size) {pQueue->_pTail = NULL;}//减小队列长度--pQueue->_size;return 0;
}

epoll_ctl.c

#include "head.h"//将fd加入epfd
int epoll_add(int fd, int epfd)
{struct epoll_event event;memset(&event, 0, sizeof(event));event.events = EPOLLIN;event.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);return 0;
}//将fd从epfd中移除
int epoll_del(int fd, int epfd)
{struct epoll_event event;memset(&event, 0, sizeof(event));event.events = EPOLLIN;event.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);return 0;
}


客户端代码(2个头文件,4个源文件)


head.h

#ifndef __HEAD_H__
#define __HEAD_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>//检查命令行参数个数
#define ARGS_CHECK(argc, num) {if (argc != num) {\fprintf(stderr, "Args error\n");\exit(-1);}}//检查系统调用返回值
#define EXIT_CHECK(ret, num, msg) if (ret == num) {\do {\perror(msg);\exit(-1);\}while(0); }#endif

func.h

#ifndef __FUNC_H__
#define __FUNC_H__//接收发送缓冲区大小
#define BUF_SIZE 1024
//命令中最多有几个参数
#define MAX_WORDNUM 8
//命令一个参数的最大长度
#define MAX_WORDLEN 30//套接字类型
typedef int socket_t;//传输小货车
typedef struct {int _data_len;//货车头,表示数据长度char _data[BUF_SIZE];//货车车厢,表示数据
}Truck_t;//通讯类型
typedef struct {socket_t  _sess_fd ;             //对端套接字char _sess_buf[BUF_SIZE] ;  //发送接收缓冲区
}Session_t, *pSession_t;//命令类型
typedef struct {int _argc;       //命令中有几个参数char _cmd[MAX_WORDNUM][MAX_WORDLEN];
}Cmd_t, *pCmd_t;void conf_get(char *conf, char *ip, char *port);//从配置文件中获取参数
int tcp_connect(char *ip, int port);//连接服务器
int upload(int fd_socket, char *filename);//上传文件
int download(int fd_socket, char *filename);//下载文件void init_cmd(char *line, pCmd_t pcmd); //将line分割,存入cmd
void print_cmd(pCmd_t pcmd); //打印命令
int cmd_check(pCmd_t pcmd);//检查命令是否合法
#endif

disk_client.c

#include "head.h"
#include "func.h"int main(int argc, char *argv[])
{//命令行参数:配置文件路径ARGS_CHECK(argc, 2); //拿到服务器ip和portchar ip_server[16] = "";char port_server[5] = "";conf_get(argv[1], ip_server, port_server);//连接服务器socket_t fd_server = tcp_connect(ip_server, atoi(port_server));if (-1 == fd_server) {perror("socket_server");exit(-1);}//从stdin中读取命令,发给服务器char line[128] = "";Cmd_t cmd;Truck_t truck;//小货车,传输数据while (1) {memset(&truck, 0, sizeof(truck));memset(&cmd, 0, sizeof(Cmd_t));memset(line, 0, sizeof(line));//从标准输入中读取命令read(STDIN_FILENO, line, sizeof(line));line[strlen(line) - 1] = '\0';if (!strcmp(line, "quit")) {exit(1); }//格式化这行数据,并存入cmdinit_cmd(line, &cmd);//判断命令是否合法if (-1 == cmd_check(&cmd)) {printf("the cmd is illegal!\n");continue;}print_cmd(&cmd);//将cmd打包放入小货车memcpy(truck._data, &cmd, sizeof(cmd));//将命令发送给服务器truck._data_len = sizeof(Cmd_t);send(fd_server, &truck._data_len, sizeof(int), 0);send(fd_server, truck._data, truck._data_len, 0);//等待服务器返回数据if (!strncmp("cd", cmd._cmd[0], 2)) {system("clear");//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}else if (!strncmp("pwd", cmd._cmd[0], 3)) {system("clear");//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}else if (!strncmp("rm", cmd._cmd[0], 2)) {system("clear");//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}else if (!strncmp("mkdir", cmd._cmd[0], 5)) {system("clear");//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}else if (!strncmp("ls", cmd._cmd[0], 2)) {system("clear");//先接长度,后接数据while (1) {recv(fd_server, &truck._data_len, sizeof(int), 0);/* printf("data_len: %d\n", truck._data_len); */if (0 == truck._data_len) {break;}recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}}else if (!strncmp("puts", cmd._cmd[0], 4)) {system("clear");//上传文件upload(fd_server, cmd._cmd[1]);//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}else if (!strncmp("gets", cmd._cmd[0], 4)) {system("clear");//下载文件download(fd_server, cmd._cmd[1]);//先接长度,后接数据recv(fd_server, &truck._data_len, sizeof(int), 0);recv(fd_server, truck._data, truck._data_len, MSG_WAITALL);printf("%s\n", truck._data);}}return 0;
}

disk_conf.c

#include "head.h"//得到一行中'='符号后面的字符串
static void set_arg(char *line, char *arg)
{//将指针偏移到字符=char *ptr = strchr(line, '=');if (NULL == ptr) {fprintf(stderr, "conf_file is error!\n");exit(-1);}//= 后面的字符串strcpy(arg, ptr + 2);
}//从配置文件中获取参数
void conf_get(char *conf, char *ip, char *port)
{FILE *fp = fopen(conf, "r");EXIT_CHECK(fp, NULL, "fopen");char line[128] = "";//得到ipfgets(line, sizeof(line), fp);line[strlen(line) - 1] = '\0';set_arg(line, ip);//得到portmemset(line, 0, sizeof(line));fgets(line, sizeof(line), fp);line[strlen(line) - 1] = '\0';set_arg(line, port);fclose(fp);
}

disk_func.c

#include "head.h"
#include "func.h"//将line分割,存入cmd
void init_cmd(char *line, pCmd_t pcmd)
{//使用strtok将line拆分char *token;const char s[] = {' ', '\n'};//首次使用strtok时,需传入待分割的字符串和分隔符集合//之后再调用,第一个参数设为NULL,表示从继续上次的位置分割token = strtok(line, s);while (NULL != token) {strcpy(pcmd->_cmd[pcmd->_argc++], token);token = strtok(NULL, s);}
}//打印命令
void print_cmd(pCmd_t pcmd)
{printf("cmd: ");for (int i = 0; i < pcmd->_argc; ++i) {printf("%s ", pcmd->_cmd[i]);}printf("\n");
}//检查命令是否合法
int cmd_check(pCmd_t pcmd) 
{char tmp[64] = "";strncpy(tmp, pcmd->_cmd[0], 64);if (!strcmp(tmp, "cd") || !strcmp(tmp, "ls") ||!strcmp(tmp, "pwd") ||!strcmp(tmp, "rm") ||!strcmp(tmp, "puts") ||!strcmp(tmp, "gets") ||!strcmp(tmp, "mkdir")) {return 0;} else {return -1;}
}//上传文件
int upload(int fd_socket, char *filename)
{//查看文件类型是否正确DIR *dirp = opendir("./");struct dirent *dir_cur;int flag = -1; //是否传输标志, 0-不传,1-传while ((dir_cur = readdir(dirp)) != NULL) {if (!strcmp(dir_cur->d_name, filename)) {if (S_ISREG(dir_cur->d_type)) {//如果是普通文件, 传输flag = 1;send(fd_socket, &flag, sizeof(int), 0);}else {//如果不是普通文件,不传flag = 0;send(fd_socket, &flag, sizeof(int), 0);}break;}}closedir(dirp);if (0 == flag) {return -1;}int ret = -1;//定义一个小货车,用来传输文件Truck_t truck;memset(&truck, 0, sizeof(Truck_t));//根据文件名打开传输文件int fd_file = open(filename, O_RDONLY);EXIT_CHECK(fd_file, -1, "open");#if 0//发文件大小struct stat file_info;memset(&file_info, 0, sizeof(file_info));fstat(fd_file, &file_info);truck._data_len = sizeof(file_info.st_size);memcpy(truck._data, &file_info.st_size, truck._data_len);ret = send(fd_socket, &truck, sizeof(int) + truck._data_len, 0);EXIT_CHECK(ret, -1, "send_filesize");
#endif//发文件内容while (1) {memset(truck._data, 0, sizeof(truck._data));//读取文件truck._data_len = read(fd_file, truck._data, BUF_SIZE);if (0 == truck._data_len) {//传输完成,退出循环break;}//发送ret = send(fd_socket, &truck, sizeof(int) + truck._data_len, 0);if (-1 == ret) {//服务器异常断开,退出循环printf("server already break!\n");break;}}//传输完成,通知fdtruck._data_len = 0;send(fd_socket, &truck._data_len, sizeof(int), 0);//关闭传输文件close(fd_file);return 0;
}//下载文件
int download(int fd_socket, char *filename)
{//先接收传输标志,如果是1就下载,如果是0就退出int flag = -1;recv(fd_socket, &flag, sizeof(int), 0);if (0 == flag) {return -1;}//打开或创建一个文件int fd_file = open(filename, O_WRONLY | O_CREAT, 0600);EXIT_CHECK(fd_file, -1, "open");/* //接收文件大小 *//* int filesize = 0; *//* recv(fd_socket, &filesize, sizeof(int), 0); *//* printf("filesize: %d", filesize); *///接收文件内容Truck_t truck;while (1) {memset(&truck, 0, sizeof(truck));//接收数据长度recv(fd_socket, &truck._data_len, sizeof(truck._data_len), 0);if (0 == truck._data_len) {//文件传输完毕break;}//接收数据内容recv(fd_socket, truck._data, truck._data_len, MSG_WAITALL);write(fd_file, truck._data, truck._data_len);}close(fd_file);return 0;
}

tcp_connect.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>//连接服务器
int tcp_connect(char *ip, int port)
{int fd_server = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serAddr;memset(&serAddr, 0, sizeof(serAddr));serAddr.sin_family = AF_INET;serAddr.sin_addr.s_addr = inet_addr(ip);serAddr.sin_port = htons(port);if (-1 == connect(fd_server, (struct sockaddr*)&serAddr, sizeof(serAddr))) {perror("connect");return -1;}return fd_server;
}


总结

服务器通过线程池处理任务,方便管理,后续均在次基础上升级。


http://www.ppmy.cn/ops/113076.html

相关文章

CCF刷题计划——LDAP(交集、并集 how to go)

LDAP 计算机软件能力认证考试系统 不知道为什么&#xff0c;直接给我报一个运行错误&#xff0c;得了0分。但是我在Dev里&#xff0c;VS里面都跑的好好的&#xff0c;奇奇怪怪。如果有大佬路过&#xff0c;请帮小弟看看QWQ。本题学到的&#xff1a;交集set_intersection、并集…

鸿蒙开发(NEXT/API 12)【使用head发送网络请求 (C/C++)】远场通信服务

场景介绍 发送一个带有默认HTTP参数的HTTP HEAD请求&#xff0c;并返回来自服务器的HTTP响应。使用异步回调。类似GET请求&#xff0c;但只返回相应头&#xff0c;不返回实体内容。可以获取资源的元信息&#xff0c;如文件大小、修改日期等。 使用示例 CPP侧导入模块。 #incl…

Android 15 正式发布至 AOSP

Google官方宣布&#xff0c;将于近期发布了 Android 15&#xff0c;而在早些时候&#xff0c;Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内&#xff0c;Android 15 将在受支持的 Pixel 设备上正式推出&#xff0c;并将于今年晚些时候在三星、Honor、iQOO、…

JAVA8引入了哪些新特性

‌Java 8引入了多项新特性&#xff0c;使得编写代码更加简洁、易于维护和功能更强大。‌ 这些新特性主要包括&#xff1a; 1‌、Lambda表达式‌&#xff1a;Lambda表达式是Java 8最重要的特性之一 它提供了一种简洁的方式来表示匿名函数。Lambda表达式的语法为(parameters) -&…

使用C++进行机器学习开发

在机器学习的开发过程中&#xff0c;Python 是最广泛使用的编程语言&#xff0c;主要原因是其庞大的库生态和简便的语法。然而&#xff0c;C作为一种高性能语言&#xff0c;在某些性能要求极高或资源受限的场景下也具有非常重要的地位。C的高效性和对底层硬件的控制能力&#x…

MySQL行转列

在数据库操作中&#xff0c;有时我们需要将行数据转换为列数据&#xff0c;这在生成报表或进行数据汇总时尤为常见。本文将以一个学生成绩表为例&#xff0c;演示在MySQL中实现行转列的几种方法。 示例数据 假设我们有一个学生成绩表 score&#xff0c;包含以下三列&#xff…

超链接/表格/表单的复习(课后作业)

1.作业1 提示&#xff1a; 标题在title中修改 百度logo是图片链接(img) 新闻&#xff0c;贴吧是超链接&#xff0c;直接上官网cv 还有文本呢输入框 完成前端HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&q…

【算法篇】哈希类(笔记)

目录 一、常见的三种哈希结构 二、LeetCode 练习 1. 有效的字母异位词 2. 两个数组的交集 3. 快乐数 4. 两数之和 5. 四数相加II 6. 赎金信 7. 三数之和 8. 四数之和 一、常见的三种哈希结构 当想使用哈希法来解决问题的时候&#xff0c;一般会选择如下三种数据…

TS axios封装

方式一 service/request/request.ts import axios from axios import { ElLoading } from element-plus import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from axios import type { ILoadingInstance } from element-plus/lib/el-loading/src/loading.typ…

jeesite支持db2数据库初始化sql

点击下载&#xff1a;jeesite5.8.1-db2-sql.rar 提取码: yqev

Rasa对话模型——做一个语言助手

1、Rasa模型 1.1 模型介绍 Rasa是一个用于构建对话 AI 的开源框架&#xff0c;主要用于开发聊天机器人和语音助手。Rasa 提供了自然语言理解&#xff08;NLU&#xff09;和对话管理&#xff08;DM&#xff09;功能&#xff0c;使开发者能够创建智能、交互式的对话系统。 1.2…

MindShare PCIE 3.0 笔记-第一二章

MindShare 官网&#xff0c;地址如下: MindShare Chapter 1&#xff1a;PCIE 背景介绍 - PCI 总线模型 1. 以 PCI 总线作为外设总线的 SOC 芯片架构 下图展示了一个以 PCI 总线作为外设总线的 SOC 芯片架构(PCI 总线类似 AXI 下的 AHB&#xff1f;)&#xff1a; 由上图可知…

RK3568平台(音频篇)Tinyalsa open调用流程

一.TinyALSA 简介 TinyALSA 是一个轻量级的 ALSA(Advanced Linux Sound Architecture,高级 Linux 音频架构)实现,用于与 Linux 内核中的 ALSA(高级 Linux 声音架构)进行交互,旨在为嵌入式系统和资源受限的设备提供音频支持。 ALSA是位于Linux Kernel层面的音频系统。T…

MySQL 数据库中常用的SQL函数

一、字符串函数 #concat Select concat (hello,world); ​ #lower Select lower (HELLO); ​ #upper Select upper (hello); ​ #lpad Select lpad (wang,8,--); ​ #trim Select trim(helloworld); 二、数值函数 #ceil Select ceil(1.1); ​ #floor Select floor(1.8); ​ #…

C#迭代器方法和yield用法

一.迭代器方法介绍 可使用foreach循环进行遍历的方法&#xff0c;称为迭代器方法。 迭代器方法使用yield return语句返回元素。 到达yield return语句时&#xff0c;会记住当前在代码中的位置。 下次调用迭代器函数时&#xff0c;将从该位置开始执行。换言之&#xff0c;如果…

Oracle SQL injection(SQL注入)

Oracle SQL注入是一种网络安全漏洞&#xff0c;它允许攻击者在Oracle数据库驱动的Web应用程序中插入或“注入”恶意的SQL代码。这种攻击通常发生在应用程序未能正确验证或清理用户输入的数据时&#xff0c;从而允许攻击者操纵数据库查询&#xff0c;进而获取、修改或删除敏感信…

xml中SQL执行错误(使用另外一张表的两个字段,组装SQL的where查询条件)

SQL实现功能描述&#xff1a;根据系统设置中的商店到期提醒周期、单位&#xff0c;在过期提醒的列表中&#xff0c;对数据进行周期展示 错误复现&#xff1a; Mapper接口中抽象方法的定义如下&#xff1a; Page<ShopVo> queryList(Param(“vo”) ShopVo shopVo ,Page&…

下载github patch到本地

以下是几种从 GitHub 上下载以.patch 结尾的补丁文件的方法&#xff1a; 通过浏览器直接下载 打开包含该.patch 文件的 GitHub 仓库。在仓库的文件列表中找到对应的.patch 文件。点击该文件&#xff0c;浏览器会显示文件的内容&#xff0c;在页面的右上角通常会有一个“Raw”…

抓机遇,促发展——2025第十二届广州国际汽车零部件加工技术及汽车模具展览会

新能源时代&#xff0c;电动化、智能化正在重塑全球汽车市场格局。中国自主品牌新能源汽车的市占率不断提升、头部效应初显&#xff0c;更有机会带动相关供应链企业成长。中国的零部件企业有望抓住变局下的机会&#xff0c;在新一轮竞争中崛起。 智能电动车时代&#xff0c;汽车…

openssl 生成多域名 多IP 的数字证书

openssl.cnf 文件内容&#xff1a; [req] default_bits 2048 distinguished_name req_distinguished_name copy_extensions copy req_extensions req_ext x509_extensions v3_req prompt no [req_distinguished_name] countryName CN stateOrProvinceName GuangDong l…