C语言初学者编程水平上不来?不妨尝试这10个C语言例子

初学者通过下面几个c语言,大家可以提高自己的编程水平,

1. 打印任意一段内存的数据

void print_array(char *title, unsigned char *data, int len) {
    printf("%s:\n", title);
    for (int i = 0; i < len; i++) {
        // 每行开头打印当前字节的地址
        if (i % 16 == 0) {
            printf("0x%08X: ", (unsigned int)(data + i));
        }

        // 打印当前字节的十六进制形式
        printf("%02X ", data[i]);

        // 每行打印16个字节后换行,并打印ASCII字符
        if ((i + 1) % 16 == 0 || i == len - 1) {
            // 对齐填充
            for (int j = 0; j < 16 - (i % 16) - 1; j++) {
                printf("   ");
            }
            printf(" | ");
            // 打印ASCII字符
            for (int j = i - (i % 16); j <= i; j++) {
                if (data[j] >= 32 && data[j] <= 126) {
                    printf("%c", data[j]);
                } else {
                    printf(".");
                }
            }
            printf("\n");
        }
    }
    printf("----------------------------------------\n");
}

2. 实现下面信令的封装和解析,只写出结构即可

名称字段个数说明
起始字节1只能为0x7e
命令1
参数2高字节在低位,低字节在高位
长度2包括子命令和数据区长度,高字节在低位,低字节在高位
子命令1
数据区N可变长度,最大长度为20
校验字1从命令开始累计到数据区
结束符1只能为0x7e

数据帧封装


int msg_send(u8 cmd, u16 param, u8 subcmd, u8 *data, u8 len)
{
	int pos = 0;
	u8 crc = 0;
	int i,j;
	int status = 0;
	int ret = 0;
	u8 rawdata[MAX_FRM_LEN] = {0};

	//填充7e
	pos = 0;
	rawdata[pos] = 0x7e;
	pos+=1;
	rawdata[pos] = cmd;
	pos+=1;
	setdata16(&rawdata[pos],cmd);
	pos+=2;
	setdata16(&rawdata[pos],len+1);
	pos+=2;
	rawdata[pos] = subcmd;	
	pos+=1;

	for(i=0;i<len;i++)
	{
		rawdata[pos +i] = data[i];
	}
	pos+=len;

	//crc sum first
	for(i=1;i<pos;i++)
	{
		crc += rawdata[i];
	}


	rawdata[pos] = crc;
	pos+=1;
	rawdata[pos] = 0x7e;
	pos+=1;

	print_array("[frm]",rawdata,pos);

	return pos;
}

int main()
{
	int len;
	int ret;
	u16 param = 0x9527;
	u8 data[FRM_DATA_MAX_LEN]={0x1,0x2,0x3,0x4};
	len = msg_send(MSG_TYPE_QUERY, param, 1, data, 4);		
	return 1;
}

数据帧解析

用上一个程序执行结果,作为解析函数的测试数据

	u8 buf[]={0x7E,0x01 ,0x00 ,0x01 ,0x00 ,0x05 ,0x01 ,0x01 ,0x02 ,0x03 ,0x04 ,0x12 ,0x7E };
int my_check_crc(char *data)
{
	int i;
	u8 crc=0;
	u16 datalen=0;

	getdata16(&datalen,&data[4]);
	for(i=1;i<1+6+datalen-1;i++)
	{
		crc+=data[i];
	}
	printf("crc=%x  %x\n",crc&0xff,data[1+6+datalen-1]);//datalen包括子命令1个字节
	return (crc&0xff)==(data[1+6+datalen-1]&0xff);
}

int frm_parse(PENG_FRM_MSG_S *pmsg,u8 data[],int len)
{
	int ret = -1;
	int pos = 0;
	
	if(len<9)
	{
		printf("invalid frm len %d\n",len);
		return -1;
	}
	//check crc
	ret = my_check_crc(data);
	if(ret != 1)
	{
		printf("crc check error\n");
		return -1;
	}
	pos = 0;
	pmsg->startCode = data[pos];
	pos++;
	pmsg->cmd = data[pos];
	pos++;
	getdata16(&pmsg->param,&data[pos]);
	pos += 2;
	getdata16(&pmsg->len,&data[pos]);
	pos += 2;
	pmsg->subcmd = data[pos];
	pos++;

	if(pmsg->len+8 != len)
	{
		printf("err invalid frm len=%d\n",pmsg->len);	
		return -1;
	}
	memcpy(pmsg->data,&data[pos],pmsg->len-1);
	return 1;
}

int main()
{
	int len;
	int ret;
	PENG_FRM_MSG_S msg; 
	PENG_FRM_MSG_S *pmsg = &msg; 
	u8 buf[]={0x7E,0x01 ,0x00 ,0x01 ,0x00 ,0x05 ,0x01 ,0x01 ,0x02 ,0x03 ,0x04 ,0x12 ,0x7E };

	ret = frm_parse(pmsg,buf,sizeof(buf));
	printf("cmd:%x param:%x,len:%x,subcmd:%x\n",
		pmsg->cmd,pmsg->param,pmsg->len,pmsg->subcmd);
	
	print_array("[data]<<<",pmsg->data,pmsg->len-1);
			
	return 1;
}

思考:如果起始字符和结束符之间的数据如果有0x7e/0x7d需要转义为0x7d5e/0x7d5d,应该如何处理?

参考下面一篇文章
7E头解析的那些事儿(帧格式分析实例)

3. 编写基于udp C/S架构的服务器客户端程序

  • udp_server.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define SERVER_PORT 8888 
#define MAX_MSG_SIZE 1024 

void udps_respon(int sockfd) 
{ 
	struct sockaddr_in addr; 
	int addrlen,n; 
	char msg[MAX_MSG_SIZE]; 

	while(1) 
	{	/* 从网络上读,并写到网络上 */ 
		bzero(msg,sizeof(msg)); // 初始化,清零
		addrlen = sizeof(struct sockaddr_in); 
		
		//addr存放客户端的port ip信息
		n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&addr,&addrlen); // 从客户端接收消息
		
		msg[n]=0; 
		/* 显示服务端已经收到了信息 */ 
		fprintf(stdout,"Server have received %s  %s\n",msg,inet_ntoa(addr.sin_addr)); // 显示消息
		
		
		sendto(sockfd,msg,strlen(msg),0,(struct sockaddr*)&addr,addrlen); 
	} 
} 

int main(void) 
{ 
	int sockfd; 
	struct sockaddr_in addr; 

	/* 服务器端开始建立socket描述符 */ 
	sockfd=socket(AF_INET,SOCK_DGRAM,0); 
	if(sockfd<0) 
	{ 
		fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	/* 服务器端填充 sockaddr结构 */ 
	bzero(&addr,sizeof(struct sockaddr_in)); 
	addr.sin_family=AF_INET; 
	addr.sin_addr.s_addr=htonl(INADDR_ANY); 
	addr.sin_port=htons(SERVER_PORT); 

	/* 捆绑sockfd描述符 */ 
	if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) 
	{ 
		fprintf(stderr,"Bind Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	udps_respon(sockfd); // 进行读写操作
	close(sockfd); 
} 
  • udp_client.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define SERVER_PORT 8888 
#define MAX_BUF_SIZE 1024 

void udpc_requ(int sockfd,const struct sockaddr_in *addr,int len) 
{ 
	char buffer[MAX_BUF_SIZE]; 
	int addrlen,n; 
	struct sockaddr_in addr_server; 
	
	while(1) 
	{ 	/* 从键盘读入,写到服务端 */ 
		printf("Please input char:\n");
		fgets(buffer,MAX_BUF_SIZE,stdin); 
		
		sendto(sockfd,buffer,strlen(buffer),0,addr,len); 
		bzero(buffer,MAX_BUF_SIZE); 
		
		addrlen = sizeof(struct sockaddr); 
		n=recvfrom(sockfd,buffer,MAX_BUF_SIZE,0,(struct sockaddr*)&addr_server,&addrlen);
		buffer[n]=0;
		printf("recv:%s\n",buffer);
	} 
} 

int main(int argc,char **argv) 
{ 
	int sockfd; 
	struct sockaddr_in addr; 

	if(argc!=2) 
	{ 
		fprintf(stderr,"Usage:%s server_ip\n",argv[0]); 
		exit(1); 
	}

	/* 建立 sockfd描述符 */ 
	sockfd=socket(AF_INET,SOCK_DGRAM,0); 
	if(sockfd<0) 
	{ 
		fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	/* 填充服务端的资料 */ 
	bzero(&addr,sizeof(struct sockaddr_in)); 
	addr.sin_family=AF_INET; 
	addr.sin_port=htons(SERVER_PORT);
	if(inet_aton(argv[1],&addr.sin_addr)<0)  /*inet_aton函数用于把字符串型的IP地址转化成网络2进制数字*/ 
	{ 
		fprintf(stderr,"Ip error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in)); // 进行读写操作
	close(sockfd); 
} 

编译:

gcc udp_server.c -o s
gcc udp_client.c -o c

先运行服务器

./s

再打开一个终端,运行客户端

./c

客户端输入任意字符串会直接返回给服务器。

4. 基于步骤2/3通过udp实现信令在client/server之间传输,只写出结构即可

clien.c

#define SERVER_PORT 8888 
#define MAX_BUF_SIZE 1024 
typedef struct sockaddr SA;

void sendfrm_thread(int sockfd,const struct sockaddr_in *addr,int len) 
{ 
	u8 data[FRM_DATA_MAX_LEN]={0x1,0x2,0x3,0x4};
	int addrlen,frmlen; 
	struct sockaddr_in addr_server; 
	u16 param = 0x9527;
	u8 rawdata[MAX_FRM_LEN] = {0};
	
	while(1) 
	{ 	 		
		frmlen = msg_send(rawdata,MSG_TYPE_QUERY, param, 1, data, 4);
		
		sendto(sockfd,rawdata,frmlen,0,(SA *)addr,len); 
		
		sleep(2);
	} 
} 
int main(int argc,char **argv) 
{ 
	int sockfd; 
	struct sockaddr_in addr; 

	if(argc!=2) 
	{ 
		fprintf(stderr,"Usage:%s server_ip\n",argv[0]); 
		exit(1); 
	}

	sockfd=socket(AF_INET,SOCK_DGRAM,0); 
	if(sockfd<0) 
	{ 
		fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	bzero(&addr,sizeof(struct sockaddr_in)); 
	addr.sin_family=AF_INET; 
	addr.sin_port=htons(SERVER_PORT);
	if(inet_aton(argv[1],&addr.sin_addr)<0)  
	{ 
		fprintf(stderr,"Ip error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	sendfrm_thread(sockfd,&addr,sizeof(struct sockaddr_in)); 
	close(sockfd); 
} 

server.c

void rcvfrm_thread(int sockfd) 
{ 
	int ret;
	struct sockaddr_in addr; 
	int addrlen,len; 
	PENG_FRM_MSG_S msg; 
	PENG_FRM_MSG_S *pmsg = &msg; 

	char buf[MAX_MSG_SIZE]; 

	addrlen = sizeof(struct sockaddr_in); 

	while(1) {
		bzero(buf,sizeof(buf)); 
		
		len=recvfrom(sockfd,buf,MAX_MSG_SIZE,0,(struct sockaddr*)&addr,&addrlen);

		buf[len]=0; 
		ret = frm_parse(pmsg,buf,len);
		
		printf("\n[msg]<<< cmd:%x param:%x,len:%x,subcmd:%x\n",
			pmsg->cmd,pmsg->param,pmsg->len,pmsg->subcmd);
		
		//print_array("[data]<<<",pmsg->data,pmsg->len-1);

		
	} 
} 

int main(void) 
{ 
	int sockfd; 
	struct sockaddr_in addr; 

	sockfd=socket(AF_INET,SOCK_DGRAM,0); 
	if(sockfd<0) 
	{ 
		fprintf(stderr,"Socket Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	bzero(&addr,sizeof(struct sockaddr_in)); 
	addr.sin_family=AF_INET; 
	addr.sin_addr.s_addr=htonl(INADDR_ANY); 
	addr.sin_port=htons(SERVER_PORT); 

	if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) 
	{ 
		fprintf(stderr,"Bind Error:%s\n",strerror(errno)); 
		exit(1); 
	} 

	rcvfrm_thread(sockfd);
	close(sockfd); 
} 

5. 使用数据库sqlite,将步骤4的信令insert到数据库中,

qslite操作可以参考下面文章:

嵌入式数据库sqlite3【基础篇】-基本命令操作,小白一看就懂

6. 用c语言执行步骤5的数据库操作指令,并将代码合并到步骤4的架构中

C语言操作数据库,可以参考:
如何用C语言操作sqlite3,一文搞懂

7. 用任意一款抓包工具抓取一个ip数据包,保存到数组frm[]

网络/命令行抓包工具tcpdump详解

一文包你学会网络数据抓包


拷贝出数据:

80 8F 1D C7 A6 07 D8 BB C1 C8 51 C3 08 00 45 00 00 34 FC 6E 40 00 80 06 00 00 C0 A8 00 6B 8E FA C4 CA 21 AD 01 BB 25 C1 79 D5 00 00 00 00 80 02 FA F0 14 FF 00 00 02 04 05 B4 01 03 03 08 01 01 04 02

转换成数组:

unsigned char frm[]={
0x80,0x8F,0x1D,0xC7,0xA6,0x07,0xD8,0xBB,0xC1,0xC8,0x51,0xC3,0x08,0x00,0x45,0x00,0x00,0x34,0xFC,0x6E,0x40,
0x00,0x80,0x06,0x00,0x00,0xC0,0xA8,0x00,0x6B,0x8E,0xFA,0xC4,0xCA,0x21,0xAD,0x01,0xBB,0x25,0xC1,0x79,0xD5,
0x00,0x00,0x00,0x00,0x80,0x02,0xFA,0xF0,0x14,0xFF,0x00,0x00,0x02,0x04,0x05,0xB4,0x01,0x03,0x03,0x08,0x01,
0x01,0x04,0x02
};

8. 提取出ip数据包的mac头,ip数据包的ip头

注意:字节序问题

mact头提取



#define MAC_FMG "%02x:%02x:%02x:%02x:%02x:%02x\n"
#define printfmac(x) printf(MAC_FMG,x[0],x[1],x[2],x[3],x[4],x[5])

struct mac_h{
	u8 dst[6];
	u8 src[6];
	u16 pro;
};
int parse_mac_h(u8 *data,struct mac_h *pmac_h,int len)
{
	int pos = 0;
	
	if(len<6+6+2)
	{
		return -1;
	}
	pos = 0;
	memcpy(pmac_h->dst,&data[pos],6);
	pos+=6;
	memcpy(pmac_h->src,&data[pos],6);
	pos+=6;
	pmac_h->pro = data[pos]<<8 | data[pos+1];
	pos+=2;
}


int main()
{
	struct mac_h mac_header;
	......
	parse_mac_h(frm,&mac_header,sizeof(frm));
	......
	return 1;
}

执行结果如下:

ip头

#define IPADDR_FMG "%d.%d.%d.%d\n"
#define printipaddr(x) printf(IPADDR_FMG,((u8*)&(x))[0],((u8*)&(x))[1],((u8*)&(x))[2],((u8*)&(x))[3])

void dump_iphdr(struct iphdr *iph)
{
	printf("ihl:%d\nversion:%d\ntos:%d\ntot_len:%x\nid:%x\nfrag_off:%x\nttl:%d\nprotocol:%d\ncheck:%x\n",
		iph->ihl,
		iph->version,
		iph->tos,
		iph->tot_len,
		iph->id,
		iph->frag_off,
		iph->ttl,
		iph->protocol,
		iph->check		
	);
	printipaddr(iph->saddr);		
	printipaddr(iph->daddr);	
}


int parse_iphdr(u8 *data,struct iphdr *iph,int len)
{
	int pos = 0;
	
	iph->ihl = data[pos]&0xf;
	iph->version = data[pos]>>4;
	pos+=1;
	iph->tos = data[pos];
	pos+=1;
	getdata16(&iph->tot_len,&data[pos]);
	pos+=2;
	getdata16(&iph->id,&data[pos]);
	pos+=2;
	getdata16(&iph->frag_off,&data[pos]);
	pos+=2;

	iph->ttl = data[pos];
	pos+=1;
	iph->protocol = data[pos];
	pos+=1;
	getdata16(&iph->check,&data[pos]);
	pos+=2;

	getdata32(&iph->saddr,&data[pos]);
	pos+=4;
	getdata32(&iph->daddr,&data[pos]);
	pos+=4;	
}

在这里插入图片描述

思考:
为什么不可以直接用下面代码提取数据信息:

	memcpy(&mac_header,&frm[0],sizeof(struct mac_h));
	memcpy(&ip_header,&frm[14],sizeof(struct iphdr));

9. 将步骤8的结构体变量赋一些初始值,并按照mac头IP头格式将结构体内容填充到一段buf中

void send_pkt(struct mac_h *mach,struct iphdr *iph,char *pdata,int datalen)
{
	int pos = 0;
	char buf[14+20+1500]={0};

	/*add mac头*/
	pos =0 ;
	memcpy(&buf[pos],mach->dst,6);
	pos+=6;
	memcpy(&buf[pos],mach->src,6);
	pos+=6;
	setdata16(&buf[pos],mach->pro);
	pos+=2;
	
	/*add ip头*/
	buf[pos] = (char)(iph->ihl | iph->version<<4);
	pos+=1;
	buf[pos] = iph->tos;
	pos+=1;
	setdata16(&buf[pos],iph->tot_len);
	pos+=2;
	setdata16(&buf[pos],iph->id);
	pos+=2;
	setdata16(&buf[pos],iph->frag_off);
	pos+=2;

	buf[pos] = iph->ttl;
	pos+=1;
	buf[pos] = iph->protocol;
	pos+=1;
	setdata16(&buf[pos],iph->check);
	pos+=2;

	setdata32(&buf[pos],iph->saddr);
	pos+=4;
	setdata32(&buf[pos],iph->daddr);
	pos+=4;	

	memcpy(&buf[pos],pdata,datalen);
	pos+=datalen;	
	print_array("\nfrm",buf,pos);
}


main()
{
...
	datalen = ip_header.tot_len;
	memcpy(data,frm + sizeof(struct mac_h)+sizeof(struct iphdr),datalen-20);
	send_pkt(&mac_header,&ip_header,data,datalen-20);
...
}

10. 将3步骤的结构体变量保存到文件,并读取出来,

 void save_head(struct mac_h *mach,struct iphdr *iph,char *pdata,int datalen)
{
	int fd  =-1;
	int len = 0;
	struct mac_h mac_head_test;
	struct iphdr ip_head_test;
	char buf[1500];



	fd  =open(FAILE_NAME,O_RDWR|O_CREAT);
	if(fd < 0)
	{
		perror("open fail\n");
		return;
	}
	write(fd,mach,sizeof(struct mac_h));
	write(fd,iph,sizeof(struct iphdr));
	write(fd,pdata,datalen);

	lseek(fd,0,SEEK_SET);

	len = read(fd,&mac_head_test,sizeof(struct mac_h));
	len = read(fd,&ip_head_test,sizeof(struct iphdr));
	len = read(fd,buf,1500);

	buf[len] = '\0';

	printf("------recover from file--------------\n");
	dump_machdr(&mac_head_test);
	dump_iphdr(&ip_head_test);	

	print_array("\ndata",buf,len);
}



save_head(&mac_header,&ip_header,data,datalen-20);

完整代码获取,

后台回复: frm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一口Linux

众筹植发

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值