基于PJSIP的文章太多太多,但是大多数都是抄来抄去,自己创建的少之又少,最近因为项目的原因需要通过PJSIP来处理基于transaction的可靠消息发送,即发送不给予dialog的消息,但是又不需要自己去管理消息超时和重发,于是折腾来折腾去以后就找到了对应的方法。此程序源自于pjsip自带的stateless/stateful proxy,加以改进的地方是主动发送Message消息,如果要发送其他消息则可以修改msgGwTestSendMessage中的method,以及 MSG_TYPE_TXT MSG_SUBTYPE_TXT 等,makefile如下:
GCC = gcc
CUR_VERSION=$(shell date +%Y%m%d)
CFLAGS = -g -DPJ_HAS_LIMITS_H -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -D_CURRENT_MAKE_TIME=$(shell date +%s) -Werror -Wall
ROOTPATH=/root/pjproject-2.14.1/
INCLUDES= -I${ROOTPATH}/pjlib-util/include -I${ROOTPATH}/pjlib/include -I${ROOTPATH}/pjsip/include
LIB = -L${ROOTPATH}/pjlib/lib -L${ROOTPATH}/pjlib-util/lib -L${ROOTPATH}/pjnath/lib -L${ROOTPATH}/pjmedia/lib -L${ROOTPATH}/pjsip/lib -L${ROOTPATH}/third_party/lib -lpjsip-x86_64-unknown-linux-gnu -lpjlib-util-x86_64-unknown-linux-gnu -lpj-x86_64-unknown-linux-gnu -luuid -lm -lpthread
OBJ_MSGGW = msgGw.o
.c.o:
$(GCC) -c $(CFLAGS) $(INCLUDES) $<
all: msggw clean
msggw: ${OBJ_MSGGW}
$(GCC) -o bin/msgGw ${OBJ_MSGGW} $(CFLAGS) $(LIB)
clean:
rm *.o
配置文件如下:
LISTENPORT=3456
LOGLEVEL=1
MAXLOGLINE=50000
LOGFILE=/root/msggw/log/msgGw.log
源代码如下,基本上没有注释,将就看吧,重点是如何建立stateful的transtraction和自定义的消息类型,此处我使用的是application/json格式,当然也可以自己定义,测试环境使用sipp搭建,做了一个并发10000的消息发送/接收脚本,具体如何使用sipp请移步官方网站
//
//作者:陈到底------专注SIP通信24年
//
#include <pjsip.h>
#include <pjlib-util.h>
#include <pjlib.h>
#include <time.h>
#define APP_NAME "msgGw"
#define MAX_MSG_BODY_LEN 1500
#define MSG_TYPE_TXT "application"
#define MSG_SUBTYPE_TXT "json"
static pj_str_t msgContentType = { MSG_TYPE_TXT, strlen(MSG_TYPE_TXT)}, msgContentSubtype = { MSG_SUBTYPE_TXT, strlen(MSG_SUBTYPE_TXT)};
/* Options */
pj_caching_pool Gcp;
pjsip_endpoint *PjsipEndpoint;
int ServerListenPort;
pj_pool_t *PjsipMemPool;
pj_bool_t QuitFlag;
#define MAX_LOG_FILES 5
char DebugFileName[256];
int LogLevel;
int MaxLogFileLine;
int DebugFileLines;
FILE* DebugFp;
static pj_bool_t msgGwReceiveRxRequest(pjsip_rx_data *rdata );
static pj_bool_t msgGwReceiveRxResponse(pjsip_rx_data *rdata );
void initDebugFile(char* logDir,int logLevel)
{
DebugFileLines=0;
LogLevel=logLevel;
DebugFp=NULL;
strcpy(DebugFileName,logDir);
}
void myPhoneLogger(int level, const char *data, int len)
{
if(level>LogLevel)
{
return;
}
if(0==DebugFileName[0])
{
return;
}
if(NULL==DebugFp)
{
DebugFp=fopen(DebugFileName,"a+");
}
if(NULL==DebugFp)
{
fprintf(stderr,"Can't open debug file:%s\n",DebugFileName);
return;
}
fprintf(DebugFp,"%s",data);
if(DebugFileLines++>MaxLogFileLine)
{
fclose(DebugFp);
int i;
char tmpBuf1[512];
char tmpBuf2[512];
sprintf(tmpBuf1,"%s_5",DebugFileName);
unlink(tmpBuf1);
for(i=MAX_LOG_FILES;i>0;i--)
{
sprintf(tmpBuf1,"%s_%d",DebugFileName,i);
sprintf(tmpBuf2,"%s_%d",DebugFileName,i-1);
rename(tmpBuf2,tmpBuf1);
}
rename(DebugFileName,tmpBuf2);
DebugFileLines=0;
DebugFp=NULL;
}
if(NULL!=DebugFp)
{
if(0==level)
{
fflush(DebugFp);
}
else if(!(DebugFileLines%20))
{
fflush(DebugFp);
}
}
}
void safeDumpPjstr(pj_str_t* inPut, char* outPut, int maxLen)
{
int len;
outPut[0]=0;
if(NULL==inPut)
{
return;
}
if(NULL==inPut->ptr || 0==inPut->slen)
{
return;
}
len=inPut->slen;
if(len>maxLen-1)
{
len=maxLen-1;
}
memcpy(outPut,inPut->ptr,len);
outPut[len]=0;
}
static pj_bool_t msgGwLogRxSipMessage(pjsip_rx_data *rdata)
{
PJ_LOG(3,(APP_NAME, "RX %d bytes %s from %s %s:%d:\n"
"%.*s\n"
"--end msg--\n",
rdata->msg_info.len,
pjsip_rx_data_get_info(rdata),
rdata->tp_info.transport->type_name,
rdata->pkt_info.src_name,
rdata->pkt_info.src_port,
(int)rdata->msg_info.len,
rdata->msg_info.msg_buf));
return PJ_FALSE;
}
static pj_status_t msgGwLogTxSipMessage(pjsip_tx_data *tdata)
{
PJ_LOG(3,(APP_NAME, "TX %ld bytes %s to %s %s:%d:\n"
"%.*s\n"
"--end msg--\n",
(tdata->buf.cur - tdata->buf.start),
pjsip_tx_data_get_info(tdata),
tdata->tp_info.transport->type_name,
tdata->tp_info.dst_name,
tdata->tp_info.dst_port,
(int)(tdata->buf.cur - tdata->buf.start),
tdata->buf.start));
return PJ_SUCCESS;
}
/* The module instance. */
static pjsip_module mod_msg_logger =
{
NULL, NULL, /* prev, next. */
{ "mod-msg-logger", 14 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&msgGwLogRxSipMessage, /* on_rx_request() */
&msgGwLogRxSipMessage, /* on_rx_response() */
&msgGwLogTxSipMessage, /* on_tx_request. */
&msgGwLogTxSipMessage, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
static pj_status_t msgGwInitSipStack(void)
{
pj_status_t status;
pj_sockaddr_in addr;
status = pj_init();
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
status = pjlib_util_init();
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
pj_caching_pool_init(&Gcp, &pj_pool_factory_default_policy, 0);
status = pjsip_endpt_create(&Gcp.factory, NULL, &PjsipEndpoint);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
status = pjsip_tsx_layer_init_module(PjsipEndpoint);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Create listening transport */
addr.sin_family = pj_AF_INET();
addr.sin_addr.s_addr = 0;
addr.sin_port = pj_htons((pj_uint16_t)ServerListenPort);
status = pjsip_udp_transport_start( PjsipEndpoint, &addr, NULL, 1, NULL);
if (status != PJ_SUCCESS)
return status;
/* Create pool for the application */
PjsipMemPool = pj_pool_create(&Gcp.factory, "proxyapp", 1024*1024*1024, 1024*1024, NULL);
/* Register the logger module */
pjsip_endpt_register_module(PjsipEndpoint, &mod_msg_logger);
return PJ_SUCCESS;
}
static pj_status_t msgGwInitProcess(void)
{
pj_sockaddr pri_addr;
pj_sockaddr addr_list[16];
unsigned addrCnt = PJ_ARRAY_SIZE(addr_list);
unsigned i;
unsigned nameCnt=0;
pjsip_host_port hostPort[64];
/* List all names matching local endpoint.
* Note that PJLIB version 0.6 and newer has a function to
* enumerate local IP interface (pj_enum_ip_interface()), so
* by using it would be possible to list all IP interfaces in
* this host.
*/
/* The first address is important since this would be the one
* to be added in Record-Route.
*/
if (pj_gethostip(pj_AF_INET(), &pri_addr)==PJ_SUCCESS)
{
char addr[PJ_INET_ADDRSTRLEN];
pj_inet_ntop(pj_AF_INET(), &pri_addr.ipv4.sin_addr, addr, sizeof(addr));
pj_strdup2(PjsipMemPool, &hostPort[nameCnt].host, addr);
hostPort[nameCnt].port = ServerListenPort;
nameCnt++;
}
/* Get the rest of IP interfaces */
if (pj_enum_ip_interface(pj_AF_INET(), &addrCnt, addr_list) == PJ_SUCCESS)
{
for (i=0; i<addrCnt; ++i)
{
char addr[PJ_INET_ADDRSTRLEN];
if (addr_list[i].ipv4.sin_addr.s_addr == pri_addr.ipv4.sin_addr.s_addr)
continue;
pj_inet_ntop(pj_AF_INET(), &addr_list[i].ipv4.sin_addr, addr, sizeof(addr));
pj_strdup2(PjsipMemPool, &hostPort[nameCnt].host, addr);
hostPort[nameCnt].port = ServerListenPort;
nameCnt++;
}
}
/* Add loopback address. */
#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF
hostPort[nameCnt].host = pj_str("127.0.0.1");
hostPort[nameCnt].port = ServerListenPort;
nameCnt++;
#endif
hostPort[nameCnt].host = *pj_gethostname();
hostPort[nameCnt].port = ServerListenPort;
nameCnt++;
hostPort[nameCnt].host = pj_str("localhost");
hostPort[nameCnt].port = ServerListenPort;
nameCnt++;
PJ_LOG(1,(APP_NAME, "Proxy started, listening on port %d", ServerListenPort));
PJ_LOG(1,(APP_NAME, "Local host aliases:"));
for (i=0; i<nameCnt; ++i)
{
PJ_LOG(1,(APP_NAME, " %.*s:%d", (int)hostPort[i].host.slen, hostPort[i].host.ptr, hostPort[i].port));
}
return PJ_SUCCESS;
}
static int msgGwWorkerThread(void *p)
{
pj_time_val delay = {0, 10};
PJ_UNUSED_ARG(p);
while (!QuitFlag)
{
pjsip_endpt_handle_events(PjsipEndpoint, &delay);
}
return 0;
}
void msgGwUriProcessing(const pjsip_uri* inUri, char* outPut,int len)
{
pjsip_sip_uri *uri;
outPut[0]=0;
if(!PJSIP_URI_SCHEME_IS_SIP(inUri) && !PJSIP_URI_SCHEME_IS_SIPS(inUri))
{
PJ_LOG(1,(APP_NAME,"%s Not sip or sips uri",__func__));
return;
}
uri=(pjsip_sip_uri*)inUri;
safeDumpPjstr(&(uri->host), outPut, len-1);
//pjsip_uri_print(PJSIP_URI_IN_OTHER, uri, tmpString, sizeof(tmpString)-1);
//printf("URI String:\n%s\n",tmpString);
}
pj_bool_t msgGwProcessReceivedMessage(pjsip_rx_data *rdata)
{
pjsip_msg_body *body;
char msgBodyStr[MAX_MSG_BODY_LEN];
char fromUser[256];
char toUser[256];
int len;
if(NULL==rdata)
{
PJ_LOG(1,(APP_NAME,"%s rdata is null",__func__));
return PJ_FALSE;
}
//TODO:获取到发送人和接收人的名称,需要做检查处理
msgGwUriProcessing(rdata->msg_info.from->uri,fromUser,sizeof(fromUser)-1);
msgGwUriProcessing(rdata->msg_info.to->uri,toUser,sizeof(toUser)-1);
body=rdata->msg_info.msg->body;
if(NULL==body || NULL==body->data)
{
return PJ_FALSE;
}
safeDumpPjstr(&(body->content_type.type), msgBodyStr, sizeof(msgBodyStr)-1);
if(strcasecmp(MSG_TYPE_TXT,msgBodyStr))
{
PJ_LOG(1,(APP_NAME,"%s Get message but content type<%s>not supported",__func__,msgBodyStr));
return PJ_FALSE;
}
safeDumpPjstr(&(body->content_type.subtype), msgBodyStr, sizeof(msgBodyStr)-1);
if(strcasecmp(MSG_SUBTYPE_TXT,msgBodyStr))
{
PJ_LOG(1,(APP_NAME,"%s Get message but content subtype<%s>not supported",__func__,msgBodyStr));
return PJ_FALSE;
}
len=body->print_body(body, msgBodyStr, sizeof(msgBodyStr));
if(len<=0)
{
return PJ_FALSE;
}
//TODO:对接收到的消息体进行处理
return PJ_TRUE;
}
static pj_status_t msgGwVerifyRequest(pjsip_rx_data *rdata)
{
/* URI scheme */
if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.msg->line.req.uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri))
{
pjsip_endpt_respond_stateless(PjsipEndpoint, rdata, PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, NULL, NULL);
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_UNSUPPORTED_URI_SCHEME);
}
/* Max-Forwards */
if (rdata->msg_info.max_fwd && rdata->msg_info.max_fwd->ivalue <= 1)
{
pjsip_endpt_respond_stateless(PjsipEndpoint, rdata, PJSIP_SC_TOO_MANY_HOPS, NULL, NULL, NULL);
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TOO_MANY_HOPS);
}
//TODO:如果要限制消息来自某个特定的IP,则在此处做检查
//printf("Src Address:%s\n",rdata->pkt_info.src_name);
return PJ_SUCCESS;
}
static void msgGwSafeQuitStack(void)
{
pjsip_endpt_destroy(PjsipEndpoint);
pj_pool_release(PjsipMemPool);
pj_caching_pool_destroy(&Gcp);
pj_shutdown();
}
static pjsip_module msgGwModStruct =
{
NULL, NULL, /* prev, next. */
{ "mod-stateful-proxy", 18 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&msgGwReceiveRxRequest, /* on_rx_request() */
&msgGwReceiveRxResponse, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
static pj_status_t msgGwInitStateful(void)
{
pj_status_t status;
status = pjsip_endpt_register_module( PjsipEndpoint, &msgGwModStruct);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
return PJ_SUCCESS;
}
/* Callback to be called to handle new incoming requests. */
static pj_bool_t msgGwReceiveRxRequest( pjsip_rx_data *rdata )
{
pj_status_t status;
pjsip_method *method;
char tmpString[128];
//PJSIP_OTHER_METHOD
method=&(rdata->msg_info.msg->line.req.method);
safeDumpPjstr(&(method->name), tmpString, sizeof(tmpString)-1);
if(PJSIP_CANCEL_METHOD!=method->id && !strcasecmp(tmpString,"MESSAGE"))
{
status = msgGwVerifyRequest(rdata);
if (PJ_SUCCESS!=status)
{
PJ_LOG(1,(APP_NAME,"RX invalid request"));
return PJ_TRUE;
}
if(PJ_TRUE==msgGwProcessReceivedMessage(rdata))
{
pjsip_endpt_respond_stateless(PjsipEndpoint, rdata, 200, NULL, NULL, NULL);
}
else
{
pjsip_endpt_respond_stateless(PjsipEndpoint, rdata, PJSIP_SC_BAD_REQUEST, NULL, NULL, NULL);
}
}
else
{
pjsip_endpt_respond_stateless(PjsipEndpoint, rdata, PJSIP_SC_BAD_REQUEST, NULL, NULL, NULL);
}
return PJ_TRUE;
}
static pj_bool_t msgGwReceiveRxResponse( pjsip_rx_data *rdata )
{
PJ_LOG(1,(APP_NAME,"%s get response here",__func__));
return PJ_TRUE;
}
static void msgGwMessageTransactionCallBack(void *token, pjsip_event *event)
{
pjsip_transaction *tsx;
if (event->type != PJSIP_EVENT_TSX_STATE)
{
return;
}
tsx = event->body.tsx_state.tsx;
if (PJSIP_TSX_STATE_COMPLETED==tsx->state)
{
//TODO: 处理传输完成或者失败,需要对你自己的数据库做标记
PJ_LOG(1,(APP_NAME,"tsx->state:%d token:%p completed",tsx->state,token));
}
else if (PJSIP_TSX_STATE_TERMINATED==tsx->state)
{
PJ_LOG(1,(APP_NAME,"tsx->state:%d token:%p terminated",tsx->state,token));
}
else if (PJSIP_TSX_STATE_DESTROYED==tsx->state)
{
PJ_LOG(1,(APP_NAME,"tsx->state:%d token:%p destroyed",tsx->state,token));
}
}
//此处将生成你自己的 contentType 和 msg Body
void msgGwAppendMessageBody(pjsip_tx_data* tdata, char* msgBody)
{
pjsip_msg *msg;
pjsip_msg_body *body;
int len;
len=strlen(msgBody);
if(0==len)
{
return;
}
msg=tdata->msg;
body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
body->content_type.type = msgContentType;
body->content_type.subtype = msgContentSubtype;
body->data = pj_pool_alloc(tdata->pool, len+1);
pj_memcpy(body->data, msgBody, len);
body->len = (unsigned)len;
body->print_body = &pjsip_print_text_body;
msg->body = body;
}
static pj_status_t msgGwTestSendMessage(void)
{
pjsip_tx_data *tdata;
pj_status_t status;
pjsip_method method={PJSIP_OTHER_METHOD, {"MESSAGE", 7}};
char dstString[256];
pj_str_t dst_uri;
char localString[256];
pj_str_t local_uri;
char contactString[256];
pj_str_t local_contact;
char textString[1024];
long tmpToken;
sprintf(dstString,"<sip:chenjian@192.168.1.101:2345>");
sprintf(localString,"<sip:sender@192.168.1.101:3456>");
sprintf(contactString,"<sip:sender@192.168.1.101:3456>");
sprintf(textString,"{\"msgtype\": \"1\", \"subtype\":\"3\", \"body\": \"This is a test msg\"}");
dst_uri=pj_str(dstString);
local_uri=pj_str(localString);
local_contact=pj_str(contactString);
//pj_str_t pText;
//pText=pj_str(textString);
//status = pjsip_endpt_create_request(PjsipEndpoint, &method, &dst_uri, &local_uri, &dst_uri, &local_contact, NULL, -1, &pText, &tdata);
status = pjsip_endpt_create_request(PjsipEndpoint, &method, &dst_uri, &local_uri, &dst_uri, &local_contact, NULL, -1, NULL, &tdata);
if(PJ_SUCCESS!=status)
{
printf("%s can't create request\n",__func__);
return status;
}
msgGwAppendMessageBody(tdata, textString);
tmpToken=rand();
status=pjsip_endpt_send_request(PjsipEndpoint, tdata, -1, (void*)tmpToken, &msgGwMessageTransactionCallBack);
if (status != PJ_SUCCESS)
{
printf("Error sending stateful request\n");
}
return status;
}
int getConfigFromFile( FILE* fp, char *itemTitle, char *outvalue)
{
int i;
char fileBuffer[1024];
char itemFormat[256];
int ret=0;
fseek(fp,0,SEEK_SET);
sprintf(itemFormat,"%s=%%s",itemTitle);
outvalue[0]=0;
while (1)
{
memset(fileBuffer,0,sizeof(fileBuffer));
fgets( fileBuffer, sizeof(fileBuffer)-10, fp );
if( feof( fp ) )
{
break;
}
for(i=0;i<sizeof(fileBuffer)-10;i++)
{
if(fileBuffer[i]==' '||fileBuffer[i]=='\n')
{
fileBuffer[i]=0;
break;
}
}
if( (fileBuffer[0] == '#')||(fileBuffer[0] == ';')) //comment line
continue;
if(0==strncmp(fileBuffer,itemTitle,strlen(itemTitle)) && fileBuffer[strlen(itemTitle)]=='=')
{
if(sscanf(fileBuffer,itemFormat,outvalue)==1)
{
ret=1;
}
break;
}
}
return(ret);
}
void msgGwGetConfigFromFile(char* fileName)
{
FILE* fp;
char tmpString[1024];
int i;
char logFile[256];
ServerListenPort = 3456;
MaxLogFileLine=50000;
pj_log_set_level(1);
if((fp=fopen(fileName,"r"))==NULL)
{
printf("Can't open config file:%s\n",fileName);
exit(0);
}
if(getConfigFromFile(fp,"LISTENPORT", tmpString))
{
ServerListenPort=atoi(tmpString);
}
if(getConfigFromFile(fp,"MAXLOGLINE", tmpString))
{
MaxLogFileLine=atoi(tmpString);
}
if(getConfigFromFile(fp,"LOGLEVEL", tmpString))
{
i=atoi(tmpString);
if(i>=0)
{
pj_log_set_level(i);
}
}
logFile[0]=0;
if(getConfigFromFile(fp,"LOGFILE", tmpString))
{
strcpy(logFile,tmpString);
}
initDebugFile(logFile,i);
fclose(fp);
}
int main(int argc, char *argv[])
{
pj_status_t status;
pj_thread_t *threadPid;
int i;
srand(time(NULL));
msgGwGetConfigFromFile("msgGw.cfg");
pj_log_set_log_func(myPhoneLogger);
status = msgGwInitSipStack();
if (status != PJ_SUCCESS)
{
PJ_LOG(1,(APP_NAME,"Error initializing stack"));
return 1;
}
status = msgGwInitProcess();
if (status != PJ_SUCCESS)
{
PJ_LOG(1,(APP_NAME,"Error initializing proxy"));
return 1;
}
status = msgGwInitStateful();
if (status != PJ_SUCCESS)
{
PJ_LOG(1,(APP_NAME,"Error initializing stateful proxy"));
return 1;
}
status = pj_thread_create(PjsipMemPool, "sproxy", &msgGwWorkerThread, NULL, 0, 0, &threadPid);
if (status != PJ_SUCCESS)
{
PJ_LOG(1,(APP_NAME,"Error creating thread"));
return 1;
}
while (!QuitFlag)
{
char line[10];
if (fgets(line, sizeof(line), stdin) == NULL)
{
puts("EOF while reading stdin, will quit now..");
QuitFlag = PJ_TRUE;
break;
}
if(line[0]=='s')
{
while(1)
{
for(i=0;i<10000;i++)
{
msgGwTestSendMessage();
}
sleep(1);
}
}
else if (line[0] == 'q')
{
QuitFlag = PJ_TRUE;
}
else if (line[0] == 'd')
{
pj_bool_t detail = (line[1] == 'd');
pjsip_endpt_dump(PjsipEndpoint, detail);
pjsip_tsx_layer_dump(detail);
}
}
pj_thread_join(threadPid);
msgGwSafeQuitStack();
return 0;
}
main中通过s来开始发送消息,每发送10000条消息sleep 1 秒,经测试如果没有sleep的话会有内存连续增长的问题,加上sleep以后没有出现内存连续增长,此程序也能