




已阅读5页,还剩36页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
LWIP之SOCKET的实现 /214870/158413 Lwip协议栈的实现目的,无非是要上层用来实现app的socket编程。好,我们就从socket开始。为了兼容性,lwip的socket应该也是提供标准的socket接口函数,恩,没错,在srcincludelwipsocket.h文件中可以看到下面的宏定义:#if LWIP_COMPAT_SOCKETS#define accept(a,b,c) lwip_accept(a,b,c)#define bind(a,b,c) lwip_bind(a,b,c)#define shutdown(a,b) lwip_shutdown(a,b)#define closesocket(s) lwip_close(s)#define connect(a,b,c) lwip_connect(a,b,c)#define getsockname(a,b,c) lwip_getsockname(a,b,c)#define getpeername(a,b,c) lwip_getpeername(a,b,c)#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)#define listen(a,b) lwip_listen(a,b)#define recv(a,b,c,d) lwip_recv(a,b,c,d)#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)#define send(a,b,c,d) lwip_send(a,b,c,d)#define sendto(a,b,c,d,e,f) lwip_sendto(a,b,c,d,e,f)#define socket(a,b,c) lwip_socket(a,b,c)#define select(a,b,c,d,e) lwip_select(a,b,c,d,e)#define ioctlsocket(a,b,c) lwip_ioctl(a,b,c)#if LWIP_POSIX_SOCKETS_IO_NAMES#define read(a,b,c) lwip_read(a,b,c)#define write(a,b,c) lwip_write(a,b,c)#define close(s) lwip_close(s)先不说实际的实现函数,光看这些定义的宏,就是标准socket所必须有的接口。接着看这些实际的函数实现。这些函数实现在srcapisocket.c中。先看下接受连接的函数,这个是tcp的原型:int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen)可以看到这里的socket类型参数 s,实际上是个int型在这个函数中的第一个函数调用是sock = get_socket(s);这里的sock变量类型是lwip_socket,定义如下:/* Contains all internal pointers and states used for a socket */struct lwip_socket /* sockets currently are built on netconns, each socket has one netconn */ struct netconn *conn; /* data that was left from the previous read */ struct netbuf *lastdata; /* offset in the data that was left from the previous read */ u16_t lastoffset; /* number of times data was received, set by event_callback(), tested by the receive and select functions */ u16_t rcvevent; /* number of times data was received, set by event_callback(), tested by select */ u16_t sendevent; /* socket flags (currently, only used for O_NONBLOCK) */ u16_t flags; /* last error that occurred on this socket */ int err;好,这个结构先不管它,接着看下get_socket函数的实现【也是在srcapisocket.c文件中】,在这里我们看到这样一条语句sock = &socketss;很明显,返回值也是这个sock,它是根据传进来的序列号在sockets数组中找到对应的元素并返回该元素的地址。好了,那么这个sockets数组是在哪里被赋值了这些元素的呢?进行到这里似乎应该从标准的socket编程的开始,也就是socket函数讲起,那我们就顺便看一下。它对应的实际实现是下面这个函数Int lwip_socket(int domain, int type, int protocol)【srcapisocket.c】这个函数根据不同的协议类型,也就是函数中的type参数,创建了一个netconn结构体的指针,接着就是用这个指针作为参数调用了alloc_socket函数,下面具体看下这个函数的实现static int alloc_socket(struct netconn *newconn) int i; /* Protect socket array */ sys_sem_wait(socksem); /* allocate a new socket identifier */ for (i = 0; i function(&(apimsg-msg); UNLOCK_TCPIP_CORE(); return ERR_OK;* Call the lower part of a netconn_* function* This function is then running in the thread context* of tcpip_thread and has exclusive access to lwIP core code.err_t tcpip_apimsg(struct api_msg *apimsg)【此为非locking的】 struct tcpip_msg msg; if (mbox != SYS_MBOX_NULL) msg.type = TCPIP_MSG_API; msg.msg.apimsg = apimsg; sys_mbox_post(mbox, &msg); sys_arch_sem_wait(apimsg-msg.conn-op_completed, 0); return ERR_OK; return ERR_VAL;其实,功能都是一样的,都是要对apimsg-function函数的调用。只是途径不一样而已。看看它们的功能说明就知道了。这么来说apimsg-function的调用很重要了。从netconn_new_with_proto_and_callback函数的实现,可以知道这个function就是do_newconnVoid do_newconn(struct api_msg_msg *msg) if(msg-conn-pcb.tcp = NULL) pcb_new(msg); /* Else? This new connection already has a PCB allocated. */ /* Is this an error condition? Should it be deleted? */ /* We currently just are happy and return. */ TCPIP_APIMSG_ACK(msg);还是看TCP的,在pcb_new函数中有如下代码:case NETCONN_TCP: msg-conn-pcb.tcp = tcp_new(); if(msg-conn-pcb.tcp = NULL) msg-conn-err = ERR_MEM; break; setup_tcp(msg-conn); break;我们知道在这里建立了这个tcp的连接。至于这个超级牛的函数,以后再做介绍。嗯,还是回过头来接着看accept函数吧。Sock获得了,接着就是newconn = netconn_accept(sock-conn);通过mbox取得新的连接。粗略的估计了一下,这个新的连接应该和listen有关系。那就再次打断一下,看看那个listen操作。lwip_listen - netconn_listen_with_backlog- do_listen- tcp_arg(msg-conn-pcb.tcp, msg-conn);tcp_accept(msg-conn-pcb.tcp, accept_function);/注册了一个接受函数* Accept callback function for TCP netconns.* Allocates a new netconn and posts that to conn-acceptmbox.static err_t accept_function(void *arg, struct tcp_pcb *newpcb, err_t err) struct netconn *newconn; struct netconn *conn; conn = (struct netconn *)arg; /* We have to set the callback here even though * the new socket is unknown. conn-socket is marked as -1. */ newconn = netconn_alloc(conn-type, conn-callback); if (newconn = NULL) return ERR_MEM; newconn-pcb.tcp = newpcb; setup_tcp(newconn); newconn-err = err; /* Register event with callback */ API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0); if (sys_mbox_trypost(conn-acceptmbox, newconn) != ERR_OK) /* When returning != ERR_OK, the connection is aborted in tcp_process(), so do nothing here! */ newconn-pcb.tcp = NULL; netconn_free(newconn); return ERR_MEM; return ERR_OK;对了,accept函数中从mbox中获取的连接就是这里放进去的。再回到accept中来,取得了新的连接,接下来就是分配sock了,再然后,再然后?再然后就等用户来使用接收、发送数据了。到此整个APP层,也就是传输层以上对socket的封装讲完了。在最后再总结一些整个路径的调用情况吧LWIP之API_MSG结构及其实现 /214870/158414 从上面一篇的socket实现来看,如果要评起到最关键作用的一个结构体,那么struct api_msg当之无愧。先看下它的定义:/* This struct contains a function to execute in another thread context and a struct api_msg_msg that serves as an argument for this function. This is passed to tcpip_apimsg to execute functions in tcpip_thread context. */struct api_msg /* function to execute in tcpip_thread context */ void (* function)(struct api_msg_msg *msg); /* arguments for this function */ struct api_msg_msg msg;功能说的很清楚。但是具体怎么个操作法还是不知道,没关系,接着看它的调用。举一个例子,刚好是上一篇中调用,但是没有看具体实现的err_t netconn_getaddr(struct netconn *conn, struct ip_addr *addr, u16_t *port, u8_t local) struct api_msg msg; msg.function = do_getaddr; msg.msg.conn = conn; msg.msg.msg.ad.ipaddr = addr; msg.msg.msg.ad.port = port; msg.msg.msg.ad.local = local; TCPIP_APIMSG(&msg); return conn-err;说明一下,api_msg结构几乎都是在netconn_xxx函数中被调用,方式千篇一律,除了msg.funcion的赋值不一样外。上面的调用很简单,对该结构体变量赋值,接着就是调用TCPIP_APIMSG,这个函数上面讲过,可过去看下。既然如此,就不得不说mbox及其相关函数了。static sys_mbox_t mbox = SYS_MBOX_NULL;【tcp.c】再看sys_mbox_t的定义,在【srcincludelwipsys.h】中/* For a totally minimal and standalone system, we provide null definitions of the sys_ functions. */typedef u8_t sys_sem_t;typedef u8_t sys_mbox_t;typedef u8_t sys_prot_t;可以看到这里只是简单的定义成了u8类型,注意上面的红色字体的说明,很明显这个是可移植的一部分,需要根据不同的平台,不同的操作系统具体定义。可以借鉴焦海波大侠的关于ucos上对lwip的移植笔记来看。我们可以看到在api_msg结构的处理过程中,所有的信息都是包含在api_msg_msg结构体中的,api_msg只是将其和function简单的组合了。下面看下这个牛结构的定义:/* This struct includes everything that is necessary to execute a function for a netconn in another thread context (mainly used to process netconns in the tcpip_thread context to be thread safe). */struct api_msg_msg /* The netconn which to process - always needed: it includes the semaphore which is used to block the application thread until the function finished. */ struct netconn *conn; /* Depending on the executed function, one of these union members is used */ union /* used for do_send */ struct netbuf *b; /* used for do_newconn */ struct u8_t proto; n; /* used for do_bind and do_connect */ struct struct ip_addr *ipaddr; u16_t port; bc; /* used for do_getaddr */ struct struct ip_addr *ipaddr; u16_t *port; u8_t local; ad; /* used for do_write */ struct const void *dataptr; int len; u8_t apiflags; w; /* used ofr do_recv */ struct u16_t len; r;#if LWIP_IGMP /* used for do_join_leave_group */ struct struct ip_addr *multiaddr; struct ip_addr *interface; enum netconn_igmp join_or_leave; jl;#endif /* LWIP_IGMP */#if TCP_LISTEN_BACKLOG struct u8_t backlog; lb;#endif /* TCP_LISTEN_BACKLOG */ msg;一个很合理的设计,至少笔者是这么认为的。关键在于msg union的设计。LWIP之TCP层发送相关 /214870/158415 现在我们正式开始进入对TCP的研究,它属于传输层协议,它为应用程序提供了可靠的字节流服务。在LWIP中基本的TCP处理过程被分割为六个功能函数的实现:tcp_input(), tcp_process(), tcp_receive()【与TCP输入有关】, tcp_write(), tcp_enqueue(), tcp_output()【用于TCP输出】。这些是从大的方面来划分的。现在先从小部tcp.c文件来分析一下:我们知道这里的函数都是被socket那一层的最终调用的。为了利于分析,我选择lwip_send函数来分析,具体不多说,最终调用到了static err_t do_writemore(struct netconn *conn)这个函数,当然这期间也做了不少工作,最主要的就是把发送数据的指针放到了msg的指定变量中msg.msg.msg.w.dataptr = dataptr;/指针msg.msg.msg.w.len = size; /长度这些又经过转化放到了netconn的write_msg中最后就是对do_writemore的调用了,下面详细分析这个函数。这个函数的最直接调用有以下几个:available = tcp_sndbuf(conn-pcb.tcp);err = tcp_write(conn-pcb.tcp, dataptr, len, conn-write_msg-msg.w.apiflags);err = tcp_output_nagle(conn-pcb.tcp);err = tcp_output(conn-pcb.tcp);好,先看tcp_sndbuf这个。从其参数,我们应该想像pcb的重要性。#define tcp_sndbuf(pcb) (pcb)-snd_buf)/由下面分析得这里直接返回buf大小那就继续跟踪这个pcb好了。是的,这还得从lwip_socket开始,在创建连接netconn的时候调用了do_newconn,它接着调用了pcb_new,在这个函数中如果是tcp的话有以下代码msg-conn-pcb.tcp = tcp_new();哈哈,还记得吧,在前面我讨论到了这里,就没有再讨论了。嗯,现在开始吧。* Creates a new TCP protocol control block but doesnt place it on* any of the TCP PCB lists.* The pcb is not put on any list until binding using tcp_bind().struct tcp_pcb * tcp_new(void) return tcp_alloc(TCP_PRIO_NORMAL);也许注释的意义更大一些吧,哈哈,至此,我们从注释可以知道,tcp_bind的作用是把tcp的pcb放入list中,至少是某一个list,既然都提到了,似乎不说说有点过意不去啊。Lwid_bind函数最终会调用到tcp_bind【当然是tcp为例进行分析】。这个函数也比较有意思,在进入正题之前先来了下面这么个调用if (port = 0) port = tcp_new_port();意思很明显,就是分配个新的端口号,有意思的就是这个端口号的分配函数tcp_new_portstatic u16_ttcp_new_port(void) struct tcp_pcb *pcb;#ifndef TCP_LOCAL_PORT_RANGE_START#define TCP_LOCAL_PORT_RANGE_START 4096#define TCP_LOCAL_PORT_RANGE_END 0x7fff#endif static u16_t port = TCP_LOCAL_PORT_RANGE_START; again: if (+port TCP_LOCAL_PORT_RANGE_END) port = TCP_LOCAL_PORT_RANGE_START; for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb-next) if (pcb-local_port = port) goto again; for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb-next) if (pcb-local_port = port) goto again; for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb-next) if (pcb-local_port = port) goto again; return port;分别检查了3个pcb链表,本来我不想把这个函数的实现列在这里的,但是这里告诉了我们一些东西,至少我们知道有3个pcb的链表,分别是tcp_active_pcbs【处于接受发送数据状态的pcbs】、tcp_tw_pcbs【处于时间等待状态的pcbs】、tcp_listen_pcbs【处于监听状态的pcbs】。似乎tcp_bind函数更有意思,它分别检查了4个pcbs链表,除了上面的三个还有一个tcp_bound_pcbs【处于已经绑定但还没有连接或者监听pcbs】。作用是是否有与目前pcb的ipaddr相同的pcb存在。不过这些都不是最重要的,TCP_REG(&tcp_bound_pcbs, pcb);才是我们的目的,果然如此,直接加入到了tcp_bound_pcbs链表中了。struct tcp_pcb * tcp_alloc(u8_t prio)/是的,你没看错,这个参数是学名是叫优先级pcb = memp_malloc(MEMP_TCP_PCB);if (pcb != NULL) memset(pcb, 0, sizeof(struct tcp_pcb); pcb-prio = TCP_PRIO_NORMAL; pcb-snd_buf = TCP_SND_BUF;/对的,别的可以先不管,这就是我们要找的东西 pcb-snd_queuelen = 0; pcb-rcv_wnd = TCP_WND; pcb-rcv_ann_wnd = TCP_WND; pcb-tos = 0; pcb-ttl = TCP_TTL; /* The send MSS is updated when an MSS option is received. */ pcb-mss = (TCP_MSS 536) ? 536 : TCP_MSS; pcb-rto = 3000 / TCP_SLOW_INTERVAL; pcb-sa = 0; pcb-sv = 3000 / TCP_SLOW_INTERVAL; pcb-rtime = -1; pcb-cwnd = 1; iss = tcp_next_iss(); pcb-snd_wl2 = iss; pcb-snd_nxt = iss; pcb-snd_max = iss; pcb-lastack = iss; pcb-snd_lbb = iss; pcb-tmr = tcp_ticks; pcb-polltmr = 0;#if LWIP_CALLBACK_API pcb-recv = tcp_recv_null;#endif /* LWIP_CALLBACK_API */ /* Init KEEPALIVE timer */ pcb-keep_idle = TCP_KEEPIDLE_DEFAULT; #if LWIP_TCP_KEEPALIVE pcb-keep_intvl = TCP_KEEPINTVL_DEFAULT; pcb-keep_cnt = TCP_KEEPCNT_DEFAULT;#endif /* LWIP_TCP_KEEPALIVE */ pcb-keep_cnt_sent = 0; return pcb;就是一个tcp_pcb的结构体初始化过程,使用默认值填充该结构好了,下面接着走,该轮到tcp_write了吧* Write data for sending (but does not send it immediately). * It waits in the expectation of more data being sent soon (as * it can send them more efficiently by combining them together). * To prompt the system to send data now, call tcp_output() after * calling tcp_write().【srccoretcp_out.c】err_t tcp_write(struct tcp_pcb *pcb, const void *data, u16_t len, u8_t apiflags) /* connection is in valid state for data transmission? */ if (pcb-state = ESTABLISHED | pcb-state = CLOSE_WAIT | pcb-state = SYN_SENT | pcb-state = SYN_RCVD) if (len 0) return tcp_enqueue(pcb, (void *)data, len, 0, apiflags, NULL, 0); return ERR_OK; else return ERR_CONN; 这个函数确实够简单的了,检查pcb状态,直接入队列。嗯,还是觉得看注释比看函数实现过瘾。下面的这个tcp_enqueue才是个大头函数,lwip协议栈的设计与实现文档中是这么介绍的:应用层调用tcp_write()函数以实现发送数据,接着tcp_write()函数再将控制权交给tcp_enqueue(),这个函数会在必要时将数据分割为适当大小的TCP段,然后再把这些TCP段放到所属连接的传输队列中【pcb-unsent】。函数的注释是这样写的:* Enqueue either data or TCP options (but not both) for tranmission * Called by tcp_connect(), tcp_listen_input(), tcp_send_ctrl() and tcp_write().咱们接着上面往下看,tcp_output_nagle这个函数实际上是个宏,通过预判断以决定是否调用tcp_output。也就是说最后的执行是tcp_output来实现的。这个函数或许比tcp_enqueue还要恐怖,从体积上来看。这里也只做一个简单介绍,具体请参阅tcp_out.c文档。该函数的注释是这么说的:Find out what we can send and send it。文档是这么解释的:tcp_output函数会检查现在是不是能够发送数据,也就是判断接收器窗口是否拥有足够大的空间,阻塞窗口是否也足够大,如果条件满足,它先填充未被tcp_enqueue函数填充的tcp报头字段,接着就使用ip_route或者ip_output_if函数发送数据。LWIP之TCP层接收相关 既然定了这么个标题,当然是要从socket的recv来讲了。这里主要涉及到lwip_recvfrom这个函数。它的大致过程是,先通过netconn_recv(sock-conn);从netconn的recvmbox中收取数据,在这里有个do_recv的调用,而do_recv又调用了tcp_recved,关于这个函数的注释如下:* This function should be called by the application when it has* processed the data. The purpose is to advertise a larger window* when the data has been processed.知道了,这里的do_recv只是起到一个知会的作用,可能的话会对接收条件做一些调整。回到主题,lwip_recvfrom从netconn接收完数据就是要copy the contents of the received buffer into the supplied memory pointer mem,这一步是通过netbuf_copy_partial函数来完成的。接着往下走,我们发现,数据的来源是在netconn的recvmbox,刚才提到过的。好了,那么是在什么地方,什么时候这个recvmbox被填充的呢?在工程中搜索recvmbox,发现在recv_tcp函数有这样一句:sys_mbox_trypost(conn-recvmbox, p)ok,就是它了。看看函数的原型:* Receive callback function for TCP netconns.* Posts the packet to conn-recvm
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 钽铌分离工岗位操作规程考核试卷及答案
- 教师招聘之《小学教师招聘》复习试题及答案详解(夺冠)
- 菌物标本采集制作工三级安全教育(车间级)考核试卷及答案
- 钽铌分离工理念考核试卷及答案
- 粉矿烧结工上岗考核试卷及答案
- 煤气化备配煤工突发故障应对考核试卷及答案
- 矿井通风工成本预算考核试卷及答案
- 选矿脱水工内部技能考核试卷及答案
- 酶制剂充填封装工效率提升考核试卷及答案
- 教育宣传考试题及答案
- 滑板项目选材指标与标准
- 额窦手术课件
- 智慧养猪解决方案演示课件
- 最新中医骨伤科学考试题库及答案
- 产品形态设计课件完整
- 德国巴斯夫抗氧剂和紫外线吸收剂
- SG-A088接地装置安装工程工检验批质量验收记录
- 《芯片原理与技术》课件微流控芯片
- 混凝土外观质量缺陷及治理措施PPT课件
- 十四条经络养生课件
- 麻醉医师资格分级授权管理能力评价与再授权制及程序培训考核试题及答案
评论
0/150
提交评论