kamailio二次开发简述版完整教程(附上小例子的源码)

关于kamailio module的二次开发,仅以实际项目中的一个小例子为主,讲解一个module从编写到调用的整个周期。

背景:在视频会议中,我们需要在信令层记住与会者相关信息,通常为fromtag,totag,callid,并且使得这些信息与会议房间相关联,比如通过会议房间号,你需要获得所有与会者的全部信息,想到的数据结构自然是htable+list。当然这不是重点,重点是如何在kamailio中以module的方式封装好它。

第一段讲解

第一步:明确你所需要暴露给配置脚本调用的函数,并弄清楚你所需要包含的头文件,以4.3版本为例,后续版本的目录结构会有变动,但道不变。这些是kamailio为我们所提供的函数,其中pvar代表与伪变量相关的定义,而mod_fix.h与sr_module.h则是kamailio module核心的头文件,而cfg_struct.h也是4.3版本下编写每个module所必须的头文件,locking.h对应的锁的一些相关操作,tm_load则是与事物相关的头文件。总之如果你不确定到底需要包含哪些头文件,就把我这个复制过去。

// auhor : L
// date  : 7.3
#include 
#include 
#include 
#include "../../pvar.h"
#include "../../mod_fix.h"
#include "../../trim.h"
#include "../../sr_module.h"
//#include "../../parser/hf.h"
#include "../../cfg/cfg_struct.h"
#include "../tm/tm_load.h"
#include "../../locking.h"

第二步,确定你所需要对外暴露的接口,

            以下是module的配置参数,有了如下的定义之后,则可以在路由脚本中使用这个参数,之后会通过编写的配置脚本进行进一步解释;

static param_export_t mod_params[]={

	{"hashsize", INT_PARAM, &hashsize}, 

	{ 0,0,0 }
};

            以下是module暴露的接口,用于cfg脚本直接调用,其格式如下,其中room_entry_add对应函数名称,fixup_map以及fixup_map_free为检查调用时候所传入参数的个数是否与所定义的相符合,而最后一项ANY_ROUTE则表明该函数调用的作用范围,会在接下来的配置脚本进一步讲解。

static cmd_export_t cmds[] = {

  {"room_entry_add", (cmd_function)room_entry_add, 4, fixup_map, fixup_map_free, ANY_ROUTE},		

  {"entry_get_by_roomid", (cmd_function)entry_get_by_roomid, 4, fixup_room , fixup_room_free, ANY_ROUTE},

  {"print_all" , (cmd_function)print_a;;}

  {0, 0, 0, 0, 0, 0}	

};

     以下是关于kamailio module本身的一些信息,包括名称,暴露的参数,暴露的接口,以及初始化函数等等,当然具体你需要参考如下链接中的module规范,不在赘述。

http://www.asipto.com/pub/kamailio-devel-guide/#c16_add_parameter

struct module_exports exports = {

  "map_store",	

  DEFAULT_DLFLAGS,	

  cmds,        // export functions;	

  mod_params,  // export parameters;

  0,           /* exported statistics */

  0,           /* exported MI functions */

  0,           /* exported pseudo-variables */

  0,           /* extra processes */  

  mod_init,    // mod init function;

  0,

  0, // mod destory function

  0  

};

第三步:如果你所设计的module在调用时候需要做些初始化工作,则需要编写相应的mod_init的函数,以下是我所编写的:包括初始hash_size的设置,map_table(哈希表的)
初始化,以及有关于锁的一些初始化操作,很简单代码一目了然。

static int mod_init(void){
  //锁的初始化

  if(hashsize <=0 ){

    LM_ERR("hashsize is less than 0");

    return -1;	

  }  
  //hash table初始化;
  if(!(map_table = (struct hash_table*)shm_malloc(sizeof(struct hash_table)))){

	LM_ERR("MOD INIT MAP_TABLE FAILED");

	return -1;

  }   

  map_table->size = 0;   

  if(!(map_table->table = (struct map*)shm_malloc(sizeof(struct map)*hashsize))){

	LM_ERR("MOD INIT MAP FAILED");

	shm_free(map_table);

 	return -1;	   

  }
  
  //针对hash table的初始化;
  for(int i=0; itable[i].roomid = NULL;  

	map_table->table[i].size = 0;       

	map_table->table[i].next = NULL;  	

    map_table->table[i].entry = NULL;

  }  
  
  roomlock = lock_alloc();	
  //锁的初始化;  

  if(roomlock == NULL){

	 LM_ERR("lock alloc failed");

     return -1;	 

  }	
  
  if(lock_init(roomlock) == NULL){

	 LM_ERR("lock init failed");

     lock_dealloc(roomlock);

     return -1;	 

  }	

  return 0;	 	

}

通常你还需要编写一些额外的fixup函数用于检查函数调用时候出现的以外错误,通常如下:当你在路由脚本中调用函数,如果参数对不上的时候,就会将相应的问题传到到日志中,你可以通过日志追踪具体的问题。

static int fixup_map(void** param, int param_no){

  if(param_no <=4){

	return fixup_spve_null(param, 1);   

  }	

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	
}

static int fixup_map_free(void** param, int param_no){

  if (param_no <= 4) {

	return fixup_free_spve_null(param, 1);

  }	

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;			

}
// mabybe is only exists three param;

static int fixup_room(void** param, int param_no){

  if(param_no <=4){

	return fixup_spve_null(param, 1);   

  }

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	
}

static int fixup_room_free(void** param, int param_no){

  if(param_no <=4){

	return fixup_free_spve_null(param, 1);   

  }

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	

综上以上的总代码如下,(包含了我自身所定义简单的哈希表+list这样的数据结构的头文件)

// auhor : L
// date  :   7.3

#include 
#include 
#include 

#include "../../pvar.h"

#include "../../mod_fix.h"

#include "../../trim.h"

#include "../../sr_module.h"
//#include "../../parser/hf.h"
#include "../../cfg/cfg_struct.h"

#include "../tm/tm_load.h"

#include "../../locking.h"

// self definition header
#include "map_store.h"

#include "datatype.h"

#include "global.h"


MODULE_VERSION

static int mod_init(void);

static int fixup_map(void** param, int param_no);

static int fixup_map_free(void **param,int param_no);

static int fixup_room(void** param,int param_no);

static int fixup_room_free(void** param, int param_no);

// in global.h
struct hash_table* map_table ; //global variable;

struct map* current = NULL;

unsigned int current_size = 0;

gen_lock_t *roomlock;          // lock the hash table; 
                               // 需要改进需要多把锁;
                               // 可实现定期的把数据down到数据库里
unsigned int hashsize = 100;

/********************

***cmd export

********************/
static cmd_export_t cmds[] = {

  {"room_entry_add", (cmd_function)room_entry_add, 4, fixup_map, fixup_map_free, ANY_ROUTE},		

  {"entry_get_by_roomid", (cmd_function)entry_get_by_roomid, 4, fixup_room , fixup_room_free, ANY_ROUTE},

  {"print_all" , (cmd_function)print_a;;}

  {0, 0, 0, 0, 0, 0}	

};

/***********************

*Script Parameters

***********************/

static param_export_t mod_params[]={

	{"hashsize", INT_PARAM, &hashsize},

	{ 0,0,0 }

};

// 可能需要锁的初始化;

/********************

* module exports

********************/
struct module_exports exports = {

  "map_store",	

  DEFAULT_DLFLAGS,	

  cmds,        // export functions;	

  mod_params,  // export parameters;

  0,           /* exported statistics */

  0,           /* exported MI functions */

  0,           /* exported pseudo-variables */

  0,           /* extra processes */  

  mod_init,    // mod init function;

  0,

  0, // mod destory function

  0  

};

static int mod_init(void){
  //锁的初始化

  if(hashsize <=0 ){

    LM_ERR("hashsize is less than 0");

    return -1;	

  }  
  //hash table初始化;
  if(!(map_table = (struct hash_table*)shm_malloc(sizeof(struct hash_table)))){

	LM_ERR("MOD INIT MAP_TABLE FAILED");

	return -1;

  }   

  map_table->size = 0;   

  if(!(map_table->table = (struct map*)shm_malloc(sizeof(struct map)*hashsize))){

	LM_ERR("MOD INIT MAP FAILED");

	shm_free(map_table);

 	return -1;	   

  }
  
  //针对hash table的初始化;
  for(int i=0; itable[i].roomid = NULL;  

	map_table->table[i].size = 0;       

	map_table->table[i].next = NULL;  	

    map_table->table[i].entry = NULL;

  }  
  
  roomlock = lock_alloc();	
  //锁的初始化;  

  if(roomlock == NULL){

	 LM_ERR("lock alloc failed");

     return -1;	 

  }	
  
  if(lock_init(roomlock) == NULL){

	 LM_ERR("lock init failed");

     lock_dealloc(roomlock);

     return -1;	 

  }	

  return 0;	 	

}
/* Fixup Functions */
static int fixup_map(void** param, int param_no){

  if(param_no <=4){

	return fixup_spve_null(param, 1);   

  }	

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	
}

static int fixup_map_free(void** param, int param_no){

  if (param_no <= 4) {

	return fixup_free_spve_null(param, 1);

  }	

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;			

}
// mabybe is only exists three param;

static int fixup_room(void** param, int param_no){

  if(param_no <=4){

	return fixup_spve_null(param, 1);   

  }

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	
}

static int fixup_room_free(void** param, int param_no){

  if(param_no <=4){

	return fixup_free_spve_null(param, 1);   

  }

  LM_ERR("function takes at most 4 parameters.\n");

  return -1;	
}

以上主要讲解了map_store.c如何编写。当你编写一个xxx module的时候,通常目录下的xxxx.c这个文件中需要按照如上定义,并且文件名就是你所定义module的文件名,这样你可以很方便的复用kamailio本身套写的makefile。

第二段讲解

该部分主要展示了,你如何在kamailio中展现你的逻辑,仅适合初级者,我就当你看到这篇文章时,甚至连如何编译一个c语言程序都不懂来讲。

       首先明确你编写module的目的,一般来说是为了给cfg脚本调用,当然其目的也可以是给别的module调用。其实这两者的区别对于开发者来说,仅仅体现在module.c这个文件的定义上。
       明确了目的之后,你需要构思如何实现你所想要的逻辑,包括数据结构定义,以及一些.h以及,c之间的依赖关系,起始最差的情况,你实在理不清,无非是你把所有的全部写在一个文件里呗。 #_#
       以我所编写的为例子,包含如下:
        datatype.h 、 global.h 、 map_store.h map_util.h
        map_util.c map_store.c map_mod.c

       我所需要实现的是记录会议房间号 => 与会者成员的信息这样的映射关系,无非是htable => list, hash key是会议房间号,而list则是与会者每一条信息呗,其定义如下:(以下定义我觉得稍微有一点数据结构知识的人,都知道我在写什么,并没有花里胡哨的定义,其实有点类似于java中的currentHashMap但是我觉得当时编写的时候没有必须要,因为仅仅是demo版就简化着来吧,当然后续你可以拓展,拓展的思路无非是为每一个map_entry_array添加一把锁.)
对应datatype.h文件

#define ROOMSIZE 50
/*
***
   目前数据结构仅仅适用于一对多的通话,
   每一路通话的属性可以对应在map_entry_array中添加;
***
*/

/*

*  对应存储dialog每一条的信息;

*/
struct map_entry_array{

  char* callid;

  char* fromtag;

  char* totag;
  
  //sure you  can add more info there;
};

/*
* 对应每一个房间的信息,这个结构本身可能需要带锁,目前仅仅是最简单的实现;

*/
struct map{

  char* roomid;     // 房间号;

  int size;         // 当前房间dialog数目;
  
  struct map* next;        //

  struct map_entry_array** entry; //对应存储房间信息,目前默认每个房间开启roomsize为50;
  
  //you can add lock here (if you can);
  //you can add more information there;

};
/*
* 对应的hashtable, size仅仅做保留位;
*/
struct hash_table{

  struct map* table;  //  0  1 2 3....;

  int size;
};

而global.h中则定义了你所需要的全局变量,之所以拆分出来是为了防止map_mod.c在以后的拓展中过于臃肿,当然你也可以不拆,其定义如下, 主要定义了这个module中所需要使用到的全部的全局变量。(我觉得这样分离出来,看着稍微舒服点,而不是把所有的变量全部融合到一个文件中去。)

/*
   when you want to config more property;
*/
extern unsigned int hashsize ;       

extern struct hash_table* map_table;  //  map store the information;

extern struct map* current;           //  map current;

extern unsigned int current_size;     //  the current size of the room

gen_lock_t *roomlock;                 //  when you want get all the info of the room you have to get the lock

接着是map_util.h里定义的是你所使用到的内部函数实现,之所以有这个文件存在,主要因为配置脚本中伪变量的存在,会在后序配置脚本中详细介绍,其定义如下:

#ifndef _MAP_UTILS_H_

#define _MAP_UTILS_H_

#include "datatype.h"

#include "../../sr_module.h"

#include "../../lvalue.h"

#define CALLID  0

#define FROMTAG 1

#define TOTAG   2

typedef int (*jansson_to_val_f)(pv_value_t* , struct map_entry_array* , int );

int  char_to_val(pv_value_t* val,struct map_entry_array* entry,int type);//因为存放的是每一个房间的条目; 
#endif

map_store.h

#include "../../parser/msg_parser.h"

int room_entry_add(struct sip_msg* _m,char* _roomid, char* _callid, char* _fromtag, char* _totag);
int room_entry_delete(struct sip_msg* _m, char* _roomid, char* _callid, char* _fromtag, char* _totag); //好像
int entry_get_by_roomid(struct sip_msg* _m, char* roomid,char* callid, char* fromtag, char* totag); //result 
int print_by_room(struct sip_mag* _m, char* roomnew);
int print_all(struct sip_mas* _m);

/*  if you want to add some function to manage the server in signal just add function there*/
/*  web interface may interact int the cfg script                                          */

以上所有的.h仅仅是声明,我们需要在.c进行实现

map_util.c

#include
#include "map_util.h"
/*******************************************************************************

 * 名称: char_to_val

 * 功能: 把entry的三条属性存放到伪变量里;

 * 形参: val:伪变量,  entry: 存放房间属性; type: 以下有说明; 

 * 返回: 0成功,  -1失败;

 * 说明: 
 ******************************************************************************/

//   type : 0   callid;

//          1   fromtag;

//          2   totag;

int  char_to_val(pv_value_t* val, struct map_entry_array* entry,int type){ //因为存放的是每一个房间的条目;

  char* result = NULL;

  switch(type){

	case 0:

      result = entry->callid;

	  break;	

	case 1:

      result = entry->fromtag;

      break;

    case 2:

	  result = entry->totag;

	  break;

	default:

      return -1;

	  break;  	
  }  
  
  val->rs.s = result;            // to store the value;
  
  val->rs.len = strlen(result);
  
  val->flags = PV_VAL_STR;      // STR;
  
  return 0;
}

map_store.c : 主要实现了对外的关键函数调用,具体实现思路看代码无非是对哈希表以及list的操作;

#include
#include
//伪变量的操作部分;

#include "../../mod_fix.h"
#include "../../pvar.h"
#include "../../sr_module.h"
//#include "../../lvalue.h" 
#include "../../hashes.h"

//共享内存部分;
#include "../../mem/shm_mem.h"


//#include "datatype.h"
#include "global.h"
#include "map_util.h"

//
static void mem_release(struct map* current){

  unsigned int i=0;	

  for(i=0; ientry[i]); //release the entry memory;  	  

  }	

  shm_free(current->entry);

  shm_free(current);

}

// copy from jsonrpc module;
// private;
static char* shm_strdup(str *src)

{

	char *res;

    if (!src || !src->s)

		return NULL;
		
	if (!(res = (char *) shm_malloc(src->len + 1)))

		return NULL;

	strncpy(res, src->s, src->len);

	res[src->len] = 0;
	
	return res;
}

/*******************************************************************************

 * 名称: room_get_by_roomid

 * 功能: 在首次调用时候找到当前房间信息;

 * 形参: roomid 

 * 返回: room pointer, NULL失败意味着不存在这样的roomid。

 * 说明: 

 ******************************************************************************/
static struct map* room_get_by_roomid(const char* roomid){

  if(roomid == NULL){

	LM_ERR("room id si NULL");

    return NULL;	

  }

  unsigned int i = 0;
  
  struct map* room = NULL;

  for(i=0; itable[i].next;

    if(room == NULL) continue;	
	
	else{	  

	  while(room!=NULL){
		//find the target room;
		if(strcmp(roomid,room->roomid) == 0){

	     break;			

		}
		room = room->next;

	  }
	  
      if(room != NULL) break; //find it;	  
	}  
  }
  return room;  
}

/*******************************************************************************

 * 名称: room_entry_add

 * 功能: 往hash_table指定的room_id中添加dialog信息;

 * 形参: _roomid:    $rU;

 *       _callid:    $dlg(callid);

 *       _fromtag:   $dlg(fromtag);

 *       _totag:     $dlg(totag);

 *

 * 返回: 0成功, -1失败。

 * 说明: 中间的资源释放可能尚存在问题;

 ******************************************************************************/
int room_entry_add(struct sip_msg* _m,char* _roomid, char* _callid, char* _fromtag, char* _totag){	

  str key;       //roomid;

  str callid;	

  str fromtag;

  str totag;  

 
  if(fixup_get_svalue(_m,(gparam_p)_roomid,&key)!=0){

	LM_ERR("can not get key value");

    return -1;	

  }

  if(fixup_get_svalue(_m,(gparam_p)_callid,&callid)!=0){

	LM_ERR("can not get callid value");

    return -1;	

  }

  if(fixup_get_svalue(_m,(gparam_p)_roomid,&fromtag)!=0){

	LM_ERR("can not get fromtag value");

    return -1;	

  }

  if(fixup_get_svalue(_m,(gparam_p)_roomid,&totag)!=0){

	LM_ERR("can not get totag value");

    return -1;	

  }	

  struct map_entry_array* entry;

  if(!(entry = (struct map_entry_array*)shm_malloc(sizeof (struct map_entry_array)))){

	LM_ERR("Out of memory!");

	return -1;

  }
  // shared memory alloc
  entry->callid = shm_strdup(&callid);

  entry->fromtag = shm_strdup(&fromtag);

  entry->totag = shm_strdup(&totag);

  // 需要根据key找到table对应entry;
  unsigned int hash = get_hash1_case_raw(key.s,key.len); 

  LM_INFO("hash size is%d",hash);
  
  unsigned int slot = hash % hashsize;  

  struct map* room;
  
  struct map* start = map_table->table[slot].next;
  
  struct map* prev = start;

  //锁的问题,这样直接用可能显得过于笨重;  
  lock_get(roomlock);

  while(start!=NULL &&  strcmp(start->roomid, key.s) != 0){

	prev = start;
	
	start = start->next;				

  }  	
  //roomlock;
  if(start == NULL){
	  
	if(!(room = (struct map*) shm_malloc (sizeof (struct map)))){

	  LM_ERR("Out of memory!");

	  return -1;

    }
	room->size = 0;
	
	room->next = NULL;

	room->roomid = shm_strdup(&key);
	
	if(room->roomid == NULL){
		
	  LM_ERR("roomid allocate failed ");
	  
	  return -1;
    }	
	// key的问题

	room->entry = (struct map_entry_array**)shm_malloc(sizeof(struct map_entry_array*) * ROOMSIZE);

	if(room->entry == NULL){

	  LM_ERR("Room allocate fail");

      return -1;	  

	}

	room->entry[room->size] = entry;  //  

    room->size++;  

	if(prev == NULL){

	  map_table->table[slot].next = room;

	}
	else{ 

	  prev->next = room;

    }
  }

  else{

    if(start->size == ROOMSIZE){
		
	  LM_ERR("the room is full");

	  return -1;	  

	}   
	
	start->entry[start->size] = entry; 
	
    start->size++;  	

  }	

  lock_release(roomlock);  

  return 0;

}
/*******************************************************************************

 * 名称: entry_get_by_roomid

 * 功能: 根据roomid找到当前信息,需要调用room_get_by_roomid;

 * 形参: roomid ,  result暂时定位存放结果的魔法变量;

 * 返回: 

 * 说明: 由于是循环调用, 仅仅在首次调用的时候找当前的房间号; 后序操作都不需要再找当前的房间号;

   其中 current 与 current_size由于是全局变量所以在实际的调用中会随着循环调用而改变当前值;

 * 注意: 可能缺少魔法变量的具体的数据结构;  

 ******************************************************************************/

//这个函数存在优化的可能性;

int entry_get_by_roomid(struct sip_msg* msg, const char* roomid,char* callid,char* fromtag,char* totag){

  //current == NULL means the first call function;
  str key; 
  
  pv_spec_t* dst_pv1; 

  pv_spec_t* dst_pv2; 

  pv_spec_t* dst_pv3; 

  
  pv_value_t dst_val1;

  pv_value_t dst_val2;

  pv_value_t dst_val3;

  if(fixup_get_svalue(msg, (gparam_p)roomid, &key) != 0){  

    LM_ERR("room get failed");

	return -1;

  } 

  dst_pv1 = (pv_spec_t *)callid;

  dst_pv2 = (pv_spec_t *)fromtag;

  dst_pv3 = (pv_spec_t *)totag;

  //首次调用时, current为NULL;
  if(current == NULL){
	  
    current = room_get_by_roomid(roomid);

	if(current == NULL){

	  LM_ERR("the roomid is not exsits so can not find it");

      return -1;	  

	}
	
	current_size = current->size;  //the current room_size ;

	if(current_size < 0){
		
	  mem_release(current);  // 意味着当前房间信息全部获取完毕,需要释放当前资源;

      current = NULL; 	     // 为了下一次的首次调用;
	  
	  current_size = 0;
	  
	  return -1;			
	}

	else{

	  // 仍需要一个转化过程;
	  // store the dialog info in the $var(callid) , $var(fromtag), $var(totag);
      if(char_to_val(&dst_val1, current->entry[current_size], CALLID) == -1 ||
	  
		 char_to_val(&dst_val2, current->entry[current_size], FROMTAG) == -1 ||

	     char_to_val(&dst_val3, current->entry[current_size], TOTAG) == -1){

		 LM_ERR("result pv value set error");	
		 //可能存在内存泄漏问题;
		 return -1; 	 

	  }

      dst_pv1->setf(msg, &dst_pv1->pvp, (int)EQ_T, &dst_val1);
	  
      dst_pv2->setf(msg, &dst_pv2->pvp, (int)EQ_T, &dst_val2);      

	  dst_pv3->setf(msg, &dst_pv3->pvp, (int)EQ_T, &dst_val3);

	  current_size--;	  

	  return 0;

	}	 
  }

  else{

    //需要遍历整个room;   	  

	if(current_size <0){

	  mem_release(current);  // 意味着当前房间信息全部获取完毕,需要释放当前资源;
	  
      current = NULL; 	     // 为了下一次的首次调用;

	  current_size = 0;

	  return -1;		
	}
	else{

	  //store the dialog info;

      if(char_to_val(&dst_val1, current->entry[current_size], CALLID) == -1 ||

		 char_to_val(&dst_val2, current->entry[current_size], FROMTAG) == -1 ||
		 
	     char_to_val(&dst_val3, current->entry[current_size], TOTAG) == -1){
		 
		 LM_ERR("result pv value set error");			 
		 //可能存在内存泄漏问题;
		 return -1; 	 
	  }
	  
      dst_pv1->setf(msg, &dst_pv1->pvp, (int)EQ_T, &dst_val1);
	  
      dst_pv2->setf(msg, &dst_pv2->pvp, (int)EQ_T, &dst_val2);

	  dst_pv3->setf(msg, &dst_pv3->pvp, (int)EQ_T, &dst_val3);

	  current_size--;
	  
	  return 0;			
	}
  }
}
/*******************************************************************************

 * 名称: print_by_name

 * 功能: 打印指定房间的所有dlg信息;

 * 形参: s:  roomid(房间名称);

 *
 
 * 返回: 0成功, -1失败。

 * 说明: 仅在debug中调用查看当前module是否调用成功;

 ******************************************************************************/
void print_by_room(const char* s){

  unsigned int hash = get_hash1_case_raw(s,strlen(s)); 

  unsigned int slot = hash % hashsize;

  struct map* start = map_table->table[slot].next;

  if(start == NULL){

    LM_INFO("the room size if 0\n");

  }
  for(int i=0; isize; i++){

	LM_INFO("the callid is:%s,the fromtag is:%s, the totag is: %s\n",start->entry[i]->callid,start->entry[i]->fromtag,start->entry[i]->totag);  

  }

}
/*******************************************************************************
 * 名称: print_all

 * 功能: 打印所有房间的信息;

 * 形参: 无

 * 返回: 0成功, -1失败。

 * 说明: 仅在debug中调用查看当前module是否调用成功;

 ******************************************************************************/
void print_all(){

  unsigned int i=0;
  
  for(i=0; itable[i].next;
	
    while(room != NULL){

      LM_INFO("the room  is:%s\n",room->roomid);

      for(int j=0; jsize; j++){ 
      
        LM_INFO("the callid is:%s,the fromtag is:%s, the totag is: %s\n",room->entry[j]->callid,room->entry[j]->fromtag,room->entry[j]->totag);
      }
      room = room->next;
    }
  }
}

这一部分其实对于你掌握kamailio的编写可能无关紧要,不过还是看一看0_o,语言有时候是无力,你可以从代码里获得你想要的,结尾处我会附上所写的这个小例子以及教会如何编写简单的配置脚本。

第三阶段

当你从第一、第二阶段结束时,接下来你仅仅需要掌握cfg脚本的使用即可随心所欲的操作kamailio,使其执行任何你想要的功能,你可以从kamailio的官方module中查询,已被录用的module的用法。这里我想吐槽,那些文档其实对于开发者来说很不友好,需要你自己去猜它的功能,只能靠你自己积累如何使用了。(实验室的小伙伴们记得看cookbook)

首先你需要你需要第一部分,编译你所编写的module,进入到kamailio的安装目录:                     通常如下,以kamailio4.3为例子,你需要将你编写的module放到modules目录的文件夹下,并且修改套写的makefile ),仅仅更改name即可,当然不同的版本可能有些差异。

#
# 
#
# 
# WARNING: do not run this directly, it should be run by the master Makefile

include ../../Makefile.defs

auto_gen=

NAME = map_store.so

DEFS+=-DKAMAILIO_MOD_INTERFACE

include ../../Makefile.modules

接着进入/usr/local/src/kamailio-xxx/kamailio下进行你module的编译;

  sudo  make all   && sudo make install 

接着如果编译成功,如果编译失败的话, 找找你编写的逻辑是否有误,千万不要放弃哦,成功一次之后就有继续学习的动力了,实在不行可以在下方留言,我能解决的一定予以回答。

如果以上你全部成功了,恭喜你进入到最后路由脚本的编写:

首先如果你想加载使用任意一个模块,确保其已被编译成.so的形式,之后你可以通过loadmodule进行调用,如下:以下是我所编写cfg脚本实际调用的函数

 loadmodule  "map_store.so "
 loadmodule  "tm.so" 
 loadmodule  "jsonrpc-c.so"       ## 二次开发module
 loadmodule  "janssonrpc-c.so"   
 loadmodule  "jansson.so"
 loadmodule  "sdpops.so"             
 loadmodule  "dialog.so"           ## 二次开发module
 loadmodule  "db2_ops.so"          ## 存储模块with mysql
 loadmodule  "xprint.so"
 loadmodule  "xhttp.so"            ## rtcEnd模块
 loadmodule  "map_store.so"        ## 自己编写模块
  
 # ----------------- setting module-specific parameters ---------------
 // 这里你可以看到之前第一段介绍的export_param的作用,当通过modparam定义之后,就可以通过cfg脚本对module中暴露的函数通过cfg脚本进行初始化,如下所示,即是限定当前所设定哈希表的大小为500;
 
 modparam("map_store","hashsize",500);

       kamailio的配置脚本可称为路由脚本,因为他规定了kamailio在接收到每一个sip请求的动作,以及sip消息转发的目的地。

如下所示,使用之前暴露的map_store中暴露的print_all()测试函数,即打印当前房间中所有的用户信息。

    if(is_method("ACK")){
	   
       #t_release();

       dlg_manage();
         
       print_all();
	   
       #开启媒体流; 
       janssonrpc_notification("rpc_server","start",'{"callid":"$dlg(callid)","roomid":"$rU"}');  
    }

额外补充:当你完成一个module时,你可以参照我编写的Module对照着更改makefile即可,并把module放置在其余module的平行目录下,如下图所示即所编写的map_store module.
kamailio二次开发简述版完整教程(附上小例子的源码)_第1张图片

通知退回到安装目录(带makefile list的那个目录),执行sudo make all && sudo make install , 之后你就可以使用你所编译好的module了。

以上路由脚本需要自己看看cookbook之后就可以瞬间上手了,而至于kamailio的二次开发,则需要下点功夫,相信你可以很快的完成自己的第一个小demo.
源码连接为:https://github.com/theblackworld/kamailio/tree/master/map_store

你可能感兴趣的:(kamailio教程)