打印
[软件资料]

C语言编程水平上不来?不妨试试这几个例子

[复制链接]
20|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
duo点|  楼主 | 2025-3-4 12:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

初学者通过下面几个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);

        // 每行打印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;
}
pos+=len;

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


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;
}
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);
}

使用特权

评论回复
沙发
duo点|  楼主 | 2025-3-4 12:27 | 只看该作者

编译:

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[]

拷贝出数据:

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;
}


使用特权

评论回复
板凳
duo点|  楼主 | 2025-3-4 12:28 | 只看该作者

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

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);

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

449

主题

1718

帖子

1

粉丝