基于树莓派的温度采样项目总结

命令行提取参数

getopt_long函数,能够在Linux命令行提取短选项(-开头,与选参数之间隔空)与长选项(–开头连接参数)。它的原型是这样的。

#include <unistd.h>
#include <getopt.h>
int getopt_long(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex);

如果只想要提取长选项,那么就需要把optstring指针设为NULL。如果也需要用到短选项,那么这里设置为标识的字符。
option结构体的原型为

struct option {
   const char *name;//长选项的选项名。
   int         has_arg;//0:不需要参数,1:需要参数,2:参数是可选的
   int        *flag;//指定如何为长选项返回结果。如果flag是null,那么getopt_long返回val(可以将val设置为等效的短选项字符),否则getopt_long返回0.
   int         val;//返回值,参考flag
};

那么使用的时候,我们势必要判断命令行传入各个参数是否符合我们的要求,再进行筛选提取。

while( -1 != (optvar=getopt_long(argc,argv,"i:c:bHaho",opts,NULL)) )//返回值为-1说明已经解析完所有选项

optvar所声明的变量类型是至关重要的,使用int而不要用错用成char。而且,结构体的最后一行必须为全0。

优美使用宏定义

为什么要用上宏定义?
通常在函数返回的时候,我们使用的是return 0、return 1等反馈执行结果给调用者,而返回值等效的结果视不同人而定。stdlib库则内部宏定义了一对EXIT_SUCCESS(0)、EXIT_FAILURE(1)。让编写者与合作者的代码更容易理解。

在进行多种条件判断时,也可以进行宏定义替换掉令人费解的多种条件,例如:返回-1为无效值、0为超时、1为取值完成、2为仍然存在,条件较多,就可以采用宏定义进行替换。

文件IO中不断增长的文件描述符fd

文件描述符是什么呢?
一个进程,利用open、opendir发起系统调用,操作系统就会将打开的文件加载内存中进行管理。每个进程的PCB中都会有一个文件描述符表(struct file_struct),而打开的文件的属性(struct file)就像数组一样存放在文件描述符表中。文件描述符fd就类似于文件的属性在文件描述符表中的下标。

为什么我的文件描述符会不断增长?
操作系统会在系统调用后给我们依照按小分配原则给我们返回当前可用的最小的fd。如果发现文件描述符fd在程序运行后一直在增长,直到达到系统规定的数量(用户级为1024)抛错。这是因为我们的程序可能由于我们的疏忽,在重新打开一个文件或者文件夹后,忘记关闭该文件描述符,操作系统则不会帮我们断开我们已打开的文件描述符与文件的联系。fd则会一直增长。

段错误

什么是段错误?
段错误是我们的程序发生了越界访问,访问了不该访问的内存,这段内存可能不存在,也有可能是受系统保护、或者缺少文件、文件损坏,cpu就会产生相应的保护。linux内核会给产生段错误的程序发送SIGSEGV信号。

sqlite3数据库问题

为什么在使用sqlite3的时候出现段错误现象?
有时候我们去调用打开数据库,想从其中提取数据,但是如果该数据库的数据表是一个空的数据表,我们是会拿到空的值。拿到这本身是没有错的,但是我们再去操作这个空的值(比如将它strcpy到一个数组),就会发生段错误。所以我们要留意数据库中的数据是否正常。
为什么同样的语句,在命令行执行可以,放进代码sqlite3_exec()里执行不行呢?
检查语句后面是否漏了分号。
关于delete用不了limit的原因
最新版本的sqlite的delete语句是不支持limit的。

为什么打开文件都要放在daemon函数前面

daemon内部做了什么工作?
daemon函数实际上是程序运行后开了一个子进程继承父进程的资源,父进程退出;然后再执行一个setsid函数,创建会话,开启新的进程组、会话期,让它脱离原先的进程、会话期;再开一个子进程,用以重新申请打开一个控制终端,父进程退出…
daemon进程的老爹进程在daemon进程还活着的时候就死掉了,所以它就作为孤儿进程被init进程托管了。

为什么要放在daemon前面呢?
这是使用daemon(0,?)才会出现的问题。
而我们一般写程序,可能会使用到相对路径,如果将一个文件在daemon函数前open,那么就是原进程人家打开的文件,这个相对路径是相对原进程的也就是,而daemon又可以去继承。

而如果放在daemon(0,?)后面,它已经挂在根目录下面了,这时候的相对路径是根目录,但是,它可能并没有打开根目录文件的权限,所以会打开失败,再去操作可能会发生段错误。
能够在daemon前面启用线程吗?
不能,因为子进程不能够继承父进程的线程,子进程按照规定,只能有一个线程

getsockopt与setsockopt的区别

#include <sys/socket.h>
int setsockopt( int socket, int level, int option_name,const void *option_value, size_t ,ption_len);
int getsockopt(int socket, int level, int option_name,void *restrict option_value, socklen_t *restrict option_len);

setsockopt,用于对socket属性进行设置,比如设置超时时间,设置keepalive保活机制等;getsockopt,用于获取socket属性。

在socket客户端与服务端通信中,我们如何判断两端的连接是否断开呢?

为什么会产生管道信号、如何利用管道信号?
1.首先我们要了解,read、write的原理。(以下默认客户端发送服务端接收)

客户端将write的数据放入TX缓冲区,再通过TCP协议,发送至RX缓冲区,服务端从RX缓冲区中read数据。过程中,write与read都是阻塞的,如果向接收端发送一个空的数据,它的read会一直阻塞到我们write非空数据给它。

如果我们将服务端关闭,则write的数据放入TX缓冲区这个过程是成功的,返回成功值,但是由于我们异常强制关闭了连接,此时TCP会发出RST报文。这个报文,两种情况可以触发其发出:一种是向不存在的端口或者关闭监听的端口发送数据;一种是异常关闭了连接。所以,从TX缓冲区向RX缓冲区发送数据这一过程是失败的,本质上是一方发送SYN数据包,一方发送ACK应答。客户端socket收到RST后,再向TX缓冲区放数据,Linux内核就会发送一个SIGPIPE信号(意思是发送通道坏了)给客户端进程,缺省动作为终止程序运行。

如何使用getsockopt判断连接状态
我使用的是

getsockopt(sockfd,IPPROTO_TCP,TCP_INFO,&info,(socklen_t *)&len);

等级为IPPROTO_TCP,获取TCP_INFO这个tcp_info类型的结构体里面的状态变量值。tcp_info结构体的声明如下:

struct tcp_info {
	__u8	tcpi_state;		   //tcp state: TCP_SYN_SENT,TCP_SYN_RECV,TCP_FIN_WAIT1,TCP_CLOSE etc
	__u8	tcpi_ca_state;     //congestion state:
	__u8	tcpi_retransmits;  //重传数,表示当前待重传的包数,这个值在重传完毕后清零
	__u8	tcpi_probes;		///* 持续定时器或保活定时器发送且未确认的段数*/
	__u8	tcpi_backoff;		//用来计算持续定时器的下一个设计值的指数退避算法指数,在传送超时是会递增。
	__u8	tcpi_options;		//tcp头部选项是否包含:扩展因子、时间戳、MSS等内容
	__u8	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4; //扩展因子数值
	__u8	tcpi_delivery_rate_app_limited:1;  //限速标志

	__u32	tcpi_rto;		//重传超时时间,这个和RTT有关系,RTT越大,rto越大
	__u32	tcpi_ato;		//用来延时确认的估值,单位为微秒. 
							//在收到TCP报文时,会根据本次与上次接收的时间间隔来调整改制,在设置延迟确认定时器也会根据
							//条件修改该值
	__u32	tcpi_snd_mss;	// 本端的MSS
	__u32	tcpi_rcv_mss;	// 对端的MSS

	__u32	tcpi_unacked;	//未确认的数据段数
	__u32	tcpi_sacked;    //2个含义:server端在listen阶段,可以接收连接的数量;收到的SACK报文数量
	__u32	tcpi_lost;		//本端在发送出去被丢失的报文数。重传完成后清零
	__u32	tcpi_retrans;   /* 重传且未确认的数据段数 */
	__u32	tcpi_fackets;

	/* Times. */
	__u32	tcpi_last_data_sent;	//当前时间-最近一个包的发送时间,单位是毫秒
	__u32	tcpi_last_ack_sent;     /* 未使用*/
	__u32	tcpi_last_data_recv;	//当前时间-最近接收数据包的时间,单位是毫秒
	__u32	tcpi_last_ack_recv;     //当前时间-最近接收ack的时间,单位是毫秒

	/* Metrics. */
	__u32	tcpi_pmtu;			/* 最后一次更新的路径MTU */
	__u32	tcpi_rcv_ssthresh;   //当前接收窗口的大小
	__u32	tcpi_rtt;			//smoothed round trip time,微妙	
	__u32	tcpi_rttvar;		//描述RTT的平均偏差,该值越大,说明RTT抖动越大
	__u32	tcpi_snd_ssthresh;  //拥塞控制慢开始阈值
	__u32	tcpi_snd_cwnd;		//拥塞控制窗口大小
	__u32	tcpi_advmss;		//本端的MSS上限
	__u32	tcpi_reordering;	/* 没有丢包时,可以重新排序的数据段数 */

	__u32	tcpi_rcv_rtt;		// 作为接收端,测出的RTT值,单位为微秒. 这个值不是对方计算并传送过来的rtt,而是作为接收端,在没发送数据的情况下
								// 通过接收发送端发送的数据的情况计算得到的rtt值。在数据发送方,如果不接受数据,这个值一般情况下为0。
	__u32	tcpi_rcv_space;		/* 当前接收缓存的大小 */

	__u32	tcpi_total_retrans;  //统计总重传的包数,持续增长。

	__u64	tcpi_pacing_rate;		//发送速率
	__u64	tcpi_max_pacing_rate;	//最大发送速率,默认是unlimited,可以通过SO_MAX_PACING_RATE来设置
	__u64	tcpi_bytes_acked;    /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
	__u64	tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
	__u32	tcpi_segs_out;	     /* RFC4898 tcpEStatsPerfSegsOut */
	__u32	tcpi_segs_in;	     /* RFC4898 tcpEStatsPerfSegsIn */

	__u32	tcpi_notsent_bytes;
	__u32	tcpi_min_rtt;
	__u32	tcpi_data_segs_in;	/* RFC4898 tcpEStatsDataSegsIn */
	__u32	tcpi_data_segs_out;	/* RFC4898 tcpEStatsDataSegsOut */

	__u64   tcpi_delivery_rate;

	__u64	tcpi_busy_time;      /* Time (usec) busy sending data */
	__u64	tcpi_rwnd_limited;   /* Time (usec) limited by receive window */
	__u64	tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */
};

由于我们异常终止了连接,客户端收到RST包后也知道程序异常终止了,所以tcpi_state状态就改变了,我们只需要判断tcpi_state的值就行了。

recv判断法
当recv()返回值小于等于0时,socket连接断开。但是还需要判断 errno是否等于 EINTR,如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。

Keepalive判断法
利用setsockopt配置socket的keepalive,在长连接中,如果我们在keepalive时间内两端没有发数据,它就会帮我们发一个ping报文进行保活。而如果不设置,系统则会默认帮我们关掉连接。

为什么gcc说链接错误?为什么执行可执行文件说打不开动态文件?

1.没有指定动态库所在的路径
2.使用export临时配置的系统环境变量,设置了路径,但是下次登录后这个变量又失效掉了
3.如果只需要修改用户级别的,则找到主目录下的.~bachrc文件,在最后一行加上

待补充
------
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值