IAR sprintf() 输出不正常的问题

前几天调试了一程序,用到sprintf函数,输出结果一直不正常,同样的输出格式用printf打印则没有问题。然后同样的程序在gcc下sprintf也是正常的。怀疑IAR的sprintf函数有问题。后来发现原因是IAR把sprintf函数简化了,在project-options-general options 里面的Library options 的Tabs里有个printf formatter,默认是选成tiny的选项,结果就不支持一些复杂的输出格式。把它设置成auto,输出结果就正常了,o yeah!

methods of handling multiple connections as lwip

这里分享一些一个服务handle多client连接的经验。 (1) 多线程/进程方法 每accept并创建一个新连接之后,就create一个task来处理这个连接的事务。linux使用fork()来创建分支线程。freertos 不支持fork方法,只能创建新的线程来管理连接。此方法内存开销较大。 (2) non-blocking socket 和 select 此方法由单线程处理并发事务,即最原始的轮询方式。在系统不支持fork()的情况下经常用到。首先,accpet/recv函数都必须的non-blocking的,需立即返回,否则其它任务就得不到轮询。这种模式在跑单片机裸机程序时经常是这样的,另外在labview里面也经常是这样的轮询的方式。这个例程可以在lwip 1.4.1 contrib下app demo:chargen 里可以学习到。这种方式会占用很多cpu资源,这样的任务优先级要放低,并设置一定的轮询间隔(类比在labview里,while循环通常会插入一个等待时间)。否则 freertos 中低优先级任务就得不到运行,同优先级的任务也只能分时间片轮流运行。 这里简单总结下 select方法。 协议栈内核在每次有accept 或 recv事件到达后都会调用 event_callback(); 增加一个事件记录。在上层接收后都会减掉一个记录。select 函数通过查询socket的对应event来置位fd_set对应的比特位。本质就类似查询一个计数信号量。 (3) 使用 raw api, 基于事件触发(基于回调)方式。 此方式适合编写只进行简单处理的应用,每次接收到包就会调用相应的回调函数。多连接是由协议栈的active_pcb_lists直接管理,每个连接都创建属于自己的state argument。如果需要运算量比较大占用时间较长的服务则不适合此方式,会影响的协议栈对其它服务的响应速度。 后记,从事务的角度来看,一个tcp连接代表一个session,一句udp 请求也代表一个session。一个session从接收到返回就是一条事务线。若不能一气呵成,则就必须用一条线程去管理它,或者说维护一个工作现场,等待它的后台任务完成再回来应答这个事务。一条线程就对应一个事务现场,多任务操作系统就是在不同的工作现场的中交替工作,模拟多个人工作的情况,每一个工作现场都保存的对应工作事务的进展情况和所有需要用到的工具。 或者还有一种做法,就是一件事做完一部分后丢给后台,让它处理完之后调用我给它的办法帮我把剩下的事处理完,节省了任务之间信息传递和任务切换带来的开销。 其实一个session对应一个任务确实有点浪费,我想一般handle几百上千万个连接的服务应该不会这样做的,每个连接只需将它当前的状态变量连同数据包传给每个部门来做就可以了,毕竟每个连接的状态的数据量不是很多,没必要开辟一个专门的任务去Handle.

Lwip Netconn

从Lwip Netconn接口的代码中,我们可以学到与Lwip 协议栈规范的交互方式和这种接口的封装方法。 这里总结出一个看代码的技巧,第一步先是只看正常处理的流程,忽略异常处理的部分,这样代码就可以非常简洁,可以很快把代码流程搞清楚,第二步再看重要的异常情况的处理过程。 连接的建立过程前面已经简单总结过,这里就只说我们比较关心的recv_xxx()过程。 recv_xxx() 函数是在accept_func里注册的,并且pcb->callback_arg = new_netconn。 其回调原型: 函数原型: recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { struct netconn *conn; u16_t len; conn = (struct netconn *)arg; if (sys_mbox_trypost(&conn->recvmbox, p) != ERR_OK) { … } 这里从tcpip_thread抛出的是pbuf。 netconn 接收部分如下: 这里buf 是void 类型的指针,因为上头可能会用pbuf类型或者netbuf类型来接收邮箱里的pbuf。 netbuf的封装方式:

Lwip 里的信号量、邮箱、线程

这里简单分析总结一下Lwip sys_arch.c 中的信号量、邮箱、进程相关的内容。系统用的是FreeRTOS。 这里创建了两个全局结构体数组来管理semaphores and mailboxes. 有个不明白的地方是,结构体里的buffer是用来作什么的? 这里的semaphore其实用的是长度为1的queue来实现。mbox是长度为size的queue,其中消息对象类型都是void *,由于freertos在把消息送入队列和提取出队列都是复制的方式(应该是类似ringbuf的操作,没深入研究),所以这里为了提高效率,把对象封装一个消息结构体,只传递指针。在作消息传递时要特别注意内存的分配和释放,以免造成内存泄漏。 这里还要总结的一点是,类似于这种xxx_new, xxx_alloc的函数,用来分配对向内存的函数,传递进去的是指针的地址,因为要将指针指向新分配的内存,所以传进去的为指针的地址。 如这样调用: Lwip 的Thread是用FreeRTOS的xTaskCreate()来实现的。这里有个问题, TI的sys_arch.c里的sys_thread_new的实现,又用mem_malloc()分配了栈内存,是不是多此一举了,因为xTaskCreate里会为这个任务分配栈内存。这里似乎没有任何用处。

LwIP TCP Layer Simple analysis

LwIP Version : 1.4.1; 这里分享一些阅读LwIP协议栈的心得。 有个经验: 源代码才是最权威的资料。 若想用raw api编程,可以参考httpd_raw的代码。若涉及进程间通讯,请参考netconn接口,避免在其它进程内调用非Thead-Safe的函数。 下面说说对一些函数及过程的认识。 (1) xxx_accept(void *arg, struct tcp_pcb *pcb, err_t err); 这个函数的参数注意点: 回调原型:pcb->accept(pcb->callback_arg, pcb, err); @*arg: 这里协议栈传递过来的是lpcb,即监听pcb,不要认为和第二个参数相同。因为在tcp_listen_input()里,当有新连接请求到达时会新建一个npcb并将其加入到tcp_active_list里,在初始化npcb时,继承的是lpcb的callback_arg, 而这个callback_arg在新建监听链接时将其赋为lpcb。 @*pcb: 这里是传递进新创建的pcb。在创建APP过程中,需给其分配新的argument, 指定xxx_recv(),xxx_err(), xxx_poll(), xxx_sent()等函数。 (2) struct tcp_pcb *tcp_listen( struct tcp_pcb *pcb); 这里传入了一个original pcb,返回一个lpcb,因为listening状态的pcb 只需包含更少的信息。函数内新分配了一个 lpcb, 拷贝必要信息后,将original pcb释放。 对于SO_REUSE选项,在此函数内对比已经在监听List 中的pcb, 允许本地端口号相同,但本地IP号不同的连接可再次监听。 (3) 服务端口监听过程分析 TCP_LISTEN_BACKLOG 选项 此选项使能 TCP 对监听列表允许监听的数量控制。在tcp_listen_input()函数内, 当收到SYN帧时,此处对比并限制已经pending 状态的数量,即未被上层accept的连接数量。接下来新建立一个npcb, 并加入active_pcbs […]

用ECS在云端搭建SVN服务

应用环境: ECS 云端系统:Ubuntu 14.04-x64 本地系统:window 使用工具:cygwin + ssh 原版文档: http://www.shayanderson.com/linux/install-and-setup-subversion-server-on-ubuntu-1210-server-with-multiple-repositories.htm 一、安装及目录配置 1. 登录云端服务器 ssh xxx@[IP] 2. 安装subversion apt-get install subversion 检查是否安装完成 svn –version 3. 先建立一个Repositories 目录放置工程 mkdir /home/repos 在目录底下再建立工程 svnadmin create /home/repos/project1 svnadmin create /home/repos/project2 注:删除则用 svnadmin deltify 4. 修改目录组权限让一用户组都可管理此目录 chmod -R g+rws /home/repos //设置组权限 sudo groupadd svn //添加组 chgrp -R svn /home/repos //修改group ownership […]

HTTP协议与HTTP表单传输格式

HTTP请求 从使用者的角度看,一个HTTP请求起始于 用户端浏览器上输入的一个URL地址; 网页中的一个超链接; 提交一个HTML表单。 但本质上说,一个HTTP请求起始于用户端向HTTP服务器发送的一个URL请求。 一个标准的HTTP请求由以下几个部分组成 在HTTP请求中,第一行是请求行(request-line),用来说明请求类型、要访问的资源(URL)以及使用的HTTP版本; 紧接着是多行头部(headers)信息,用来说明服务器要使用的附加信息; 头部信息之后是一个回车换行符(/r/n),用于标明头部信息的结束。 以上是必须内容,根据需要可在头部信息结束之后增加主体数据(request-body); 主体数据之后是一个回车换行符(/r/n),用于标明主体数据的结束。 需要注意的是 请求行(request-line)中的URL部分必须以application/x-www-form-urlencoded方式编码。 主体数据(request-body)的编码方式由头部(headers)信息中的Content-Type指定。 主体数据(request-body)的长度由头部(headers)信息中的Content-Length指定。 例如,我们可以在IE浏览器上输入下面的网址: http://localhost:8000/hello/index.html HTTP请求的头部信息如下: 上述信息没有request-body部分,这是以GET方式发送的HTTP请求。如果请求中需要附加主体数据,即增加request-body部分,则必须使用POST方式发送HTTP请求。HTML超链接()只能用GET方式提交HTTP请求,HTML表单( )则可以使用两种方式提交HTTP请求。 HTML表单 HTML表单的使用方法如下: 表单中存在各种类型的表单域标签,如 每一种表单域标签均有NAME与VALUE两种标签属性。这两个标签属性决定了表单提交时传送的属性名及相应的值。 目标地址(URL) action标签属性指定了表单提交的目标地址,其值可以是完整的URL。如: 这样的表单是不符合要求的。如果其URL值存在非法字符(如中文字符),应将其进行URL Encoding处理。URL Encoding的处理方法如下: 字母数字字符 “a” 到 “z”、”A” 到 “Z” 和 “0” 到 “9” 保持不变。 特殊字符 “.”、”-“、”*” 和 “_” 保持不变。 空格字符 ” ” 转换为一个加号 “+”。 所有其他字符都是不安全的,因此首先使用一种编码机制将它们转换为一个或多个字节。然后对每个字节用一个包含 3 个字符的字符串 “%xy” […]

SSI Tag的使用

这里先说最原始的使用SSI tag的方式,因为这样做在C开发中最简单。跟后来通用的web服务器的SSI有点不一样,因为后来为了使上层开发更方便,又进行了一些封装。 最原始的SSI是这样规定的,如在html中嵌入SSI tag。使用 ;,则当Web服务器生成网页时,会将当前的标签后插入所指定的内容。这就是SSI Tag的作用。 SSI可以说是最初动态生成网页的方法,不仅仅是用来插入一些变量也可以直接插入javascript、json等等你想要发送给客户端的内容!一般用SSI来插入静态变量比较方便,如果是插入input框的当前值的话,可以往SSI中插入一段javascript代码或者是json格式的数据,再用javascript把这些实时的值加载进input框。 其Tag 在Web 服务端代码中作如下定义,并定义其序号的宏。 然后在SSIHandler里如下解析,当每次serving a page with a “.ssi”, “.shtml” or “.shtm” file extension 的时候都会将SSI tag进行替换。 学习了SSI tag怎么用的,然后可以再深入研究下在文本中检索 SSI tag 的写法。这里用了个状态机,按顺序搜索ssi lead in 和lead out。 其实看懂了SSI实现的原理,就可以联想到简单的php引擎是如何处理php代码的。另外也可以看出ssi的处理效率是比较低的,因为需要检索整个文本的ssi tag。php也类似,所以后面会出现编译型的php引擎,预先把php代码和文本分开,这样执行效率就高很多。