LWIP之SOCKET的实现

更新时间:2024-01-18 03:58:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

LWIP之SOCKET的实现

http://bluefish.blog.51cto.com/214870/158413

Lwip协议栈的实现目的,无非是要上层用来实现app的socket编程。好,我们就从socket开始。为了兼容性,lwip的socket应该也是提供标准的socket接口函数,恩,没错,在src\\include\\lwip\\socket.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所必须有的接口。

接着看这些实际的函数实现。这些函数实现在src\\api\\socket.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函数的实现【也是在src\\api\\socket.c文件中】,在这里我们看到这样一条语句sock = &sockets[s];很明显,返回值也是这个sock,它是根据传进来的序列号在sockets数组中找到对应的元素并返回该元素的地址。好了,那么这个sockets数组是在哪里被赋值了这些元素的呢?

进行到这里似乎应该从标准的socket编程的开始,也就是socket函数讲起,那我们就顺便看一下。它对应的实际实现是下面这个函数

Int lwip_socket(int domain, int type, int protocol)【src\\api\\socket.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 < NUM_SOCKETS; ++i) { if (!sockets[i].conn) {

sockets[i].conn = newconn; sockets[i].lastdata = NULL; sockets[i].lastoffset = 0;

sockets[i].rcvevent = 0;

sockets[i].sendevent = 1; /* TCP send buf is empty */ sockets[i].flags = 0; sockets[i].err = 0; sys_sem_signal(socksem); return i; } }

sys_sem_signal(socksem); return -1; }

对了,就是这个时候对全局变量sockets数组的元素赋值的。

既然都来到这里了,那就顺便看下netconn结构的情况吧。它的学名叫netconn descriptor

/** A netconn descriptor */

struct netconn {

/** type of the netconn (TCP, UDP or RAW) */ enum netconn_type type;

/** current state of the netconn */

enum netconn_state state;

/** the lwIP internal protocol control block */ union {

struct ip_pcb *ip; struct tcp_pcb *tcp; struct udp_pcb *udp; struct raw_pcb *raw; } pcb;

/** the last error this netconn had */ err_t err;

/** sem that is used to synchroneously execute functions in the core context */ sys_sem_t op_completed;

/** mbox where received packets are stored until they are fetched

by the netconn application thread (can grow quite big) */

sys_mbox_t recvmbox;

/** mbox where new connections are stored until processed

by the application thread */

sys_mbox_t acceptmbox;

/** only used for socket layer */ int socket;

#if LWIP_SO_RCVTIMEO

/** timeout to wait for new data to be received

(or connections to arrive for listening netconns) */

int recv_timeout;

#endif /* LWIP_SO_RCVTIMEO */ #if LWIP_SO_RCVBUF

/** maximum amount of bytes queued in recvmbox */ int recv_bufsize;

#endif /* LWIP_SO_RCVBUF */ u16_t recv_avail;

/** TCP: when data passed to netconn_write doesn't fit into the send buffer,

this temporarily stores the message. */

struct api_msg_msg *write_msg;

/** TCP: when data passed to netconn_write doesn't fit into the send buffer,

this temporarily stores how much is already sent. */

int write_offset;

#if LWIP_TCPIP_CORE_LOCKING

/** TCP: when data passed to netconn_write doesn't fit into the send buffer, this temporarily stores whether to wake up the original application task if data couldn't be sent in the first try. */

u8_t write_delayed;

#endif /* LWIP_TCPIP_CORE_LOCKING */

/** A callback function that is informed about events for this netconn */ netconn_callback callback; };【src\\include\\lwip\\api.h】

到此,对这个结构都有些什么,做了一个大概的了解。

下面以SOCK_STREAM类型为例,看下netconn的new过程: 在lwip_socket函数中有 case SOCK_DGRAM:

conn = netconn_new_with_callback( (protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP, event_callback); #define netconn_new_with_callback(t, c) netconn_new_with_proto_and_callback(t, 0, c)

简略实现如下: struct netconn*

netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback) {

struct netconn *conn; struct api_msg msg;

conn = netconn_alloc(t, callback); if (conn != NULL ) {

msg.function = do_newconn; msg.msg.msg.n.proto = proto;

msg.msg.conn = conn;

TCPIP_APIMSG(&msg); }

return conn; }

主要就看TCPIP_APIMSG了,这个宏有两个定义,一个是LWIP_TCPIP_CORE_LOCKING的,一个非locking的。分别分析这两个不同类型的函数

* Call the lower part of a netconn_* function

* This function has exclusive access to lwIP core code by locking it * before the function is called.

err_t tcpip_apimsg_lock(struct api_msg *apimsg)【这个是可以locking的】 {

LOCK_TCPIP_CORE();

apimsg->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_newconn

Void do_newconn(struct api_msg_msg *msg) {

if(msg->conn->pcb.tcp == NULL) { pcb_new(msg); }

/* Else? This \

/* 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结构及其实现

http://bluefish.blog.51cto.com/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的定义,在【src\\include\\lwip\\sys.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层发送相关

http://bluefish.blog.51cto.com/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);

哈哈,可谓是一网打尽啊。对于这个函数中的几个调用,我们看一个就好了,别的实现也差不多,就是个赋值的过程

Void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)) {

pcb->recv = recv; }

setup_tcp上面也有讲过,是在newconn的时候被调用的,创建完pcb,就是调用的这个函数,以配置连接的各个回调函数。

然而到这里似乎走了一个死胡同里了,貌似没有什么地方对pcb->recv有调用的,而唯一有的就是接收TCP事件TCP_EVENT_RECV的宏定义中。同时,其他的几个函数也是类似的情况,例如send_tcp函数。

真是“山穷水尽疑无路,柳暗花明又一村”啊。原来上面的也不只是死胡同。这最底层的(在tcp层)就是tcp_input,它是直接被ip层调用的,该函数的定义注释是这么写的:

* The initial input processing of TCP. It verifies the TCP header, demultiplexes * the segment between the PCBs and passes it on to tcp_process(), which implements * the TCP finite state machine. This function is called by the IP layer (in ip_input()).

里就要从tcp的三大接收处理函数说起了。

Tcp_input又调用了tcp_process函数做进一步的处理,它的定义注释如下:

* Implements the TCP state machine. Called by tcp_input. In some * states tcp_receive() is called to receive data. The tcp_seg * argument will be freed by the caller (tcp_input()) unless the * recv_data pointer in the pcb is set.

是的,下面是tcp_receive函数,被tcp_process调用

* Called by tcp_process. Checks if the given segment is an ACK for outstanding * data, and if so frees the memory of the buffered data. Next, is places the

* segment on any of the receive queues (pcb->recved or pcb->ooseq). If the segment * is buffered, the pbuf is referenced by pbuf_ref so that it will not be freed until * i it has been removed from the buffer.

然而光有这些调用顺序是不行的,最重要的是下面两个变量【都在tcp_in.c中】 static u8_t recv_flags;

static struct pbuf *recv_data;

在tcp_receive中有以下主要几句 if (inseg.p->tot_len > 0)

{

recv_data = inseg.p; }

if (cseg->p->tot_len > 0) {

/* Chain this pbuf onto the pbuf that we will pass to the application. */ if (recv_data)

{

pbuf_cat(recv_data, cseg->p);

} else {

recv_data = cseg->p; }

cseg->p = NULL; }

下面的这个是tcp_input中的,是在tcp_process处理完之后的 if (recv_data != NULL) {

if(flags & TCP_PSH)

{

recv_data->flags |= PBUF_FLAG_PUSH; }

/* Notify application that data has been received. */ TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); }

看最后一条语句就好了,很熟悉是吧,对了,就是上面传说中的死胡同,到此也解开了。

LWIP之IP层实现

这一部分的实现都是在ip.c文件中【src\\cor\\ipv4】,可以看到在这个文件中主要实现了3个函数,ip_input;ip_route;ip_output以及ip_output_if。下面分别来介绍它们。

这些函数可以分成两大类:接收和发送。下面就先从发送开始,首先要说的就是ip_output函数,这个也是发送过程中最重要的一个,它是被tcp层调用的,详细可参见以上章节。

* Simple interface to ip_output_if. It finds the outgoing network * interface and calls upon ip_output_if to do the actual work.

err_t ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest, u8_t ttl, u8_t tos, u8_t proto) {

struct netif *netif;

if ((netif = ip_route(dest)) == NULL) { return ERR_RTE; }

return ip_output_if(p, src, dest, ttl, tos, proto, netif); }

可以看到该函数的实现就像注释所说的一样,直接调用了ip_route和ip_outputif两个函数。根据以往的经验,先看下netif这个结构的实现情况:

* Generic data structure used for all lwIP network interfaces. * The following fields should be filled in by the initialization

* function for the device driver: hwaddr_len, hwaddr[], mtu, flags//这几个是要用驱动层填写的

struct netif {

/** pointer to next in linked list */ struct netif *next;

/** IP address configuration in network byte order */ struct ip_addr ip_addr; struct ip_addr netmask; struct ip_addr gw;

/** This function is called by the network device driver

* to pass a packet up the TCP/IP stack. */

err_t (* input)(struct pbuf *p, struct netif *inp);

/** This function is called by the IP module when it wants

* to send a packet on the interface. This function typically * first resolves the hardware address, then sends the packet. */

err_t (* output)(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr);

/** This function is called by the ARP module when it wants

* to send a packet on the interface. This function outputs * the pbuf as-is on the link medium. */

err_t (* linkoutput)(struct netif *netif, struct pbuf *p);

#if LWIP_NETIF_STATUS_CALLBACK

/** This function is called when the netif state is set to up or down */ void (* status_callback)(struct netif *netif); #endif /* LWIP_NETIF_STATUS_CALLBACK */

#if LWIP_NETIF_LINK_CALLBACK

/** This function is called when the netif link is set to up or down */ void (* link_callback)(struct netif *netif); #endif /* LWIP_NETIF_LINK_CALLBACK */

/** This field can be set by the device driver and could point

* to state information for the device. */

void *state;

#if LWIP_DHCP

/** the DHCP client state information for this netif */ struct dhcp *dhcp; #endif /* LWIP_DHCP */

#if LWIP_AUTOIP

/** the AutoIP client state information for this netif */ struct autoip *autoip; #endif

#if LWIP_NETIF_HOSTNAME

/* the hostname for this netif, NULL is a valid value */ char* hostname;

#endif /* LWIP_NETIF_HOSTNAME */

/** number of bytes used in hwaddr */ u8_t hwaddr_len;

/** link level hardware address of this interface */ u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; /** maximum transfer unit (in bytes) */ u16_t mtu;

/** flags (see NETIF_FLAG_ above) */ u8_t flags;

/** descriptive abbreviation */ char name[2];

/** number of this interface */

u8_t num;

#if LWIP_SNMP

/** link type (from \

u8_t link_type;

/** (estimate) link speed */ u32_t link_speed;

/** timestamp at last change made (up/down) */ u32_t ts;

/** counters */

u32_t ifinoctets; u32_t ifinucastpkts; u32_t ifinnucastpkts; u32_t ifindiscards; u32_t ifoutoctets; u32_t ifoutucastpkts; u32_t ifoutnucastpkts; u32_t ifoutdiscards; #endif /* LWIP_SNMP */

#if LWIP_IGMP

/* This function could be called to add or delete a entry in the multicast filter table of the ethernet MAC.*/

err_t (*igmp_mac_filter)( struct netif *netif, struct ip_addr *group, u8_t action);

#endif /* LWIP_IGMP */

#if LWIP_NETIF_HWADDRHINT u8_t *addr_hint;

#endif /* LWIP_NETIF_HWADDRHINT */ };

该结构体实现在【src\\include\\lwip\\netif.h】,注意到该结构体成员中有3个函数指针变量。好了,这个结构体先做一大体了解。用到的时候再详细讲。

接下来先看下ip_route函数的实现:

* Finds the appropriate network interface for a given IP address. It * searches the list of network interfaces linearly. A match is found * if the masked IP address of the network interface equals the masked * IP address given to the function.

struct netif * ip_route(struct ip_addr *dest) {

struct netif *netif;

/* iterate through netifs */

for(netif = netif_list; netif != NULL; netif = netif->next) { /* network mask matches? */ if (netif_is_up(netif)) {

if (ip_addr_netcmp(dest, &(netif->ip_addr), &(netif->netmask))) { /* return netif on which to forward IP packet */

return netif; } } }

if ((netif_default == NULL) || (!netif_is_up(netif_default))) {

snmp_inc_ipoutnoroutes(); return NULL; }

/* no matching netif found, use default netif */ return netif_default; }

可以说这个函数的实现很简单,且作用也很容易看懂,就像其注释所说的一样。不过在这个函数中我们还是发现了一些什么,对了,就是struct netif *netif_list;【src\\core\\netif.c】的使用。既然这里都使用了这个网络接口链表,那它是在哪里被初始化的呢?

好了,首先我们发现在netif_add函数中有对netif_list的调用,netif_add是被do_netifapi_netif_add函数调用的,而do_netifapi_netif_add是在netifapi_netif_add中通过netifapi_msg被TCPIP_NETIFAPI调用的。问题似乎很清楚,只要找到nnetifapi_netif_add是被谁调用的就好了,然而,搜遍整个工程也没有发现这个函数的影子,除了一个声明一个实现外。My god,又进入死胡同了?好吧,这里先标识一下,待解【见下面的解释】

我们接着看ip_output_if这个函数,具体函数可参考【src\\core\\ipv4\\ip.c】。它的函数定义注释如下:

* Sends an IP packet on a network interface. This function constructs * the IP header and calculates the IP header checksum. If the source * IP address is NULL, the IP address of the outgoing network * interface is filled in as source address.

* If the destination IP address is IP_HDRINCL, p is assumed to already * include an IP header and p->payload points to it instead of the data.

再看最后一句:return netif->output(netif, p, dest);

嗯,看来这个netif还是关键啊,如果估计不错的话,接收的时候也要用到这个结构的。那就看它在什么地方被赋值的吧。又经过一番搜索,看来在目前的代码中是找不到的了。查看lwip协议栈的设计与实现,特别是网络接口层的那一节,终于明白了,原来这些是要有设备驱动来参与的:【一下为引用】

当收到一个信息包时,设备驱动程序调用input指针指向的函数。网络接口通过output指针连接到设备驱动。这个指针指向设备驱动中一个向物理网络发送信息包

的函数,当信息包包被发送时由IP层调用,这个字段由设备驱动的初始设置函数填充。

接下来就是ip层的接收过程了。刚才上面也有提到驱动设备收到包,丢给netif的input函数,这个input函数也是设备驱动层来设置的。无非有两个可能,一个是ip_input,另外一个就是tcpip_input。因为tcpip_input函数的处理是最终调用到了ip_input【在tcpip_thread中】。按照正常情况下应该是ip_input函数的,我们先来看下这个函数。

* This function is called by the network interface device driver when * an IP packet is received. The function does the basic checks of the * IP header such as packet size being at least larger than the header * size etc. If the packet was not destined for us, the packet is * forwarded (using ip_forward). The IP checksum is always checked.

嗯,那就这样吧,到这里我们可以说IP层的发送流程已经走完了。

原型:err_t ip_input(struct pbuf *p, struct netif *inp)

该函数大致的处理过程是:处理ip包头;找到对应的netif;检查如果是广播或多播包,则丢掉;如果是tcp协议的话就直接调用了tcp_input函数处理数据。

到此,ip层的东西大致就说完了。最后,由于tcp和ip层的东西都说完了,所以此时我们顺便看下,tcpip的整体实现,这个主要是在src\\api\\tcpip.c文件中实现。我们知道发送过程是由socket直接调用的,所以这个文件中不涉及,说白了,这个文件主要是涉及到整个接收过程。这里实现的函数有tcpip_input,和tcpip_thread以及tcpip_init函数。

Tcpip_init函数很简单就是创建系统线程(sys_thread_new)tcpip_thread。 Tcpip_thread函数的注释如下:

* The main lwIP thread. This thread has exclusive access to lwIP core functions

* (unless access to them is not locked). Other threads communicate with this

* thread using message boxes.

它的整个过程就是一直从mbox中取出msg,对各种msg的一个处理过程。 Tcpip_input函数,是在tcpip_thread中被调用的处理设备驱动接收到的信息包,并调用

ip_input来进一步处理。

整个启动过程: main---> vlwIPInit() void vlwIPInit( void ) {

/* Initialize lwIP and its interface layer. */ sys_init();

mem_init(); memp_init(); pbuf_init(); netif_init(); ip_init();

sys_set_state(( signed portCHAR * ) \ tcpip_init( NULL, NULL ); sys_set_default_state(); }

从上面我们知道,tcpip_init创建tcpip_thread 在tcpip_thread的开始有如下代码: (void)arg; ip_init();

#if LWIP_UDP udp_init(); #endif

#if LWIP_TCP tcp_init(); #endif

#if IP_REASSEMBLY

sys_timeout(1000, ip_timer, NULL); #endif

if (tcpip_init_done != NULL) {

tcpip_init_done(tcpip_init_done_arg); }

下面是tcp_init的实现 Void tcp_init(void) {

/* Clear globals. */

tcp_listen_pcbs.listen_pcbs = NULL; tcp_active_pcbs = NULL; tcp_tw_pcbs = NULL; tcp_tmp_pcb = NULL; /* initialize timer */ tcp_ticks = 0; tcp_timer = 0; }

LWIP的底层结构(物理层)

我们前面讲到说是ip层的发送和接收都是直接调用了底层,也就是设备驱动层的函数实现,在这里暂且称之为物理层吧。下面就接着ip层的讲,不过由于这里的设备驱动各平台的都不一样,为此,我们选择ARM9_STR91X_IAR这个Demo作为实例,该平台的网络设备驱动在\\library\\source\\91x_enet.c文件中。而ethernetif.c文件就是我们需要的,它是连接设备驱动程序与ip层的桥梁。

Ethernetif.c文件中提供的函数主要有以下这么几个: (1) low_level_init (2) low_level_input (3) low_level_output (4) ethernetif_init (5) ethernetif_input (6) ethernetif_output

这里对外的接口只有ethernetif_init函数,它是main函数中通过

netif_add( &EMAC_if, &xIpAddr, &xNetMast, &xGateway, NULL, ethernetif_init, tcpip_input );来被调用的。我们可以清楚的看到,tcpip_input的使用,它就是被用来当有数据接收的时候被调用的以使接收到的数据进入tcpip协议栈。

在netif_add函数中,我们可以看到 netif->input = input;

if (init(netif) != ERR_OK) {

return NULL; }

Ok,从这里就进入到ethernetif_init函数了,在这个函数中,我们主要看以下几句:

netif->output = ethernetif_output; netif->linkoutput = low_level_output; low_level_init(netif); etharp_init();

可以看到,netif->output 和netif->linkoutput被赋值了,这个很重要的,等会再说。

好,再接着看low_level_init函数

s_pxNetIf = netif;//对全局变量s_pxNetIf 赋初值 ENET_InitClocksGPIO(); ENET_Init();

ENET_Start();//这3句是对网络设备的寄存等的配置

xTaskCreate( ethernetif_input, ( signed portCHAR * ) \netifINTERFACE_TASK_STACK_SIZE, NULL, netifINTERFACE_TASK_PRIORITY, NULL ); 以ethernet_input创建task,这个函数也很有意思,首先可以看到的是一个无限循环,在循环体中有以下调用:

p = low_level_input( s_pxNetIf );

s_pxNetIf->input(p, s_pxNetIf);//tcpip_input

虽然有了这两句,还不是很清楚,可以确定的是后一句是把接收到的数据送入tcpip协议栈处理,为此,我们想到上一句是从硬件读出数据。看下具体的low_level_input函数实现:

len = ENET_HandleRxPkt(s_rxBuff); 这个函数很好理解,主要的是上面的那一句。

/****************************************************************************** * Function Name : ENET_HandleRxPkt

* Description : receive a packet and copy it to memory pointed by ppkt. * Input : ppkt: pointer on application receive buffer. * Output : None

* Return : ENET_NOK - If there is no packet * : ENET_OK - If there is a packet

******************************************************************************/

u32 ENET_HandleRxPkt ( void *ppkt) {

ENET_DMADSCRBase *pDescr; u16 size;

static int iNextRx = 0;

if( dmaRxDscrBase[ iNextRx ].dmaPackStatus &

DMA_DSCR_RX_STATUS_VALID_MSK )

{

return 0; }

pDescr = &dmaRxDscrBase[ iNextRx ];

/*Get the size of the packet*/

size = ((pDescr->dmaPackStatus & 0x7ff) - 4);

//MEMCOPY_L2S_BY4((u8*)ppkt, RxBuff, size); /*optimized memcopy function*/

memcpy(ppkt, RxBuff[iNextRx], size); //string.h library*/

/* Give the buffer back to ENET */

pDescr->dmaPackStatus = DMA_DSCR_RX_STATUS_VALID_MSK;

参数:sockFd已创建、绑定并监听的插口;clientAddr远端连接信息;addrLen结构体长度。

-------------------------------------------------------------------- #include #include

int connect ( int sockFd, struct sockaddr *servAddr, int addrLen ); TCP/UDP客服端申请TCP/UDP服务器的链接。

参数:sockFd已创建的插口;servAddr服务器连接信息;addrLen结构体长度。 返回0成功,-1出错

-------------------------------------------------------------------- #include #include #include

int select( int n, fd_set *read_fds, fd_set *write_fds, fd_set *exceptfds, struct timeval *timeout );

挂起当前线程,等待特定事件发生或定时器过期。本函数可以指定4类特定事件:read、write、exception和超时。返回插口ID表示事件有响应,0表示超时,-1表示出错。

参数:n应该大于所有插口ID,用FD_SETSIZE代替;后面三个fd_set结构体存储三种插口事件位图: typedef struct fd_set {

fd_mask fds_bits[(FD_SETSIZE + NFDBITS - 1) / NFDBITS]; } fd_set;

用以下四个宏修改:

FD_SET(fd, fdset) fd插口ID,fdset是fd_set结构体地址,设置插口事件为真 FD_CLR(fd, fdset)设置插口事件为假 FD_ISSET(fd, fdset)获取插口状态,是否设置 FD_ZERO(fdset)清除所有设置 第四个参数timeval结构体如下: struct timeval {

int tv_sec; /* 秒 */ int tv_usec; /* 毫秒 */ };

用来设置超时时间。

-------------------------------------------------------------------- #include #include

int send ( int sockFd, const void *msg, int msgLen, unsigned int flags); int sendto ( int sockFd, const void *msg, int msgLen, unsigned int flags, const struct sockaddr *to, int toLen);

这两个函数都用来按插口发送数据包,send用在已经连接的插口,sendto用在没有连接上的插口。

send函数的参数:sockFD插口ID,msg要发送的数据指针,msgLen要发送的数据长度,flags发送选项(按位)

sendto函数的参数:UDP专用,插口必须是SOCK_DGRAM类型。由于没有连接,所以sendto函数增加了两个与连接有关的参数。to定义目标地址的结构体,toLen是结构体长度。sockaddr结构体如下: struct sockaddr { u_short sa_family; char sa_data[14]; };

这两个函数返回值均为实际发送字节的长度(软件需要调整偏移量将数据全部发送),-1表示发送不成功。

-------------------------------------------------------------------- #include #include

int recv ( int sockFd, const void *msg, int msgLen, unsigned int flags); int recvfrom ( int sockFd, const void *msg, int msgLen, unsigned int flags, const struct sockaddr *from, int *fromLen);

这两个函数均是按插口来接收数据包,recv函数用在已连接插口上,recvfrom用在未连接插口上。

recv函数参数:sockFd插口ID,msg接收缓存地址,msgLen接收缓存最大空间,flags接收选项。

recvfrom函数参数:UDP专用,插口必须是SOCK_DRAM类型。由于没有连接,所以recvfrom函数增加了两个与连接有关的参数。from定义目标地址的结构体,formLen是结构体长度。

两个函数均返回接收到的数据数,-1接收错误,0表示目标地址已经传输完毕并关闭连接。

-------------------------------------------------------------------- #include #include

int setsockopt ( int sd, int level, int optname, const void *optval, socklen_t optlen);

int getsockopt ( int sd, int level, int optname, void *optval, socklen_t *optlen );

setsockopt函数用来改变插口的模式,这种改变是通过修改插口选项实现的。 getsockopt函数用来获取插口选项的值。

参数:sd插口ID;level协议栈选项,包括SOL_SOCKET(插口层)、IPPROTO_TCP(TCP层)和IPPROTO_IP(IP层);optname需要修改的选项名;optval修改值地址;optlen修改值长度。 返回0表示成功。

-------------------------------------------------------------------- #include

int getsockname ( int sd, struct sockaddr *addr, int *addrLen ); int getpeername ( int sd, struct sockaddr *addr, int *addrLen );

getsockname函数用于从已连接的插口中获取本地地址信息。getpeername函数用于获取远端地址信息。

参数:sd插口ID;addr地址信息结构体;addrLen结构体长度。 返回0成功,-1错误

-------------------------------------------------------------------- #include

int close ( int sd );

关闭插口通信(丢弃未发送的数据包并拒绝接受数据)

-------------------------------------------------------------------- #include

int shutdown ( int sockFd, int how ); 该函数提供了更大的权限控制插口的关闭过程。

参数:sockFd插口ID;how仅能为0、1和2这三个值 0表示停止接收当前数据并拒绝以后的数据接收 1表示停驶发送数据并丢弃未发送的数据 2是0和1的合集

-------------------------------------------------------------------- int read (int sockFD, void *buffer, UInt32 numBytes);

从指定插口中等待数据接收并存放到buffer中。该函数会挂起线程,直到有数据接收到。

参数:sockFd插口ID;buffer缓存地址;numBytes缓冲大小

该函数返回接收到的数据大小,-1表示出错,0表示远端已经关闭连接。 -------------------------------------------------------------------- int write (int sockFD, void *buffer, UInt32 numBytes); 将缓存中数据写到指定插口准备发送。

参数:sockFd插口ID;buffer缓存地址;numBytes缓存中数据大小 该函数返回实际发送的数据量,-1表示出错。

-------------------------------------------------------------------- 补充:

lwIP协议栈在socket模式下也就是操作系统中运行,创建进程的方式与操作系统中创建进程的方式有所不同。要用专用函数:

sys_thread_t sys_thread_new(char *name, void(* thread)(void *arg), void *arg, int stacksize, int prio)

参数:name线程说明;thread线程函数;arg线程函数的参数;stacksize线程堆栈大小;prio线程优先级

在lwIP下创建线程统一使用此函数,当然这个函数也是要调用系统创建线程的API的。

非标准Socket接口,lwip提供了一套Socket API,这套API的标准与正常操作系统下的Socket API的形式不是很一致,我们先前已经在这套API上实现了Web Server,已测试在没有Mobile IP环境下工作正常。

下面我们就一个lwip典型的UDP协议工作过程作为对lwip的简单介绍。 UDP发送过程:

1.应用层:绑定UDP套接字

我们必须先创建一个UDP套接字,通过调用udp_new()进行申请,然后调用udp_bind()绑定在 UDP端口上,在这个调用过程中,我们必须编写一个用于处理这个UDP套接字接收到的数据报文的函数,并把这个函数作为udp_bind()的参数,以后 当套接字

接收到数据报文时会自动调用这个函数,我们将在后面介绍这个函数怎么调用的。绑定结束之后,必须调用udp_connect()将数据报文的目的 地址绑定在UDP的数据结构中,最后就是调用udp_send()把数据报文发送出去。 2.传输层的处理

做好应用层的处理之后,数据报文被提交到UDP层,udp_send()函数中首先给数据报文加入UDP头部,然后调用ip_route()选择一个合适的网络接口进行发送,最后调用ip_output()把数据报文传入IP层。 3.IP层的处理

ip_route()函数比较各个网络接口的IP地址是否与目的IP地址在同一子网中,如果有,就把它当成发送的网络接口返回,如果没有就返回一个默认的网络接口。 在 ip_output()函数中,先给数据报文加上IP头部,然后比较目的IP地址与网络接口的IP地址是否在同一网段,如果不是,就必须先把数据报文发送 到网关,于是使用网关的IP地址作为目的主机,如果目的IP地址与网络接口的IP地址在同一网段,则把目的IP地址作为目的主机。接着调用 arp_lookup()在ARP缓存中查找目的主机的MAC地址,找到了调用ethernet_output()把数据报文传入到数据链路层发送,如果 找不到,就调用arp_query()发送ARP请求解析目的主机的MAC地址。 4.ARP协议的处理

arp_lookup()实现在本地ARP缓存中查找目的主机的MAC地址,找到了返回该MAC地址,找不到返回NULL。

arp_query()函数中构造一个ARP请求报文,然后调用ethernet_output()把该报文送到数据链路层发送。 5. 数据链路层的处理

数据链路层的处理就是给数据报文添上相对的以太网头部,然后调用lowlever_output()直接把报文传送出去。 UDP接收过程:

接收过程与发送过程刚好相反,数据报文首先调用ethernet_input()函数到达数据链路层,去掉以太网头部之后如果是ARP报文传给调用 arp_input()交给ARP协议处理,如果是IP报文就调用ip_input()进入IP层处理,ip_input()函数中比较数据报文的目的 IP地址,如果与某个网络接口的IP地址相同,则接收这个报文,依照IP头部的协议字段,调用各自协议的输入处理函数,本例中将调用 udp_input(),在udp_input()中提取数据报文的端口号,然后在已登记的套接字中查找与该端口号符合的UDP接收函数,如果没有找到相 应的套接字,调用icmp_output()发送一个ICMP不可达报文,如果找到了,就调用该函数(这个函数就是我们在udp_bind()时传入的其 中一个参数)。

本文来源:https://www.bwwdw.com/article/ujto.html

Top