SDP协议解析C结构体

实现特定SDP报文转化为C结构

一、根据SDP规范定义C结构体

下面是一个sdp报文:

 1v=0
 2o=HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
 3s=test1.mp4 // test1.mp4:媒体文件名
 4c=IN IP4 0.0.0.0
 5t=0 0
 6a=control:*
 7a=range:npt=0-44.000000
 8m=video 0 RTP/AVP 96  // 96:track->payload_type 视频:96音频:97
 9a=control:trackID=101 // 101:轨道ID。 视频:101,102, 103 音频: 201,202, 203,204, 205
10a=rtpmap:96 MP4V-ES/90000
11a=fmtp:96 profile-level-id=2;config=000001b0020;
12m=audio 0 RTP/AVP 97
13a=control:trackID=201
14a=rtpmap:97 mpeg4-generic/24000/1
15a=fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;

需要注意的地方:

  1. 媒体数目, 媒体内的a字段数目都是不固定的。如果用C语言结构体装的话,可能需要内存分配和指针等东西。(目前想法是可以通过柔性数组处理实现)
  2. 单个字段内的数据组成也是不定的,可以先暂时取出文本域,再将文本域通过空格分为数组,至于之后的具体是什么内容,可以另写函数处理。

目前暂时就定义这个报文里面有的东西。

二、文件流模拟输入

读文件demo

feof函数判断文件结尾,fgets读取一行,遇到换行停止(会读取换行符)。

 1#include <stdio.h>
 2
 3int main(){
 4	FILE *file = NULL;
 5	char buff[255];
 6	
 7	file = fopen("sdp.txt", "r");
 8	if(file == NULL){
 9		printf("Error opening file");
10		return 1;
11	}
12	
13	while(!feof(file)){
14		fgets(buff, 255, (FILE*)file);
15		printf("%s", buff);
16	}
17
18		
19	fclose(file);
20	return 0;
21}
22

三、字符处理转化结构体

核心思想:

  • 先初始分配一个默认的内存, 存放1个媒体, 且a行只有1个。
  • 先扫描一遍文本,记录会话a行, 媒体个数, 各个媒体的a行个数。
  • 如果在上面的过程中遇到了多个a,多个m,使用字段记录变动的字段,再立刻使用realloc进行动态拓展。

遇到的问题:

  1. 多维数组指针索引遍历问题, 这个去翻C Primer Plus的302页即可。(已解决)
  2. 结构体中的柔性数组内存分配。(以解决)
  3. 字符打印始终是乱码,乱码出现在柔性数组那里,会话部分有些乱码有些没有,且每次乱码的位置都是固定的。但是后面的柔性数组没有乱码。
  4. 好像malloc的内存自动会释放,如果自己再调用free的话会报free(): double free detected in tcache 2(已解决)
  5. 不额外写printf的话会报malloc(): corrupted top size的错误, 非常奇怪。

头文件sdp_handle.h:

 1#ifndef SDP_HANDLE_H
 2#define SDP_HANDLE_H
 3#define STR_LENGTH 64 
 4#define DEFAULT_MEDIA_ATTRIBUTE 2
 5#define DEFAULT_SESSION_ATTRIBUTE 2
 6
 7typedef struct _media {
 8	char information[STR_LENGTH]; //m
 9	int a_count; //记录一个媒体的a行个数
10	char attribute[][STR_LENGTH]; //a
11} Media;
12
13typedef struct _session {
14	char version; //v
15	char originator[STR_LENGTH]; //o
16	char name[STR_LENGTH]; //s
17	char connection_information[STR_LENGTH]; //c
18	int time_active[2]; //t
19	int media_count; //记录一次会话的媒体个数
20	Media* media[5]; //m,由于变长数组只能有一个,暂时写死
21	int a_count; //记录会话的a属性个数
22	char attribute[][STR_LENGTH]; //a
23} Session;
24
25
26//字符串处理为结构体
27void handle_line_to_struct(Session* session, FILE* file);
28//打印会话信息到控制台
29void print_session(Session session);
30//将会话信息持久化到文件
31void save_session(Session session, char* file_name); 
32//创建会话内存
33Session*  malloc_session(Session* session);
34//清理会话内存
35void destory_session(Session* session);
36//创建媒体内存
37Media* malloc_media(Media* media);
38//清理媒体内存
39void destory_media(Media* media);
40//字符串指针复制函数
41void strcpy_p(char* from, char* dest, char stopsign);
42
43
44#endif

函数文件sdp_handle.c:

  1#include <stdio.h>
  2#include <string.h>
  3#include <stdlib.h>
  4#include <stdbool.h>
  5#include "sdp_handle.h"
  6
  7
  8//----------------------处理字符函数----------------
  9//处理字符串为结构体
 10void handle_line_to_struct(Session* session, FILE* file){
 11	char buff[255]; //缓冲区
 12	int handled_a = 0; //记录已经处理的会话a行数
 13	int handled_a_m = 0; //记录单个媒体已经处理的a行数
 14
 15	//1.读取文件
 16	//TODO 定义安全检查函数
 17	//TODO 错误处理
 18	while(true){
 19		fgets(buff, 255, (FILE*)file);
 20		char* str = buff;
 21
 22		//报文末尾判断
 23		if(feof(file)){
 24			break;
 25		}
 26	
 27		//2.对每行进行parse
 28		//char* seperate = strchr(str, '='); //显式地区分类别和文本
 29		char type = *str; //字段类别头
 30		char* begin = str+2; //文本的开始
 31		//printf("%c\n", type);
 32		switch(type){
 33			//TODO 覆盖一般SDP报文的内容
 34			case 'v':
 35				//暂时假设v字段只有1位
 36				session->version = *begin; 
 37				break;
 38			case 'o':
 39				strcpy_p(begin, session->originator, '\n');
 40				break;
 41			case 's':
 42				strcpy_p(begin, session->name, '\n');
 43				break;
 44			case 'c':
 45				strcpy_p(begin, session->connection_information, '\n');
 46				break;
 47			case 't':
 48				//由于时间有可能是长位的,采用atoi函数
 49				char* devide = strchr(begin, ' ');
 50				devide++;
 51				char start_time[20];
 52				char stop_time[20];
 53				strcpy_p(begin, start_time, ' ');
 54				strcpy_p(devide, stop_time, '\n');
 55				session->time_active[0] = atoi(start_time);
 56				session->time_active[1] = atoi(stop_time);
 57				break;
 58			case 'm':
 59				//创建媒体
 60				Media* media;
 61				media = malloc_media(NULL);
 62				handled_a_m = 0; //计数器清零
 63				strcpy_p(begin, media->information, '\n');
 64				*(session->media + session->media_count)= media; //赋值给对应的媒体段
 65				session->media_count++;
 66				media = NULL;
 67				break;
 68			case 'a':
 69				//判断a行的归属
 70				if(session->media_count == 0){
 71					//1.处理session的a
 72					//如果a段的内存不够,进行扩容
 73					if(handled_a >= session->a_count){
 74						session->a_count += 1; //一次扩容1行
 75						session = malloc_session(session);
 76					}
 77					//对应位置进行复制字符串
 78					strcpy_p(begin, *(session->attribute+handled_a), '\n');
 79					handled_a++;
 80				}else{
 81					//2.处理media的a
 82					//还是先检查内存是否足够
 83					Media* media_now;
 84					media_now = *(session->media+(session->media_count-1));
 85					if(handled_a_m >= media_now->a_count){
 86						media_now->a_count += 1; //一次扩容1行
 87						media_now = malloc_media(media_now);
 88					}
 89					//对应位置进行复制
 90					strcpy_p(begin, *(media_now->attribute+handled_a_m), '\n');
 91					handled_a_m++;
 92				}
 93				break;		
 94			default:
 95				printf("Unkown Type:%c\n", type);
 96		}
 97	}
 98	
 99}
100
101//复制字符串函数
102void strcpy_p(char* from, char* dest, char stopsign){
103	//复制
104	for(; *from != stopsign; from++, dest++){
105		*dest = *from;
106	}
107	//最后面加'\0'
108	*dest = '\0';
109}
110
111
112//-----------------输出函数-----------------
113
114//打印会话信息到控制台
115void print_session(Session session){
116	//printf("\n");
117	printf("Session Information:\n");
118	printf("------------------------------\n");
119	printf("version:%c\n", session.version);
120	printf("originator:%s\n", session.originator);
121	printf("name:%s\n", session.name);
122	printf("connection information:%s\n", session.connection_information);
123	printf("time_active, start:%d, end:%d\n", session.time_active[0], session.time_active[1]);
124	printf("attribute:\n");
125	for(int i = 0; i < session.a_count; i++){
126		printf("%s\n", (*(session.attribute+i)));
127	}
128	printf("\n");
129	printf("\n");
130
131	if(session.media_count == 0){
132		printf("No Media Information!\n");
133	}else{
134		printf("Media Information:\n");
135		printf("------------------------------\n");
136		for(int i = 0; i < session.media_count; i++){
137			Media* media_now = *(session.media+i);
138			printf("Media %d:\n", (i+1));
139			printf("information:%s\n", media_now->information);
140			printf("attribute:\n");
141			for(int j = 0; j < media_now->a_count; j++){
142				printf("%s\n", *(media_now->attribute + j));
143			}
144			printf("\n");
145		}
146	}
147}
148
149
150
151//会话持久化到rst文件
152void save_session(Session session, char* file_name){
153	//1.创建文件
154	FILE* write_file = fopen(file_name, "w+");
155
156	//2.写入文件
157	fprintf(write_file, "Session Information:\n");
158	fprintf(write_file, "------------------------------\n");
159
160	fprintf(write_file, "#. ");
161	fprintf(write_file, "version: ");
162	fputc(session.version, write_file);
163	fprintf(write_file, "\n\n");
164
165	fprintf(write_file, "#. ");
166	fprintf(write_file, "originator: ");
167	fprintf(write_file, "%s\n\n", session.originator);
168
169
170	fprintf(write_file, "#. ");
171	fprintf(write_file, "name: ");
172	fprintf(write_file, "%s\n\n", session.name);
173
174	fprintf(write_file, "#. ");
175	fprintf(write_file, "connection information: ");
176	fprintf(write_file, "%s\n\n", session.connection_information);
177
178	fprintf(write_file, "#. ");
179	fprintf(write_file, "time_active, start: ");
180	fprintf(write_file, "%d", session.time_active[0]);
181	fprintf(write_file, ", end: ");
182	fprintf(write_file, "%d\n\n", session.time_active[1]);
183
184	fprintf(write_file, "#. ");
185	fprintf(write_file, "attribute: \n");
186	for(int i = 0; i < session.a_count; i++){
187		fprintf(write_file, "	- %s\n", (*(session.attribute+i)));
188	}
189	fprintf(write_file, "\n\n");
190
191	if(session.media_count == 0){
192		fprintf(write_file, "No Media Information!\n\n");
193	}else{
194		fprintf(write_file, "Media Information:\n");
195		fprintf(write_file, "------------------------------\n");
196		for(int i = 0; i < session.media_count; i++){
197			Media* media_now = *(session.media+i);
198
199			fprintf(write_file, "Media %d:\n\n", (i+1));
200
201			fprintf(write_file, "information:");
202			fprintf(write_file, "%s\n\n", media_now->information);
203
204			fprintf(write_file, "attribute:\n\n");
205			for(int j = 0; j < media_now->a_count; j++){
206				fprintf(write_file, "#. ");
207				fprintf(write_file, "%s\n\n", *(media_now->attribute + j));
208			}
209		}
210	}
211
212	//3.关闭文件
213	fclose(write_file);
214
215}
216
217
218
219
220//----------------处理内存-------------------
221
222//分配会话内存
223Session* malloc_session(Session* session){
224	Session* session_new = NULL;
225	if(session == NULL){
226		//初始化
227		printf("Initial session memory:%ld\n", sizeof(Session) + DEFAULT_SESSION_ATTRIBUTE * STR_LENGTH * sizeof(char));
228		session_new = (Session*)malloc(sizeof(Session) + DEFAULT_SESSION_ATTRIBUTE * STR_LENGTH * sizeof(char));
229		session_new->a_count=DEFAULT_SESSION_ATTRIBUTE;
230		session_new->media_count = 0;
231	}else{
232		//根据a_count动态分配内存
233		printf("Realloc session memory:%ld\n", sizeof(Session) + session->a_count * STR_LENGTH * sizeof(char));
234		session_new = (Session*)realloc(session, sizeof(Session) + session->a_count * STR_LENGTH * sizeof(char));
235	}
236
237	//判断内存是否分配成功
238	if(session_new == NULL){
239		printf("No aviliable memory for session!\n");
240	}
241
242	return session_new;
243}
244
245//清理会话内存
246void destory_session(Session* session){
247	printf("Clear session memory\n");
248	for(int i = 0; i < session->media_count; i++){
249		destory_media(*(session->media+i));
250	}
251	free(session);
252	session = NULL;
253}
254
255//分配媒体内存
256Media* malloc_media(Media* media){
257	Media* media_new = NULL;
258	//初始化判断
259	if(media == NULL){
260		//新分配内存
261		printf("Malloc new media memory\n");
262		media_new = (Media*)malloc(sizeof(Media) + DEFAULT_MEDIA_ATTRIBUTE*sizeof(char)*STR_LENGTH);
263		media_new->a_count = DEFAULT_MEDIA_ATTRIBUTE;
264	}else{		
265		//动态根据参数a_count分配内存
266		printf("Realloc media memory\n");
267		media_new = realloc(media, sizeof(Media)+media->a_count*sizeof(char)*STR_LENGTH);
268	}
269	
270	//内存分配失败检验
271	if(media_new == NULL){
272		printf("No memory aviliable for media\n");
273	}
274	
275	return media_new;
276}
277
278//清理媒体内存
279void destory_media(Media* media){
280	printf("Clear media memory\n");
281	free(media);
282	media == NULL;
283}
284

四、文件流模拟输出&格式化打印结构体

对应函数在上面那节已经给出。

main.c文件:

 1#include <stdio.h>
 2#include <string.h>
 3#include "sdp_handle.h"
 4
 5//TODO 修复不打印额外字符会出现:malloc(): corrupted top size的bug
 6//命令行参数1作读取的sdp报文文件
 7int main(int argc, char *argv[]){
 8	//文件指针
 9	FILE *file = NULL;
10	//会话结构体
11	Session* session = NULL;
12
13	//合法性检验
14	file = fopen("sdp.txt", "r");
15	if(file == NULL){
16		printf("Error opening file");
17		return 1;
18	}
19	//printf("Open file success\n");
20
21	//创建新会话
22	session = malloc_session(session);
23
24	//解析sdp.txt文件
25	handle_line_to_struct(session, file);
26	//关闭文件
27	fclose(file);
28
29	//打印结构体到控制台
30	print_session(*session);
31
32	//保存结构体为rst文件
33	save_session(*session, argv[1]);
34
35	//回收内存
36	destory_session(session);
37
38	return 0;
39}

Makefile:

 1out: main.o sdp_handle.o
 2	gcc -o out main.o sdp_handle.o
 3
 4sdp_handle.o:sdp_handle.c
 5	gcc -c sdp_handle.c 
 6
 7main.o: main.c
 8	gcc -c main.c 
 9
10
11clean:
12	rm main.o sdp_handle.o out
13
14run:
15	./out sdp.rst

命令行输出测试:

 1./out sdp.rst
 2Initial session memory:384
 3Malloc new media memory
 4Malloc new media memory
 5Realloc media memory
 6Session Information:
 7------------------------------
 8version:0
 9originator:HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
10name:test1.mp4
11connection information:IN IP4 0.0.0.0
12time_active, start:0, end:0
13attribute:
14cont��{�
15*p
16
17
18Media Information:
19------------------------------
20Media 1:
21information:video 0 RTP/AVP 96
22attribute:
23control:trackID=101
24fmtp:96 profile-level-id=2;config=000001b0020;
25
26Media 2:
27information:audio 0 RTP/AVP 97
28attribute:
29control:trackID=201
30rtpmap:97 mpeg4-generic/24000/1
31fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;
32
33Clear session memory
34Clear media memory
35Clear media memory

写文件输出测试:

 1Session Information:
 2------------------------------
 3#. version: 0
 4
 5#. originator: HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
 6
 7#. name: test1.mp4
 8
 9#. connection information: IN IP4 0.0.0.0
10
11#. time_active, start: 0, end: 0
12
13#. attribute: 
14	- contx�s�
15	- �
16
17
18Media Information:
19------------------------------
20Media 1:
21
22information:video 0 RTP/AVP 96
23
24attribute:
25
26#. control:trackID=101
27
28#. fmtp:96 profile-level-id=2;config=000001b0020;
29
30Media 2:
31
32information:audio 0 RTP/AVP 97
33
34attribute:
35
36#. control:trackID=201
37
38#. rtpmap:97 mpeg4-generic/24000/1
39
40#. fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;

可见还是存在些许问题的,有些字段始终是乱码的, 但是找不到原因。

实现解析任意SDP报文

尚未解决问题:

  • 各结构体需定义完善,覆盖所有可能的字段
  • session段规定的一些字段是media段的默认值
  • 有些字段如果media段规定,session段不需要规定
  • optional字段需要注意
  • 文本域里面的文本解析分离
  • 需要判断报文的可用性,判断格式是否符合规范

暂时不考虑。

参考

下面是做这次小软件参考的链接:

C语言中的结构体指针_结构体指针c语言-CSDN博客

C 结构体 | 菜鸟教程

如何使用fgets防止缓冲区溢出?_如何防止fgets在缓冲区溢出时多次运行?_防止缓冲区溢出 - 腾讯云开发者社区 - 腾讯云

C语言基础—文件读写 - 知乎

vim 配置c/c++开发环境_vim配置c++开发环境 deepin-CSDN博客

(84 封私信 / 10 条消息) 如何在 Linux 下利用 Vim 搭建 C/C++ 开发环境? - 知乎

结构体嵌套问题的常见错误_c语言 结构体里嵌套结构体数组-CSDN博客

【转载】在linux下使用gcc/g++编译多个.h .c 文件_arm-linux-gnueabi-gcc编译多个。c 。h文件-CSDN博客

C语言动态内存分配_使用realloc重新分配内存后,务必要将原有地址使用free释放,否则会出现内存泄-CSDN博客

「C系列」C 错误处理_c语言错误处理-CSDN博客

realloc(): invalid next size: C语言报错-CSDN博客

C> fgets读取文件最后一行重复问题 - 明明1109 - 博客园

C语言打印输出字符串的几种方法_c语言输出字符串-CSDN博客

C++结构体中变长数组的使用问题分解刨析_C 语言_脚本之家

C语言——指针(数组,函数)_数组指针-CSDN博客