精华内容
下载资源
问答
  • 2021-05-19 08:59:21

    给定程序的功能是:从键盘输入若干行文本(每行不超过80个字符),写到文件myfile4.txt中,用-1作为字符串输入结束的标志。然后将文件的内容读出显示在屏幕上。文件的读写分别由自定义函数ReadText和WriteText实现。

    注意:部分源程序给出如下。

    请勿改动主函数main和其他函数中的任何内容,仅在函数fun的横线上填入所编写的若干表达式或语句。

    /**********code.c**********/

    #include

    #include

    #include

    void WriteText(FILE*);

    void ReadText(FILE*);

    void main()

    {

    FILE*fp;

    if((fp=fopen("myfile4.txt","w"))==NULL)

    {

    printf("open fail!!\n"); exit(0);

    }

    WriteText(fp);

    fclose(fp);

    if((fp=fopen("myfile4.txt","r"))==NULL)

    {

    printf("open fail!!\n"); exit(0);

    }

    ReadText(fp);

    fclose(fp);

    }

    /**********found**********/

    void WriteText(FILE __1__)

    {

    char str[81];

    printf("\nEnter string writh -1 to end:\n");

    gets(str);

    while(strcmp(str,"-1")!=0){

    /**********found**********/

    fputs(__2__,fw);

    fputs("\n",fw);

    gets(str);

    }

    }

    void ReadText(FILE*fr)

    {

    char str[81];

    printf("\nRead file and output to screen:\n");

    fgets(str,81,fr);

    while(!feof(fr));{

    /**********found**********/

    printf("%s",__3__);

    fgets(str,81,fr);

    }

    }

    /**********-code.c**********/

    更多相关内容
  • 创建函数FUN_GET_ORG_CHILD(rootId) CREATE DEFINER=`sjjy`@`%` FUNCTION `FUN_GET_ORG_CHILD`(rootId varchar(100)) RETURNS varchar(2000) CHARSET utf8mb4//设置返回值类型 BEGIN DECLARE sTemp varchar(2000...

    创建函数FUN_GET_ORG_CHILD(rootId)

    CREATE  FUNCTION `FUN_GET_ORG_CHILD`(rootId varchar(100))
    
    RETURNS varchar(2000) CHARSET utf8mb4//设置返回值类型
    BEGIN   
    DECLARE sTemp varchar(2000);  //定义局部变量
    DECLARE sTempChd varchar(1000); //定义局部变量
    SET sTemp = '$';   //变量赋值
    SET sTempChd = rootId; //变量赋值 
    WHILE sTempChd is not null and sTempChd <> '0' DO   //while循环
        SET sTemp = concat(sTemp, ',', sTempChd);   
        SELECT group_concat(ORG_ID) INTO sTempChd FROM T_SYS_ORG where FIND_IN_SET(PARENT_ID, sTempChd)>0;   
    END WHILE;   
    RETURN sTemp;   
    END

    此sql:SELECT * FROM T_SYS_ORG where FIND_IN_SET(ORG_ID, FUN_GET_ORG_CHILD(#{orgId}))  这样就能查询某个机构的本下级信息了。简单记录一下

    扩展一:DEFINER

    扩展二:select *  into   变量A  from  table_name     或者 select * from table_name into 变量A

                  将查出的结果赋值给变量A

    MySQL自定义函数:

    mysql 自定义函数(user-defined function ) 简称UDF 是对mysql功能的一个扩展。

    语法:    

    
    DELIMITER $$
    CREATE FUNCTION 函数名(变量名1 数据类型1,变量名2 数据类型2,...) RETURNS 返回值类型
    BEGIN
    变量的声明
    sql语句
    RETURN 值; 
    END $$
    DELIMITER;
    
    /*
    注意:
    1、参数 包含两部分:参数名 参数类型
    2、函数体:肯定会有return语句,如果没有会报错
         如果return语句没有放在函数体的最后也不报错,但不建议
    3、函数体中仅有一句话,则可以省略begin end
    4、使用 delimiter语句设置结束标记
    */

     简单示例:统计user表的用户数量

    DELIMITER //
    CREATE FUNCTION countUser() RETURNS INT
    BEGIN
    DECLARE total int default 0;
    select count(*) into total from user;
    return total;
    END //
    DELIMITER;

    1、DELIMITER 定界符

          需要定义定界符,否则是创建不了函数的,因为mysql见到'分号'就认为执行结束了,只有开始

    创建时定义分界符,结束时在配对一个分界符,mysql认为这个时候才结束,使得函数能够完整编译创建

    附一个详细讲delimiter的博客:MySql中 delimiter 详解

    2、变量的声明

                    MYSQL中,变量分为三类:局部变量,用户变量、系统变量(会话变量,全局变量)

          局部变量:      

                        (存储过程中)通过DECLARE可以定义一个局部变量,default设置默认值。

                        declare声明变量必须放在最前面(可能是语法要求吧:declare声明的变量要放在begin...end块的第一行)

                        作用范围:BEGIN...END块中

                        语法:DECLARE 变量1[,变量2,... ]变量类型 [DEFAULT 默认值]

                        多个同类型的变量 可以分开或者一起声明   DECLARE total,total1,total2 VARCHAR(10) default null;

                        赋值:通过set或者 select * into 变量 from table 或者 select * from table into 变量

           用户变量:                  

                        用户变量不用事前申明,在用的时候直接用“@变量名”使用即可。

                        第一种用法:set @num=1; 或set @num:=1; //这里要使用set语句创建并初始化变量,直接使用@num变量

                        第二种用法:select @num:=1; 或 select @num:=字段名 from 表名 where …

                        注意上面两种赋值符号,使用set时可以用“=”或“:=”,但是使用select时必须用“:=赋值”

                        作用范围:在客户端链接到数据库实例整个过程中都是有效的。     

           系统变量

                      会话变量

                                    仅对当前客户端连接有效,
                                    使用set session varname=value;或者set @@session.varname=value;设置会话级别的系统变量
                                    或者使用set local varname=value;或者set @@local.varname=value;设置会话级别的系统变量
                                    其中,修改会话变量不需要root权限,且session可以省略,如:set varname=value;

                                    查看所有的会话变量:
                                                                       SHOW [SESSION] VARIABLES;
                                    查看一个会话变量也有如下三种方式:

                                                                        select @@var_name;

                                                                 select @@session.var_name;

                                                                 show session variables like “%var%”;

                    全局变量

                                    使用set global varname=value;或者set @@global.varname=value;设置全局系统变量的值
                                    其中,修改全局变量时需要root权限,且global不可省略。

                                    查看所有的全局变量:
                                                                      show global variables;
                                    要想查看一个全局变量,有如下两种方式:
                                                                      select @@global.var_name;
                                                                      show global variables like “%var%”;

    3、函数

          存储过程和函数区别: 存储没有返回值,使用输出参数返回数据,可以有多个,函数有返回值,只能返回一个。

    字符串函数

    CHARSET(str) //返回字串字符集
    CONCAT (string2 [,... ]) //连接字串
    INSTR (string ,substring ) //返回substring首次在string中出现的位置,不存在返回0
    LCASE (string2 ) //转换成小写
    LEFT (string2 ,length ) //从string2中的左边起取length个字符
    LENGTH (string ) //string长度,字节的长度
    LOAD_FILE (file_name ) //从文件读取内容
    LOCATE (substring , string [,start_position ] ) 同INSTR,但可指定开始位置
    LPAD (string ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
    LTRIM (string ) //去除前端空格
    REPEAT (string2 ,count ) //重复count次
    REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
    RPAD (string ,length ,pad) //在str后用pad补充,直到长度为length
    RTRIM (string ) //去除后端空格
    STRCMP (string1 ,string2) //逐字符比较两字串大小,
    SUBSTRING (str , position [,length ]) //从str的position开始,取length个字符,
    TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
    UCASE (string2 ) //转换成大写
    RIGHT(string2,length) //取string2最后length个字符
    SPACE(count) //生成count个空格
    注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于1

     数学函数

    ABS (number2 ) //绝对值
    BIN (decimal_number) //十进制转二进制
    CEILING (number2 ) //向上取整
    CONV(number2,from_base,to_base) //进制转换
    FLOOR (number2 ) //向下取整
    FORMAT (number,decimal_places ) //保留小数位数
    HEX (DecimalNumber) //转十六进制
    POWER (number ,power ) //求指数
    RAND([seed]) //随机数
    ROUND (number [,decimals ]) //四舍五入,decimals为小数位数] 注:返回类型并非均为整数

    日期函数

    ADDTIME (date2 ,time_interval) //将time_interval天加到date2 秒
    ADDDATE(‘2019-04-15’,2); #2天
    CURRENT_DATE ( ) //当前日期
    CURRENT_TIME ( ) //当前时间
    CURRENT_TIMESTAMP ( ) //当前时间戳
    DATE (datetime ) //返回datetime的日期部分
    DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
    DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
    DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
    DATEDIFF (date1 ,date2 ) //两个日期差
    DAY (date ) //返回日期的天
    DAYNAME (date ) //英文星期
    DAYOFWEEK (date ) //星期(1-7) ,1为星期天
    DAYOFYEAR (date ) //一年中的第几天
    EXTRACT (interval_name FROM date ) //从date中提取日期的指定部分
    MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
    MAKETIME (hour ,minute ,second ) //生成时间串
    MONTHNAME (date ) //英文月份名
    NOW ( ) //当前时间
    SEC_TO_TIME (seconds ) //秒数转成时间
    STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
    TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
    TIME_TO_SEC(time) //时间转秒数
    WEEK (date_time [,start_of_week ]) //第几周
    YEAR (datetime ) //年份
    DAYOFMONTH(datetime) //月的第几天
    HOUR(datetime) //小时

    4、函数中常用的流程控制语句

    • if
    • case
    • loop
    • leave
    • iterate
    • repeat
    • while

        每个流程中可能包含一个单独语句,或者是使用BEGIN...END构造的复合语句,构造可以被嵌套

      a)if语句

          语法:   类似java中的   if...elseif...else....

    if   条件1    then    ...;
    elseif   条件2    then    ...;
    else ...;
    end if;

    重点:有 if 则必有 end if    if语句可用在begin...end 中或外边。 

     if示例:

    DROP FUNCTION IF EXISTS testIf;//判断函数是否存在  存在则删除
    CREATE FUNCTION `testIf`(age INT) RETURNS VARCHAR(100)//固定写法  创建函数  定义返回值类型
    BEGIN //固定语法   begin ... end 块
    #DECLARE a INT DEFAULT 0; //declare声明变量  俗称 局部变量
    #DECLARE message VARCHAR(100) DEFAULT '';//declare声明变量
    #IF a < 18 then SET message = "你的age小于18";
    #elseif a > 60 THEN SET message ="你的age大于60";
    #else SET message = "age处于18和60之间";
    #end if;//if语句的结束   固定写法
    DECLARE message VARCHAR(100) DEFAULT '';
    SET @a = age; // 俗称  用户变量  set声明
    IF @a < 18 THEN SET message = "你的age小于18";
    ELSEIF @a > 60 THEN SET message ="你的age大于60";
    ELSE SET message = "age处于18和60之间";
    END IF;
    RETURN message;//返回值
    END

    b)case语句

    语法:

    语法一:
        case  
        when  条件1  then  ...;
        when  条件2  then ...;
        else ...;
        end case;
    语法二:
        case 变量或表达式
        when 值1 then 语句1;
        when 值2 then 语句2;
        ...;
        else 语句n;
        end case;

     有case 必有end case            ,case语句可应用在begin  end 中外

    示例:

    DROP FUNCTION IF EXISTS testCase;
    CREATE FUNCTION `testCase`(age INT) RETURNS VARCHAR(100)
    BEGIN
    DECLARE message VARCHAR(100) DEFAULT '';
    SET @a = age;
    CASE WHEN @a < 18 THEN SET message = "你的age小于18";
    WHEN @a > 60 THEN SET message ="你的age大于60";
    ELSE SET message = "age处于18和60之间";
    END CASE;//case的结束  固定写法
    RETURN CONCAT('你输入的age是',@a,',',message);
    END

    c)loop和leave语句

    LOOP语句可以使某些特定的语句重复执行,实现一个简单的循环。但是LOOP语句本身没有停止循环的语句,必须是遇到LEAVE语句等才能停止循环。

    loop语法格式:

    [begin_label:] LOOP 
    statement_list 
    END LOOP [end_label] 

    begin_label参数和end_label参数分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;statement_list参数表示需要循环执行的语句。

    特点:

    1. loop相当于java中的while(true){ }
    2. 有loop  必有  end loop

    简单的loop示例:

    add_num: LOOP  
    SET @count=@count+1;  
    END LOOP add_num ; 

    由于loop没有停止循环的语句,所以此loop示例是一个死循环,要想停止循环,需要借助leave语句来跳出循环

    //如果变量@count的值等于100  则停止循环
    add_num: LOOP 
    SET @count=@count+1; 
    IF @count=100 THEN LEAVE add_num; 
    END IF;
    END LOOP add_num ; 

    简单示例:

    DROP FUNCTION IF EXISTS testLoopAndLeave;
    CREATE FUNCTION testLoopAndLeave() RETURNS VARCHAR(1000)
    BEGIN
    DECLARE TOTAL VARCHAR(1000) DEFAULT '';//存放loop循环中@count值的变化
    SET @count= 1;//声明变量@count
    add_count:LOOP
    SET @count= @count+FLOOR(RAND()*10);//@count每次加上一个随机数
    SET TOTAL = CONCAT(TOTAL,',',@count);//记录@count值循环中的变化
    IF @count > 1000 THEN LEAVE add_count;//@count大于1000则停止loop循环
    END IF;
    END LOOP add_count;
    RETURN total;
    END

    d)repeat语句

    REPEAT语句是有条件控制的循环语句。当满足特定条件时,就会跳出循环语句。REPEAT语句的基本语法形式如下:

    [begin_label:] REPEAT 
    statement_list; //循环执行体
    UNTIL search_condition //满足条件结束体   注意:until结束没有分号(容易加分号报错)  
    END REPEAT [end_label];  //begin_label end_label  开始结束标识位  两者一样哈

    特点:

    1. 先执行后判断 相当于java 中的do...while...语句
    2. 有repeat  必有 end repeat
    3. until  后写的时候不要加分号
    4. begin_lable和end_label 用到leave或iterate时需要加上,否则可省略

    简单示例:

    DROP FUNCTION IF EXISTS testRepeat;
    CREATE FUNCTION testRepeat() RETURNS VARCHAR(1000)
    BEGIN
    	DECLARE d VARCHAR(1000) DEFAULT '0';//创建变量d,记录@a的变化过程
    	SET @a=0;//声明变量a
    my_repeat:REPEAT
    	SET @a=@a+1;//repeat的循环体  每次加1
    	SET d = CONCAT(d,'-->',@a);//d变量记录
    	UNTIL @a =100 //repeat有条件循环的结束条件 满足则停止循环   此语句后没有分号
    	END REPEAT my_repeat;
    RETURN d;
    END

    e)while语句

    while语句也是有条件控制的循环语句。但while语句和repeat语句是不一样的,while语句是当满足条件时,执行循环内的语句,而repeat是先循环,满足until条件停止循环。

    while语法:

    [begin_label:] WHILE 条件 DO 
    满足条件的执行体
    END WHILE [end_label] 

    注意:

    1. while是先判断后执行。 
    2. 有while  必有 end while   
    3. 当需要用到 leave和iterate时,则需要加上begin_lable和end_lable,否则可省略不写。

    简单示例:

    DROP FUNCTION IF EXISTS testWhile;
    CREATE FUNCTION testWhile() RETURNS VARCHAR(1000)
    BEGIN
    DECLARE detail VARCHAR(1000) DEFAULT '0';
    SET @num = 0;
    while @num < 50 DO
    set @num = @num + 2;
    SET detail = CONCAT(detail,'>',@num);
    END WHILE;
    RETURN detail;
    END

    f)iterate语句

    iterate语句也是用来跳出循环的语句。但是,iterate语句是跳出本次循环,然后直接进入下一次循环,iterate语句只可以出现在LOOP、REPEAT、WHILE语句内。

        语法格式:iterate lable                lable 是标识位  即前面所说的  begin_lable 和end_lable 

    简单示例:

    DROP FUNCTION IF EXISTS  testLeaveAndIterate;
    CREATE FUNCTION testLeaveAndIterate() RETURNS VARCHAR(1000)
    BEGIN
    DECLARE msg VARCHAR(1000) DEFAULT '1';
    set @age = 0;
    add_loop:LOOP
    SET @age = @age + 1;
    IF @age = 100 THEN LEAVE add_loop;
    ELSEIF MOD(@age,2) = 0 THEN  ITERATE add_loop;
    ELSE SET msg = CONCAT(msg,',',@age);
    END IF;
    END LOOP add_loop;
    return msg;
    END

    小结:

    1. leave与iterate的区别:leave等同于java中的break   iterate等同于java中的continue。
    2. 有if 必有end  if  有case 必有 end case 有loop 必有 end loop  有repeat 必有end repeat  有while  必有end while
    3. declare声明的变量要放在begin...end块的第一行
    4. 写repeat语句时   until后的语句结束不要带分号(自己写的时候带分号 一直报错...看了好久才发现)

    删除函数:DROP FUNCTION function_name

    调用函数:SELECT function_name(parameter_value,...)

    查看函数:SHOW FUNCTION STATUS

    MYSQL存储过程

    什么是存储过程?

         简而言之,一组sql语句集,功能强大,可以实现比较复杂的逻辑功能。

         存储过程和触发器有点类似,都是一组SQL集,但是存储过程是主动调用的,且功能比触发器更加强大,触发器是某件事触发后自动调用。

    优点:

    • 提高代码的重用性
    • 简化操作
    • 减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率

    存储过程的语法

    1、创建:

    CREATE PROCEDURE 存储过程名(参数列表)
    BEGIN
    	 # 存储过程体(一组合法的SQL语句)
    END

    2、参数列表

    1) 参数列表包含三部分
    参数模式 参数名 参数类型

    in stuname varchar(20)
    

    也可以写成stuname varchar(20) 但最好把 in 加上。
    2) 参数模式

    in:该参数可以作为输入,也就是该参数需要调用方传入值。
    out:该参数可以作为输出,也就是该参数可以作为返回值。
    inout:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值。
    

    3、调用

    CALL 存储过程名(实参列表);
    

    4、删除

    如果存在该存储过程 则删除该存储过程。

    drop procedure if exists 存储过程名称
    

    重点:存储过程体中的每条sql语句的结尾要求必须加分号

    注意:如果存储过程体仅仅只有一句话,begin end可以省略。

    注意:存储过程的结尾可以使用 delimiter 重新设置(一般如果存储过程中存在多个分号结尾,就可以使用delimiter)

    5、mysql自定义函数所说的流程控制语句同样适用mysql的存储过程,不在叙述......

    存储过程示例:

    1、无参列表存储过程

    DROP PROCEDURE if EXISTS test1;
    CREATE  PROCEDURE `test1`()
    BEGIN
    INSERT INTO test_user(id,name) VALUES(UUID(),'name1'),
    																			(UUID(),'name2'),
    																			(UUID(),'name3'),
    																			(UUID(),'name4'),
    																			(UUID(),'name5'),
    																			(UUID(),'name6');
    
    END

    看一个实际开发中遇到一个运用MYSQL存储过程实现按天建表的例子:

    DROP PROCEDURE IF EXISTS my_test;
    CREATE  PROCEDURE `my_test`(IN startDate VARCHAR(10),IN dateNum INT)
    BEGIN
         declare k int;
    	   set k=0;
    		 SET @dateNum = dateNum; 
    		 SET @startDate = startDate;
         while k < dateNum do
         set @num =k;
         set @dateName=DATE_ADD(startDate,INTERVAL @num DAY);
         set @tname=concat('my_test',DATE_FORMAT(@dateName,'%Y%m%d'));
         set @dwhe="(`id` bigint(11) NOT NULL AUTO_INCREMENT,
                     `ipaddr` varchar(50) DEFAULT NULL COMMENT '',
    				 `ipaddrlong` decimal(50,0) DEFAULT NULL COMMENT '',
    				 `domain` varchar(255) DEFAULT NULL COMMENT '',
    				 `firstdomain` varchar(255) DEFAULT NULL COMMENT '',
    				 `first_time` datetime DEFAULT NULL COMMENT '',
    				 `last_time` datetime DEFAULT NULL COMMENT '',
    				 `visits_count` varchar(20) DEFAULT NULL COMMENT '',
    				 `create_time` timestamp NULL DEFAULT NULL COMMENT '',
    				 `update_time` timestamp NULL DEFAULT NULL COMMENT '',
    				 `is_check` char(1) DEFAULT NULL COMMENT '',
    				 PRIMARY KEY (`id`),
    				 UNIQUE KEY `ipaddrlong` (`ipaddr`,`firstdomain`) USING BTREE,
    				 KEY `domain_idx` (`domain`) USING BTREE,
    				 KEY `last_time_idx` (`last_time`) USING BTREE
    				) ENGINE=InnoDB AUTO_INCREMENT=3232405 DEFAULT CHARSET=utf8 COMMENT='';
    				";
         set @sql_text:=concat('create table IF NOT EXISTS ',@tname,@dwhe);
         prepare stmt from@sql_text;
         execute stmt;
         set k=k+1;
         end while;
    END

     引入这个例子除了看下存储过程,其实想记录下这个东西:

    prepare stmt from@sql_text;
    execute stmt;

     了解下mysql 存储过程中的 prepare语句:mysql 存储过程中的 prepare语句_嗯哼的博客-CSDN博客

    2、带IN模式参数的存储过程

    DROP PROCEDURE IF EXISTS testIn;
    CREATE PROCEDURE testIn(IN myName VARCHAR(20))
    BEGIN
    select * from test_user where name = myName;
    END
    
    #调用
    CALL testIn('name1');

    注意如果传参带有中文,如果上面这样会报字符转换错误,需要将VARCHAR(20)改成NVARCHAR(20)

    3、模拟用户登陆成功与失败

    DROP PROCEDURE IF EXISTS testUserLogin;
    CREATE PROCEDURE testUserLogin(IN username VARCHAR(20),IN pwd VARCHAR(20))
    BEGIN
    DECLARE result INT DEFAULT 0;
    SELECT COUNT(1) FROM test_user WHERE name = username and password=pwd INTO result;
    SELECT IF(result > 0,'登陆成功','登陆失败') as result;
    END
    #调用
    CALL testUserLogin('name1','123456');

       调用结果:     

    4、 带IN   OUT模式的存储过程

    DROP PROCEDURE IF EXISTS testInOut;
    CREATE PROCEDURE testInOut(IN inId VARCHAR(64),OUT userName VARCHAR(64),OUT pwd VARCHAR(64))
    BEGIN
    SELECT name,password FROM test_user where id = inId into userName,pwd;
    END
    #调用
    CALL testInOut('d03e536d-f747-11ea-997c-00ff5f20e2ec',@name,@pwd);
    #查询 函数返回值 userName 和 pwd
    SELECT @name as name,@pwd as password;

    @name和@pwd的查询结果:

     5、INOUT模式的存储过程

    DROP PROCEDURE IF EXISTS testInOut;
    CREATE PROCEDURE testInOut(INOUT a INT,INOUT b INT)
    BEGIN
    SET a=a*2;
    SET b=b*3;
    END
    #调用
    SET @m=10;
    SET @n=10;
    CALL testInOut(@m,@n);
    #查询返回值
    SELECT @m,@n;

    查询返回结果:

    mysql自定义函数和存储过程 都是完成特定功能的SQL集合,二者的区别?

    区别:

    1. 调用不一样   函数:select 函数名(参数)   存储过程:call 存储名(参数) 或者 call.存储名(参数)。
    2. 参数:存储过程有IN  OUT  INOUT  三种模式的参数 而自定义函数只有IN类的参数。
    3. 返回值:存储过程可以有多个返回值,而函数只能有一个且函数声明的过程中 需要定义返回值类型和写return语句,而存储过程不需要。
    4. 存储过程一般单独执行而自定义函数往往作为其他sql的一部分去使用。
    5. 存储过程可以有select  update delete  insert  create 语句和一些换算 而mysql自定义函数只能有select语句和一些换算。所以存储过程的功能比较强大。 
    6. 存储过程,可以使用非确定函数,不允许在用户定义函数主体中内置非确定函数(这个网上看的,后面这句不是很理解)

    mysql游标

    1、什么是游标?

    游标是一个存储在MySQL服务器上的数据库查询,它不是一条select语句,而是被该语句所检索出来的结果集。

    2、 游标什么作用?

    SELECT name,age from person where age>10;

     在实际开发中后台代码调用这个sql语句,一般会返回一个List<Object>对象,进而通过java的遍历对这个集合做一系列的操作。那么在MYSQL中也有这么一个东西可以储存集合,这个东西就是游标

    注意:MySQL游标只能用于存储过程(和函数)。游标主要用于交互式应用。

    3、游标的使用:

    1. 声明游标 :这个过程实际上是没有遍历数据的,它只是定义要使用的select语句来获取数据。
    2. 打开游标 : 上面定义好后,那么这里就需要打开游标。这个过程用前面定义的select语句把数据实际检索出来。即这个步骤之后,我们就可以遍历游标中的数据了。
    3. 遍历数据 : 对于有数据的游标,根据需要取出各行的数据来进行一定的操作。
    4. 关闭游标 : 使用完游标后,一定要关闭游标。

    4、游标的语法

          a)声明游标

    DECLARE cursor_name CURSOR FOR select_statement

         cursor_name是游标名称,在一个块中游标名称是唯一的。select_statement是一个select语句(select语句不能有into子句)

          b)打开游标

    OPEN cursor_name ;

          c)遍历游标

    FETCH cursor_name INTO var_name ;

       这个语句是获取游标当前指向的数据行,并将指针指向下一行。var_name 是定义的变量,此遍历是将select_statement中查询字段赋值给var_name

         此处说遍历游标不合适,真正遍历游标是mysql自定义函数中的  loop或者repeat或者while循环语句。

         d)关闭游标

    CLOSE cursor_name ;

    下面举例子来细品游标的使用:

    有这么一张表

    \

    现在需要统计下用户名称包含test出现的次数:

    DROP PROCEDURE IF EXISTS countUserLikeName;
    CREATE PROCEDURE countUserLikeName(IN param VARCHAR(64))
    BEGIN
    #n 存的是每次遍历的用户名称
    declare n varchar(20);
    # 拼装所有的满足查询的用户名称
    DECLARE allName VARCHAR(1000) DEFAULT '$';
    #满足查询条件的信息数量
    DECLARE total INT DEFAULT 0;
    # 创建结束标志变量
    DECLARE done int default false;
    #声明游标
    DECLARE myCursor CURSOR FOR SELECT name from  test_user where name like CONCAT('%',param,'%');
    # 指定游标循环结束时的返回值
    DECLARE continue HANDLER for not found set done = true; #此处这个玩意可能不懂??,后面会说
    #total变量初始化
    SET total=0;
    #打开游标
    OPEN myCursor;
    #采用loop去遍历游标
    add_loop:LOOP  #loop遍历
    # 根据游标当前指向的一条数据 插入到上面申明的局部变量中
    	fetch myCursor into n;
    	# 判断游标的循环是否结束
    	if done then  #if语句
    		leave add_loop;	# leave跳出游标循环
    	end if;  #有if  必有  end if
    SET allName=CONCAT(allName,',',n);  # 将满足查询条件的用户名称拼接到一起
    SET total = total + 1;  #统计满足查询条件的用户数量
    END LOOP; #有loop 必有 end loop
    #关闭游标
    close myCursor; 
    # 输出结果即存储过程的返回值
    select allName,total;
    END
    #调用存储过程
    CALL countUserLikeName('test');

    调用结果:

     虽然这样的结果用mysql的group by  + group_concat就可以统计出来,这不是重点,只是想展示下游标的遍历。

    强调下 fetch:

    是获取游标当前指向的数据行,并将指针指向下一行,当游标已经指向最后一行时继续执行会造成游标溢出。
    使用loop循环游标时,他本身是不会监控是否到最后一条数据了,像下面代码这种写法,就会造成死循环;

    read_loop:loop
    fetch cur into n;
    set total = total + 1;
    end loop;

    在MySql中,造成游标溢出时会引发mysql预定义的NOT FOUND错误,所以在上面使用下面的代码指定了当引发not found错误时定义一个continue 的事件,指定这个事件发生时修改done变量的值。

    # 指定游标循环结束时的返回值
    DECLARE continue HANDLER for not found set done = true; #此处这个玩意可能不懂??,后面会说

    所以在loop循环中加上

    if done then  #if语句
    		leave add_loop;	# leave跳出游标循环
    	end if;  #有if  必有  end if

    如果done的值是true,就结束循环。继续执行下面的代码。

    MYSQL触发器

    1、触发器概念

    触发器(trigger)是MySQL提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如当对一个表进行操作(insert,delete, update)时就会激活它执行。——百度百科

    简而言之,就是一个 insert、update、delete这样的sql语句会触发触发器执行。触发器是特殊的存储过程,与存储过程的区别是存储过程的执行需要被调用而触发器是靠事件的触发去执行,除了语法上不一样,begin.......end块中都是一样的东西。

    2、触发器语法

    CREATE TRIGGER  <触发器名> <BEFORE|AFTER>  <INSERT|UPDATE|DELETE> ON <表名> FOR EACH Row
    BEGIN
    
    <触发器主体>
    
    END

    触发器四个点:

    1. 监视对象--------table
    2. 监视事件--------table的update、insert、delete
    3. 触发时间--------before(操作前)、after(操作后)
    4. 触发事件--------update、insert、delete

    注意事项:

    1. 触发器绑定的实质是表中的每一行,当某一行发生update、insert、delete操作就会触发触发器。
    2. 如果 before 触发器失败或者语句本身失败,将不执行 after 触发器(如果有的话)
    3. 触发器名称对于一个table来说是唯一的,跨表或者跨库可以有相同名称的触发器
    4. 每一个table最多支持6个触发器: before/update、after/update、before/insert、after/insert、before/delete、after/delete

    3、触发器的引用行变量

    触发器针对的是数据库中的每一行记录,每行数据在操作前后都会有一个对应的状态,触发器将没有操作之前的状态保存到 old 关键字中,将操作后的状态保存到 new 中

    语法:old/new.字段名

    • 在触发目标上执行insert操作后会有一个新行,如果在触发事件中需要用到这个新行的变量,可以用new关键字表示 
    • 在触发目标上执行delete操作后会有一个旧行,如果在触发事件中需要用到这个旧行的变量,可以用old关键字表示 
    • 在触发目标上执行update操作后原纪录是旧行,新记录是新行,可以使用new和old关键字来分别操作
          触发器类型                                                         new和old的使用
    INSERT型触发器没有 old,只有 new,new 表示将要(插入前)或者已经增加(插入后)的数据
    UPDATE型触发器既有 old 也有 new,old 表示更新之前的数据,new 表示更新之后的数据
    DELETE型触发器没有 new,只有 old,old 表示将要(删除前)或者已经被删除(删除后)的数据

    示例展示:

        业务需求:用户增删改的时候,产生操作日志信息

          涉及到表:用户表(user)、日志表(user_log)、日志详情表(user_log_detail)

          新增删除用户的时候要在user_log插入对应的日志信息。修改的 时候也要在user_log中插入一条日志信息,且在 user_log_detail插入详情信息(即修改字段、修改前什么值、修改后什么值)

    表结构如下:

    ##用户表
    CREATE TABLE `user` (
      `id` varchar(64) NOT NULL,
      `name` varchar(64) DEFAULT NULL,
      `phone` varchar(64) DEFAULT NULL,
      `address` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    ##日志表
    CREATE TABLE `user_log` (
      `id` varchar(64) NOT NULL,
      `user_id` varchar(255) DEFAULT NULL,
      `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作类型',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    ##日志详情表
    CREATE TABLE `user_log_detail` (
      `id` varchar(64) NOT NULL,
      `user_log_id` varchar(255) DEFAULT NULL,
      `col` varchar(255) DEFAULT NULL COMMENT '列名称',
      `old` varchar(255) DEFAULT NULL COMMENT '修改前字段值',
      `new` varchar(255) DEFAULT NULL COMMENT '修改后字段值'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    触发器创建:

    #新增用户后插入日志
    DROP TRIGGER IF EXISTS insert_user_after;
    CREATE TRIGGER insert_user_after after INSERT ON user FOR EACH ROW
    BEGIN
    INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(after)');
    END;
    #新增用户前插入日志
    DROP TRIGGER  IF EXISTS insert_user_before;
    CREATE TRIGGER insert_user_before before INSERT ON user FOR EACH ROW
    BEGIN
    INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(before)');
    END;
    #删除用户前插入日志
    DROP TRIGGER  IF EXISTS delete_user_before;
    CREATE TRIGGER delete_user_before before delete ON user FOR EACH ROW
    BEGIN
    INSERT INTO user_log(id,user_id,type)VALUES(UUID(),old.id,'delete(before)');
    END;
    #删除用户后插入日志
    DROP TRIGGER  IF EXISTS delete_user_after;
    CREATE TRIGGER delete_user_after after delete ON user FOR EACH ROW
    BEGIN
    INSERT INTO user_log(id,user_id,type)VALUES(UUID(),old.id,'delete(after)');
    END;
    
    #修改用户后插入日志表和日志详情表
    DROP TRIGGER  IF EXISTS  update_user_after;
    CREATE TRIGGER update_user_after after update ON user FOR EACH ROW
    BEGIN
    DECLARE name VARCHAR(64) DEFAULT '';
    DECLARE phone VARCHAR(64) DEFAULT '';
    DECLARE address VARCHAR(64) DEFAULT '';
    SET @log_id = UUID();
    INSERT INTO user_log(id,user_id,type)VALUES(@log_id,new.id,'update(after)');
    
    IF old.name <> new.name 
        THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'用户名称',old.name,new.name);
    END IF;
    IF old.phone <> new.phone
        THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'手机号',old.phone,new.phone);
    END IF;
    IF old.address <> new.address
        THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'用户地址',old.address,new.address);
    END IF;
    END;
    

     新增用户:INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');

      结果:

                                

     删除用户:INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');

    结果:

                                 

    修改用户:update user SET name='test_update',phone='66666_update',address='俺是那嘎达的' where id = '67ef0412-f97f-11ea-8349-00ff5f20e2ec';

    结果:

                          

    小结:

    触发器是对某张table的insert、update、delete操作(事件)进行绑定的一种特殊的存储过程,依靠这些事件的发生去触发执行触发器,触发器实质上是绑定数据表的行,新增的行数据保存在new上、删除的行数据保存在old上、修改的行原始数据保存在old上,新的数据保存在new上。调用:old.col_name   new.col_name

    PS:MySQL 的触发器中不能对本表进行 insert、update 和 delete 操作,否则会报错 

    测试触发器insert本表:

    DROP TRIGGER  IF EXISTS  insert_user_after;
    CREATE TRIGGER insert_user_after after INSERT ON user FOR EACH ROW
    BEGIN
    INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(after)');
    #插入本表user的操作
    INSERT INTO user(id,name,phone,address) values(UUID(),'test123','123','123');
    END;
    
    
    ##执行insert语句去触发insert_user_after触发器
    INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');
    
    报错信息:
    
    [SQL]INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');
    [Err] 1442 - Can't update table 'user' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.

    MYSQL事件(定时任务)

    在MySQL 5.1中新增了一个特色功能事件调度器(Event Scheduler),简称事件。它可以作为定时任务调度器,取代部分原来只能用操作系统的计划任务才能执行的工作。另外,更值得一提的是,MySQL的事件可以实现每秒钟执行一个任务,这在一些对实时性要求较高的环境下是非常实用的。

    事件调度器是定时触发执行的,从这个角度上看也可以称作是“临时触发器”。但是它与触发器又有所区别,触发器只针对某个表产生的事件执行一些语句,而事件调度器则是在某一段(间隔)时间执行一些语句。

    查看事件调度器是否开启

    事件由一个特定的线程来管理。启用事件调度器后,拥有SUPER权限的账户执行SHOW PROCESSLIST就可以看到这个线程了。

    查看事件是否开启

    SHOW VARIABLES LIKE 'event_scheduler';
    SELECT @@event_scheduler;
    SHOW PROCESSLIST;

    开启或关闭事件调度器

    使用SET GLOBAL命令可以开启或关闭事件。将event_scheduler参数的值设置为ON,则开启事件;如果设置为OFF,则关闭事件。

    方法一:

    -- 开启事件调度器
    SET GLOBAL event_scheduler = ON;
     
    -- 关闭事件调度器
    SET GLOBAL event_scheduler = OFF;
     
    -- 查看事件调度器状态
    SHOW VARIABLES LIKE 'event_scheduler';

    注意:如果想要始终开启事件,那么在使用SET GLOBAL开启事件后,还需要在my.ini(Windows系统)/my.cnf(Linux系统)中添加event_scheduler=on。因为如果没有添加,MySQL重启事件后又会回到原来的状态。

    方法二:通过更改配置文件

    在MySQL的配置文件my.ini(Windows系统)/my.cnf(Linux系统)中,找对[mysqld],然后在下面添加以下代码开启事件。

    # 事件调度器启动状态
    event_scheduler = on

    在配置文件中添加代码并保存文件后,还需要重新启动MySQL服务器才能生效。通过该方法开启事件,重启MySQL服务器后,不恢复为系统默认的状态。

    创建时间的语法格式

    CREATE
    	[DEFINER={user | CURRENT_USER}]
    	EVENT [IF NOT EXISTS] event_name
    	ON SCHEDULE schedule
    	[ON COMPLETION [NOT] PRESERVE]
    	[ENABLE | DISABLE | DISABLE ON SLAVE]
    	[COMMENT 'comment']
    	DO event_body;

    参数释义:

    DEFINER可选,用于定义事件执行时检查权限的用户
    IF NOT EXISTS可选项,用于判断要创建的事件是否存在
    EVENT event_name必选,用于指定事件名,event_name的最大长度为64个字符,如果为指定event_name,则默认为当前的MySQL用户名(不区分大小写)
    ON SCHEDULE schedule必选,用于定义执行的时间和时间间隔
    ON COMPLETION [NOT] PRESERVE

    可选,用于定义事件是否循环执行,即是一次执行还是永久执行,默认为一次执行,

    即 NOT PRESERVE

    ENABLE | DISABLE | DISABLE ON SLAVE可选项,用于指定事件的一种属性。其中,关键字ENABLE表示该事件是活动的,也就是调度器检查事件是否必选调用;关键字DISABLE表示该事件是关闭的,也就是事件的声明存储到目录中,但是调度器不会检查它是否应该调用;关键字DISABLE ON SLAVE表示事件在从机中是关闭的。如果不指定这三个选择中的任意一个,则在一个事件创建之后,它立即变为活动的。
    COMMENT 'comment'可选,用于定义事件的注释
    DO event_body

    必选,用于指定事件启动时所要执行的代码。可以是任何有效的SQL语句、存储过程或者一个计划执行的事件。如果包含多条语句,可以使用BEGIN...END复合结构

    这个参数要详细了解下

    1、ON SCHEDULE  schedule,参数schedule的值为一个AS子句,用于指定事件在某个时刻发生,其语法格式如下:

    AT timestamp [+ INTERVAL interval] ...
      | EVERY interval
        [STARTS timestamp [+ INTERVAL interval] ...]
        [ENDS timestamp [+ INTERVAL interval] ...]

    参数说明:

    (1)timestamp:表示一个具体的时间点,后面加上一个时间间隔,表示在这个时间间隔后事件发生。

    (2)EVERY子句:用于表示事件在指定时间区间内每隔多长时间发生一次,其中 STSRTS子句用于指定开始时间;ENDS子句用于指定结束时间。

    (3)interval:表示一个从现在开始的时间,其值由一个数值和单位构成。例如,使用“4 WEEK”表示4周;使用“‘1:10’ HOUR_MINUTE”表示1小时10分钟。间隔的距离用DATE_ADD()函数来支配。

    interval参数值的语法格式如下:

    quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
                  WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
                  DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

    一些常用的时间间隔设置:

    (1)每隔5秒钟执行

    ON SCHEDULE EVERY 5 SECOND

    (2)每隔1分钟执行

    ON SCHEDULE EVERY 1 MINUTE

    (3)每天凌晨1点执行

    ON SCHEDULE EVERY 1 DAY STARTS DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL 1 HOUR)

    (4)每个月的第一天凌晨1点执行

    ON SCHEDULE EVERY 1 MONTH STARTS DATE_ADD(DATE_ADD(DATE_SUB(CURDATE(),INTERVAL DAY(CURDATE())-1 DAY),INTERVAL 1 MONTH),INTERVAL 1 HOUR)

    (5)每 3 个月,从现在起一周后开始

    ON SCHEDULE EVERY 3 MONTH STARTS CURRENT_TIMESTAMP + 1 WEEK

    (6)每十二个小时,从现在起三十分钟后开始,并于现在起四个星期后结束

    ON SCHEDULE EVERY 12 HOUR STARTS CURRENT_TIMESTAMP + INTERVAL 30 MINUTE ENDS CURRENT_TIMESTAMP + INTERVAL 4 WEEK

     (7)在yyyy-MM-dd hh:mm:ss执行

    on schedule at '2016-10-01 21:50:00'                  -- 在2016年10月1日,晚上9点50执行

    示例1:创建名称为event_user的事件,用于每隔5秒钟向数据表tb_user(用户信息表)中插入一条数据。

    (1)首先创建tb_user(用户信息表)。

    -- 创建用户信息表
    
    CREATE TABLE IF NOT EXISTS tb_user
    
    (
    
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户编号',
    
    name VARCHAR(30) NOT NULL COMMENT '用户姓名',
    
    create_time TIMESTAMP COMMENT '创建时间'
    
    ) COMMENT = '用户信息表';

    (2)创建事件。

    -- 创建事件
    
    CREATE EVENT IF NOT EXISTS event_user
    
    ON SCHEDULE EVERY 5 SECOND
    
    ON COMPLETION PRESERVE
    
    COMMENT '新增用户信息定时任务'
    
    DO INSERT INTO tb_user(name,create_time) VALUES('pan_junbiao的博客',NOW());

    执行结果:

    示例2:创建一个事件,实现每个月的第一天凌晨1点统计一次已经注册的会员人数,并插入到统计表中。

    (1)创建名称为p_total的存储过程,用于统计已经注册的会员人数,并插入到统计表tb_total中。

    CREATE PROCEDURE p_total()
    
    BEGIN
    
    DECLARE n_total INT default 0;
    
    SELECT COUNT(*) INTO n_total FROM db_database11.tb_user;
    
    INSERT INTO tb_total (userNumber,createtime) VALUES(n_total,NOW());
    
    END;

    (2)创建名称为e_autoTotal的事件,用于在每个月的第一天凌晨1点调用存储过程。

    CREATE EVENT IF NOT EXISTS e_autoTotal
    
    ON SCHEDULE EVERY 1 MONTH STARTS DATE_ADD(DATE_ADD(DATE_SUB(CURDATE(),INTERVAL DAY(CURDATE())-1 DAY),INTERVAL 1 MONTH),INTERVAL 1 HOUR)
    
    ON COMPLETION PRESERVE ENABLE
    
    DO CALL p_total();

    查询事件

    在MySQL中可以通过查询information_schema.events表,查看已创建的事件。其语句如下:

    SELECT * FROM information_schema.events; 


    修改事件

    在MySQL 5.1及以后版本中,事件被创建之后,还可以使用ALTER EVENT语句修改其定义和相关属性。其语法如下:

    ALTER
    
    [DEFINER={user | CURRENT_USER}]
    
    EVENT [IF NOT EXISTS] event_name
    
    ON SCHEDULE schedule
    
    [ON COMPLETION [NOT] PRESERVE]
    
    [ENABLE | DISABLE | DISABLE ON SLAVE]
    
    [COMMENT 'comment']
    
    DO event_body;

    ALTER EVENT语句与CREATE EVENT语句基本相同。另外ALTER EVENT语句还有一个用法就是让一个事件关闭或再次活动。


    启动与关闭事件

    另外ALTER EVENT语句还有一个用法就是让一个事件关闭或再次活动。

    示例:启动名称为event_user的事件。

    ALTER EVENT event_user ENABLE;

    示例:关闭名称为event_user的事件。

    ALTER EVENT event_user DISABLE;


    删除事件

    在MySQL 5.1及以后版本中,删除已经创建的事件可以使用DROP EVENT语句来实现。

    示例:删除名称为event_user的事件。

    DROP EVENT IF EXISTS event_user;

    展开全文
  • 习题 9.8 修改第6题的程序,增加一个fun函数,改写main函数。改为在fun函数中调用change和display函数。在fun函数中使用对象的引用(Student &)作为形参。 代码: #include <iostream> using ...

    习题 9.8 修改第6题的程序,增加一个fun函数,改写main函数。改为在fun函数中调用change和display函数。在fun函数中使用对象的引用(Student &)作为形参。

    代码:

    #include <iostream>
    using namespace std;
    class Student
    {
    public:
    	Student(int n, float s) :num(n), score(s) {}
    	void change(int n, float s) { num = n; score = s; }
    	void display() { cout << num << " " << score << endl; }
    private:
    	int num;
    	float score;
    };
    
    int main()
    {
    	void fun(Student &);
    	Student stud(101, 78.5);
    
    	fun(stud);
    
    	return 0;
    }
    
    void fun(Student &s)
    {
    	s.display();
    	s.change(101, 80.5);
    	s.display();
    }

    运行结果:

    展开全文
  • nDPI注册自定义协议解析前言方法一:修改protos.txt方法二:添加源码在头文件添加新的协议ID编写协议源文件做好相关定义编写ndpi_search函数编写dissector函数在ndpi_main.c中注册测试 前言 ​ 最近需要用到nDPI流量...

    前言

    ​ 最近需要用到nDPI流量监测工具,由于其example中的ndpiReader已经十分强大,所以打算在ndpiReader的基础上增加可识别的自定义协议。

    在ndpiReader中增加自定义协议的方式有两种:

    1. 是通过编辑example/protos.txt文件来使ndpiReader匹配相应的协议,但是这种的匹配条件有限,只能通过端口、host和IP地址来定义新协议。修改方法简单,但在需要根据协议格式匹配新协议的时候不适用。
    2. 通过在源码中注册自定义协议以及自己编写自定义协议的匹配逻辑,来自定义协议。这种方法相对于方法一来说相对复杂(其实也并不算复杂),但是协议自定义匹配方式更为灵活。

    对于方法一,网上已经有很多阐述,本文中仅会做简单说明(详细请百度:ndpi 自定义协议)。

    而对于方法二,好像目前并没有太多人来解释如何增加并注册自定义协议,网络上现有的文章以及Ntop官方给出的资料似乎是针对nDPI过去的版本进行说明。官方文档及网上大部分说法中的许多函数在现有版本中已经做了变动。

    经过本人的摸索,希望将在nDPI中自定义协议的方式记录并分享给大家。

    方法一:修改protos.txt

    protos.txtexample文件中,文件内容如下:

    #  Format:
    #  <tcp|udp>:<port>,<tcp|udp>:<port>,.....@<proto>
    
    tcp:81,tcp:8181@HTTP
    udp:5061-5062@SIP
    tcp:860,udp:860,tcp:3260,udp:3260@iSCSI
    tcp:3000@ntop
    
    #  Subprotocols
    #  Format:
    #  host:"<value>",host:"<value>",.....@<subproto>
    
    host:"googlesyndication.com"@Google
    host:"venere.com"@Venere
    host:"kataweb.it",host:"repubblica.it"@Repubblica
    host:"ntop"@ntop
    #  IP based Subprotocols
    #  Format:
    #  ip:<value>,ip:<value>,.....@<subproto>
    
    ip:213.75.170.11@CustomProtocol
    

    至于怎么根据tcp和udp端口、host和IP地址来添加自定义协议,这个文件里已经给足了例子,大家可以依葫芦画瓢,笔者在此不详细赘述。

    方法二:添加源码

    以下说明在nDPI较新版本中的源码中添加自定义协议的详细步骤。

    为了方便说明,假设我们需要自定义的协议名字叫做ONEONEPROTOCOL。顾名思义,当应用层协议中的数据全为1,那么就认为这是ONEONEPROTOCOL协议。

    在头文件添加新的协议ID

    首先,每一个协议都必须在头文件ndpi_protocol_ids.h中有一个对应的协议ID的定义,例如我们可以找到CSGO的定义:

    NDPI_PROTOCOL_CSGO = 235, /* Counter-Strike Global Offensive, Dota = 2 */

    所以我们也要为ONEONEPROTOCOL注册一个协议ID:

    格式为NDPI_PROTOCOL_+协议名 = protocolID,注意此处的协议ID不可与已有的重复。

    NDPI_PROTOCOL_ONEONEPROTOCOL = 243, /* all of the tcp payload is 1*/

    编写协议源文件

    在定义完协议ID后,我们必须在src/lib/protocols/文件夹中创建自定义协议的源文件:oneoneprotocol.c。该文件用来识别协议格式。

    做好相关定义

    在定义完源文件后,我们需要在源文件中编写内容。首先我们需要导入必要的头文件以及做好相关定义:

    #include "ndpi_protocol_ids.h"
    #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_ONEONEPROTOCOL
    #include "ndpi_api.h"
    

    编写ndpi_search函数

    在完成上一步后,我们需要在自定义源文件中实现oneoneprotocol的检测函数,按照nDPI的规范,这个函数需要定义为void ndpi_search_+自定义协议名(struct dpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow)

    其中ndpi_struct为ndpiReader的检测模块结构体,flow为指向连接状态机的流指针(不懂这些是啥也没关系)。在这个函数体中,我们需要实现判断一个payload是否为oneoneprotocol协议:

    void ndpi_search_oneoneprotocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) {
        struct ndpi_packet_struct *packet = &flow->packet;
        u_int len = 0;
        u_int count = 0;
    
        NDPI_LOG_DBG(ndpi_struct, "search oneoneprotocol\n");
    
        while(len < packet->payload_packet_len) {
            if (ntohs(*((u_int16_t*)&packet->payload[len])) != '1'){
                count++;
            }
            len++;
        }
    
        if(count == packet->payload_packet_len - 1){
            NDPI_LOG_INFO(ndpi_struct, "found oneoneprotocol\n");
            ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ONEONEPROTOCOL, NDPI_PROTOCOL_UNKNOWN);
            return;
        }
    
        NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
    }
    

    写得可能有点乱,但大致的逻辑大家应该看得懂。。。

    首先我们将flow中的flow->packet取出并存放在packet变量中,然后输出调试信息:NDPI_LOG_DBG(ndpi_struct, "search oneoneprotocol\n");。在接下来的语句中就是很简单的判断是否每一个字节都是1了。其中packet->payload_packet_len表示当前检测的payload的长度,packet->payload为当前payload的首地址。

    注意,如果我们发现当前协议为oneoneprotocol的话,我们使用函数ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ONEONEPROTOCOL, NDPI_PROTOCOL_UNKNOWN)来将ndpi_struct中默认的NDPI_PROTOCOL_UNKNOWN标识为NDPI_PROTOCOL_ONEONEPROTOCOL,否则我们使用NDPI_EXCLUDE_PROTO(ndpi_struct, flow)语句来表示判断不成立。

    编写dissector函数

    在写完ndpi_search_oneoneprotocol函数后,在oneoneprotocol.c文件中还需包含另外一个函数:void init_+自定义协议名+_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask)用来绑定oneoneprotocol的协议掩码相关信息:

    void init_oneoneprotocol_dissector(struct ndpi_detection_module_struct *ndpi_struct,
                                 u_int32_t *id,
                                 NDPI_PROTOCOL_BITMASK *detection_bitmask) {
        ndpi_set_bitmask_protocol_detection("oneoneprotocol", ndpi_struct, detection_bitmask, *id,
                                            NDPI_PROTOCOL_ONEONEPROTOCOL,
                                            ndpi_search_oneoneprotocol,
                                            NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD,
                                            SAVE_DETECTION_BITMASK_AS_UNKNOWN,
                                            ADD_TO_DETECTION_BITMASK);
        *id += 1;
    }
    

    可以看到,这个函数主要就是调用了ndpi_set_bitmask_protocol_detection函数,而在这个detection函数中,我们需要重点关注一下几点:

    1. 第一个字符串参数可以传入当前的自定义协议名
    2. 参数第二行的NDPI_PROTOCOL_ONEONEPROTOCOL为在ndpi_protocol_ids.h定义的宏
    3. 参数第三行应该传入刚刚写的检测函数的函数名
    4. 参数第四行为协议的掩码

    关于这个协议的掩码,所有的掩码都在中有着很方便地定义在src/include/ndpi_define.h中,以下为其中一小段:

    /* v4 or v6 */
    #define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP)
    
    
    #define NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD		(NDPI_SELECTION_BITMASK_PROTOCOL_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_V6_TCP_WITH_PAYLOAD		(NDPI_SELECTION_BITMASK_PROTOCOL_V6_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD		(NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
    
    

    其中v4和v6代表ipv4和ipv6协议,大家可以根据自定义协议的信息来确定这个协议掩码。例如本例oneoneprotocol在ipv4协议上,利用tcp的payload传输,所以选择NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD。如果大家不想找已有定义的话,也可以根据以下的宏定义用|自由组合:

    define NDPI_SELECTION_BITMASK_PROTOCOL_IP			(1<<0)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP			(1<<1)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP			(1<<2)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP		(1<<3)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD		(1<<4)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_NO_TCP_RETRANSMISSION	(1<<5)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_IPV6			(1<<6)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6		(1<<7)
    #define NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC	(1<<8)
    

    在ndpi_main.c中注册

    在实现完协议源文件后,我们只需在src/lib/ndpi_main.c中添加我们的函数。

    在1934行左右添加ndpi_set_proto_defaults函数,你们一定看得到上面那一排的其他协议的定义方式,我们同样也只需依葫芦画瓢:

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ONEONEPROTOCOL,
                                no_master,
                                no_master, "ONEONEPROTOCOL", NDPI_PROTOCOL_CATEGORY_CHAT,
                                ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
                                ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
    

    以下为几个参数的修改方法:

    • 第一个参数使用给定的默认值ndpi_mod

    • 第二个参数NDPI_PROTOCOL_ACCEPTABLE,这个宏定义在src/include/ndpi_typedefs.h中,需要选择相应的宏定义来表示自定义协议的安全性。

      typedef enum {
        NDPI_PROTOCOL_SAFE = 0,              /* Surely doesn't provide risks for the network. (e.g., a news site) */
        NDPI_PROTOCOL_ACCEPTABLE,            /* Probably doesn't provide risks, but could be malicious (e.g., Dropbox) */
        NDPI_PROTOCOL_FUN,                   /* Pure fun protocol, which may be prohibited by the user policy (e.g., Netflix) */
        NDPI_PROTOCOL_UNSAFE,                /* Probably provides risks, but could be a normal traffic. Unencrypted protocols with clear pass should be here (e.g., telnet) */
        NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, /* Surely is dangerous (ex. Tor). Be prepared to troubles */
        NDPI_PROTOCOL_TRACKER_ADS,           /* Trackers, Advertisements... */
        NDPI_PROTOCOL_UNRATED                /* No idea, not implemented or impossible to classify */
      } ndpi_protocol_breed_t;
      
    • 第三个参数需要改成我们在ndpi_protocol_ids.h定义的宏:NDPI_PROTOCOL_ONEONEPROTOCOL

    • 第四个参数使用给定的默认值no_master

    • 第五个参数使用给定的默认值no_master

    • 第六个参数为我们的协议名"ONEONEPROTOCOL"

    • 第七个参数NDPI_PROTOCOL_CATEGORY_CHAT,这个宏定义在src/include/ndpi_typedefs.h中,需要选择相应的宏定义来表示自定义协议的细分类型。

      typedef enum {
        NDPI_PROTOCOL_CATEGORY_UNSPECIFIED = 0,   /* For general services and unknown protocols */
        NDPI_PROTOCOL_CATEGORY_MEDIA,             /* Multimedia and streaming */
        NDPI_PROTOCOL_CATEGORY_VPN,               /* Virtual Private Networks */
        NDPI_PROTOCOL_CATEGORY_MAIL,              /* Protocols to send/receive/sync emails */
        NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER,     /* AFS/NFS and similar protocols */
        NDPI_PROTOCOL_CATEGORY_WEB,               /* Web/mobile protocols and services */
        NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK,    /* Social networks */
        NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT,       /* Download, FTP, file transfer/sharing */
        NDPI_PROTOCOL_CATEGORY_GAME,              /* Online games */
        NDPI_PROTOCOL_CATEGORY_CHAT,              /* Instant messaging */
        NDPI_PROTOCOL_CATEGORY_VOIP,              /* Real-time communications and conferencing */
        NDPI_PROTOCOL_CATEGORY_DATABASE,          /* Protocols for database communication */
        NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS,     /* Remote access and control */
        NDPI_PROTOCOL_CATEGORY_CLOUD,             /* Online cloud services */
        NDPI_PROTOCOL_CATEGORY_NETWORK,           /* Network infrastructure protocols */
        NDPI_PROTOCOL_CATEGORY_COLLABORATIVE,     /* Software for collaborative development, including Webmail */
        NDPI_PROTOCOL_CATEGORY_RPC,               /* High level network communication protocols */
        NDPI_PROTOCOL_CATEGORY_STREAMING,         /* Streaming protocols */
        NDPI_PROTOCOL_CATEGORY_SYSTEM_OS,         /* System/Operating System level applications */
        NDPI_PROTOCOL_CATEGORY_SW_UPDATE,         /* Software update */
      
        /* See #define NUM_CUSTOM_CATEGORIES */
        NDPI_PROTOCOL_CATEGORY_CUSTOM_1,          /* User custom category 1 */
        NDPI_PROTOCOL_CATEGORY_CUSTOM_2,          /* User custom category 2 */
        NDPI_PROTOCOL_CATEGORY_CUSTOM_3,          /* User custom category 3 */
        NDPI_PROTOCOL_CATEGORY_CUSTOM_4,          /* User custom category 4 */
        NDPI_PROTOCOL_CATEGORY_CUSTOM_5,          /* User custom category 5 */
      
        /* Payload Content */
        NDPI_CONTENT_CATEGORY_AVI,
        NDPI_CONTENT_CATEGORY_FLASH,
        NDPI_CONTENT_CATEGORY_OGG,
        NDPI_CONTENT_CATEGORY_MPEG,
        NDPI_CONTENT_CATEGORY_QUICKTIME,
        NDPI_CONTENT_CATEGORY_REALMEDIA,
        NDPI_CONTENT_CATEGORY_WINDOWSMEDIA,
        NDPI_CONTENT_CATEGORY_WEBM,
      
        /* Some custom categories */
        CUSTOM_CATEGORY_MINING           = 99,
        CUSTOM_CATEGORY_MALWARE          = 100,
        CUSTOM_CATEGORY_ADVERTISEMENT    = 101,
        CUSTOM_CATEGORY_BANNED_SITE      = 102,
        CUSTOM_CATEGORY_SITE_UNAVAILABLE = 103,
        
        NDPI_PROTOCOL_NUM_CATEGORIES /*
      				 NOTE: Keep this as last member
      				 Unused as value but useful to getting the number of elements
      				 in this datastructure
      			       */
      } ndpi_protocol_category_t;
      
    • 第八个参数ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */可以定义tcp默认端口,例如ndpi_build_default_ports(ports_a, 8080, 0, 0, 0, 0) /* TCP */代表自定义协议的默认端口为8080端口,而0代表没有默认端口。

    • 第九个参数同上。

    ndpi_main.c中为自定义协议添加了ndpi_set_proto_defaults函数后,我们只需在ndpi_set_protocol_detection_bitmask2函数中添加我们的函数:init_oneoneprotocol_dissector(ndpi_struct, &a, detection_bitmask);就大功告成了~

    测试

    在这里插入图片描述

    展开全文
  • frida hook常用函数分享 https://www.52pojie.cn/thread-1196917-1-1.html
  • ggplot绘图,利用geom_blank()函数 或 ggh4x包facetted_pos_scales( ) 函数来解决facet分面中y轴的自定义范围问题~
  • 后端框架flask学习小记

    千次阅读 2021-12-09 11:10:39
    路由 - 去找函数处理请求 web界面输入一个网址,点击回车, 其实是访问的web服务器,然后服务器结果返回到前端。 这个过程中有个匹配url的过程, 就是flask路由。 Flask中,路由是指用户请求的URL与视图函数之间...
  • c函数详解

    2018-07-22 19:34:20
    c语言函数库 目录 第一章(C标准库) 4 1. &lt;assert.h&gt;:诊断 4 2. &lt;ctype.h&gt;:字符类别测试 5 3. &lt;errno.h&gt;:错误处理 5 4. &lt;limits.h&gt;:整型常量 6 ...
  • jni函数说明

    千次阅读 2012-04-17 12:28:37
    函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。   好,下面我们用testdll.cpp文件具体实现这...
  • 110道python面试题

    千次阅读 2020-05-04 09:48:34
    1、一行代码实现1--100之和利用sum()函数求和2、如何在一个函数内部修改全局变量函数内部global声明 修改全局变量3、列出5个python标准库os:提供了不少与操作系统相关...
  • 那么也要v_fname改成VARCHAR2(30) 解决方法: v_fname VARCHAR2(25);---> v_fname s_emp.first_name%TYPE; v_id NUMBER(7);---> v_id s_emp.id%TYPE; v_salary NUMBER(11,2);---> v_salary s_...
  •  Lua 每一个 chunk 都作为一个匿名函数处理。需要 f=loadfile(...), f() == dofile(...) loadstring  与 loadfile 相似,只不过它不是从文件里读入 chunk,而是从一个串中读入。loadstring 总 是在全局环境中...
  • 该文110道面试题全部来自于大家笔试面试时候拍照后发...100之和利用sum()函数求和2、如何在一个函数内部修改全局变量利用global 修改全局变量3、列出5个python标准库os:提供了不少与操作系统相关联的函数sys: 通常...
  • JNI函数调用大全

    千次阅读 2015-04-27 23:10:07
    函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。  好,下面我们用testdll.cpp文件具体实现这两...
  • java面试常见知识点

    2020-02-15 21:27:01
    native是在java和其他语言(如c++)进行协作时使用的,也就是native后的函数的实现不是用java写的。 Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如...
  • 利用sum()函数求和 2、如何在一个函数内部修改全局变量 函数内部global声明 修改全局变量 3、列出5个python标准库 os:提供了不少与操作系统相关联的函数 sys: 通常用于命令行参数 re: 正则匹配 ....
  • JNI函数调用

    千次阅读 2014-11-07 16:18:30
    JNI函数调用 Jni函数调用 分类: C/C++/JAVA学习 2011-07-04 17:29 193人阅读 评论 (0) 收藏 举报     Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 ...
  • C语言经典面试题 与 C语言面试宝典

    万次阅读 多人点赞 2017-12-22 16:15:26
    文件中都含有该自定义的宏 __cplusplus*/ /* 这样通过 extern “C” 告诉 C++ 编译器, extern “C”{} 里包含的函数都用 C 的方式来编译 */ #ifdef __cplusplus   extern “C” { ...
  • 本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里...本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇,本文部分内容来源于网络,为了本文主题讲得清晰透...
  • 110 道 Python 面试笔试题超强汇总

    千次阅读 多人点赞 2019-03-05 23:31:04
    GIL 是 Python 的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行 Python 程序的时候会占用 Python 解释器(加了一锁即 GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。...
  • 3. 关系结构模型:关系式数据结构一些复杂的数据结构归结为简单的二元关系(即二维表格形式)。常见的有Oracle、mssql、mysql等 二、 主流数据库 数据库名 公司 特点 工作环境 mssql 微软 只能能运行在windows平台,...
  • C++补充

    2021-03-22 14:23:50
    6、基类的虚函数表存放在内存的什么区,虚表指针`vptr`的初始化时间7、`new / delete `与 `malloc / free`的异同8、`new`和`delete`是如何实现的?9、`malloc`和`new`的区别?10、宏定义和函数有何区别?11、宏定义...
  •  loop语句需要在循环体中自定义退出循环的语句,否则该循环将变成一个死循环  4、leave语句 相当于C++里面的break语句  注:在procedure 中声明变量用declare ,当声明varchar 变量时初始化,当变量用declare...
  • 《Spring Boot 实战开发》 附录 II : Spring Boot 2.0 新特性 ...不过,我们首先对 Kotlin 的特性的支持放在最前面讲,因为这是一个让人兴奋、迫不及待想要第一时间了解的特性。 Kotlin 特性支持 由于Ko...
  • Web-拾贝

    千次阅读 2018-03-10 13:45:55
    当解释程序遇到这类字符串时就它解释为真实的字符。在输入转义字符串时,要严格遵守字母大小写的规则。 第二个原因是,有些字符在ASCII字符集中没有定义,因此需要使用转义字符串来表示。 Browser兼容  table 的 ...
  • c++程序设计基础(第五版)(上) 习题与解答

    万次阅读 多人点赞 2019-09-09 20:06:44
    (A)库函数 (B)自定义函数 (C)main函数 (D)空函数 2.函数名是( ),用于识别和调用函数。 (A)关键字 (B)标识符 (C)常数 (D)语句 3.用C++语言编写的源程序要成为目标程序必须要经过( )...
  • Python笔试

    千次阅读 2019-09-13 10:13:03
    2、如何在一个function里面对一个全局变量赋值 使用关键字 global x = 0 def fun(): global x x = 1 print(x) 3、写出一段代码实现删除list里面的重复元素 使用set函数 l = [2, 3, 5, 8, 6, 1, 2, 5, 8, 10, 88, 62]...
  • Jni函数调用

    千次阅读 2014-05-20 18:59:39
    函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。   好,下面我们用testdll.cpp文件具体实现这...
  • } } OK,就是这么简单就完成了图片的加载,上面Glide中各函数的功能大家如果不懂的话去官网看一下就知道了,这里不多解释。本篇文章主要是讲加载https的图片。 二、加载Https图片 使用上面的代码去加载一些网络上的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 935
精华内容 374
关键字:

怎么把found改成自定义函数fun

友情链接: Dsound3DsrcDemo2006.rar