TCP/IP协议栈之LwIP(十) 您所在的位置:网站首页 accept返回值为0 TCP/IP协议栈之LwIP(十)

TCP/IP协议栈之LwIP(十)

2024-06-17 08:01| 来源: 网络整理| 查看: 265

文章目录 一、BSD Socket简介 二、Socket API实现 2.1 Socket结构描述 2.2 Socket API接口函数 2.3 Socket API编程示例 三、基于Select的并发服务器 3.1 Select函数实现原理 3.2 基于Select的并发服务器示例 更多文章

一、BSD Socket简介

BSD Socket最初是由加州伯克利大学为Unix系统开发出来的,因此也被称为伯克利套接字(Internet Berkeley Sockets),它是一种采用C语言进程间通信库的应用程序接口(API),经常用在计算机网络间的通信,大多数其他的编程语言也都使用类似的接口。

BSD Socket作为一种API,允许不同主机或者同一个计算机上的不同进程之间的通信,它支持多种I/O设备和驱动,但是具体的实现是依赖操作系统的。这种接口对于TCP/IP是必不可少的,所以是互联网的基础技术之一,所有现代的操作系统都实现了BSD Socket API,因为它已经是连接互联网的标准接口了,也是当前主机网络程序设计领域的事实标准。

为了能更大程度上方便开发者将其他平台上的网络应用程序移植到LwIP上,也为了能让更多开发者快速上手LwIP,LwIP内核作者为LwIP设计了第三种应用程序编程接口,即Socket API,兼容于BSD Socket,但是受嵌入式处理器资源和性能的限制,部分Socket接口并未在LwIP中完全实现,如果想了解完整的BSD Socket可以参考Berkeley套接字和BSD套接字API两篇文章。同时,Socket API是基于Sequential API来实现的,所以应用程序的执行效率较基于后者实现的程序效率更低,抽象程度越高,执行效率的损失越大,读者需要平衡取舍执行效率与通用可移植性。

BSD中用一个套接字记录网络中的一个连接,套接字就像一个普通的文件,应用程序可以像操作普通文件那样操作它,例如打开、关闭、读写等。文件描述符是一个整数,所以套接字描述符也是一个整数,通过它可以索引到内核中描述连接的具体结构。

二、Socket API实现 2.1 Socket结构描述

在LwIP抽象出来的Socket API中,内核为用户提供了最多NUM_SOCKETS个可使用的socket描述符,并定义了结构体lwip_socket(对netconn结构的封装和增强)来描述一个具体连接。内核定义了数组sockets,通过一个Socket描述符就可以索引得到相应的连接结构lwip_socket,从而实现对连接的操作。连接结构lwip_socket的数据结构实现如下:

// rt-thread\components\net\lwip-1.4.1\src\api\sockets.c #define NUM_SOCKETS MEMP_NUM_NETCONN /** Contains all internal pointers and states used for a socket */ struct lwip_sock { /** sockets currently are built on netconns, each socket has one netconn */ struct netconn *conn; /** data that was left from the previous read */ void *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 */ s16_t rcvevent; /** number of times data was ACKed (free send buffer), set by event_callback(), tested by select */ u16_t sendevent; /** error happened for this socket, set by event_callback(), tested by select */ u16_t errevent; /** last error that occurred on this socket */ int err; /** counter of how many threads are waiting for this socket using select */ int select_waiting; }; /** The global array of available sockets */ static struct lwip_sock sockets[NUM_SOCKETS];

lwip_socket结构是对连接结构netconn的再次封装,在内核内部,对lwip_socket的操作最终都会映射到对netconn结构的操作上。

描述socket的结构体lwip_sock相对比较简单,它基于内核netconn来实现所有逻辑,conn指向了与socket对应的netconn结构;另外在socket数据接收时,其实它是利用netconn相关的接收函数获得一个pbuf(对于TCP)或者一个netbuf(对于UDP)数据,而这二者封装的数据可能大于socket用户指定的数据接收长度,因此在这种情况下,这两个数据包需要暂时保存在socket中,以待用户下一次读取,这里lastdata就用于指向未被用户完全读取的数据包,而lastoffset则指向了未读取的数据在数据包中的偏移。

lwip_sock最后的五个字段是为select机制实现时使用,select函数可通过事件机制监听一个或多个套接字状态的变化,常用于并发服务器编程,后面再专门介绍其实现过程。

2.2 Socket API接口函数 socket 函数原型:int socket(int domain, int type, int protocol)

该函数功能是向内核申请一个套接字,本质上,socket是对Sequential API函数netconn_new的封装,三个参数分别如下:

参数名称 参数取值 domain为创建的套接字指定使用的协议簇 AF_INET 表示IPv4网络协议AF_INET6 表示IPv6AF_UNIX 表示本地套接字(使用一个文件) type指定协议簇中的具体服务类型 SOCK_STREAM (可靠数据流交付服务,比如TCP)SOCK_DGRAM (无连接数据报交付服务,比如UDP)SOCK_RAW (原始套接字,比如RAW) protocol指定实际使用的具体协议 常见的有IPPROTO_TCP、IPPROTO_UDP等若设置为"0",表示根据前两个参数使用缺省协议

该函数的返回值为一个有效的socket描述符,内核使用这个函数就可以索引到描述连接的具体结构lwip_socket。当申请失败时,该函数返回-1。该函数的实现代码如下:

// rt-thread\components\net\lwip-1.4.1\src\include\lwip\sockets.h #define socket(a,b,c) lwip_socket(a,b,c) // rt-thread\components\net\lwip-1.4.1\src\api\sockets.c int lwip_socket(int domain, int type, int protocol) { struct netconn *conn; int i; /* create a netconn */ switch (type) { case SOCK_RAW: conn = netconn_new_with_proto_and_callback(NETCONN_RAW, (u8_t)protocol, event_callback); break; case SOCK_DGRAM: conn = netconn_new_with_callback( (protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP, event_callback); break; case SOCK_STREAM: conn = netconn_new_with_callback(NETCONN_TCP, event_callback); if (conn != NULL) { /* Prevent automatic window updates, we do this on our own! */ netconn_set_noautorecved(conn, 1); } break; default: set_errno(EINVAL); return -1; } if (!conn) { set_errno(ENOBUFS); return -1; } i = alloc_socket(conn, 0); if (i == -1) { netconn_delete(conn); set_errno(ENFILE); return -1; } conn->socket = i; set_errno(0); return i; } static int alloc_socket(struct netconn *newconn, int accepted) { int i; SYS_ARCH_DECL_PROTECT(lev); /* allocate a new socket identifier */ for (i = 0; i sockets[i].conn = newconn; /* The socket is not yet known to anyone, so no need to protect after having marked it as used. */ SYS_ARCH_UNPROTECT(lev); sockets[i].lastdata = NULL; sockets[i].lastoffset = 0; sockets[i].rcvevent = 0; /* TCP sendbuf is empty, but the socket is not yet writable until connected * (unless it has been created by accept()). */ sockets[i].sendevent = (newconn->type == NETCONN_TCP ? (accepted != 0) : 1); sockets[i].errevent = 0; sockets[i].err = 0; sockets[i].select_waiting = 0; return i; } SYS_ARCH_UNPROTECT(lev); } return -1; } bind 函数原型:int bind(int s, const struct sockaddr *name, socklen_t namelen)

该函数功能是将一个套接字与本地地址信息进行绑定,本质上,bind是对Sequential API函数netconn_bind的封装。作为服务器程序,通常需要调用该函数将套接字绑定到本地的知名端口号上,这样才能响应客户端的连接请求。参数s记录了将要进行绑定的套接字对象;参数name指向一个sockaddr结构体,包含了本地IP地址和端口号等信息;参数namelen指出了结构体的长度。结构体sockaddr的定义如下:

// rt-thread\components\net\lwip-1.4.1\src\include\lwip\sockets.h /* members are in network byte order */ struct sockaddr { u8_t sa_len; u8_t sa_family; char sa_data[14]; }; struct sockaddr_in { u8_t sin_len; u8_t sin_family; u16_t sin_port; struct in_addr sin_addr; char sin_zero[8]; }; // rt-thread\components\net\lwip-1.4.1\src\include\ipv4\lwip\inet.h /** For compatibility with BSD code */ struct in_addr { u32_t s_addr; };

结构体sockaddr中的sa_family指向该套接字所使用的协议簇,sa_data指向了bind需要的一些本地地址信息,前2个字节用于记录端口号port,接下来4个字节用于记录IP地址,剩余的8个字节用于传递其他信息,这里暂未用到。由于sa_data以连续空间的方式存在,如果用户要填写其中的IP字段和端口port字段,会显得比较麻烦,因此socket中定义了另一种sockaddr_in结构,它与sockaddr结构对等,只是从中抽出IP地址和端口号port,方便于用于的编程操作。

该函数绑定成功时返回0,绑定失败时返回-1。该函数的实现代码如下:

// rt-thread\components\net\lwip-1.4.1\src\include\lwip\sockets.h #define bind(a,b,c) lwip_bind(a,b,c) // rt-thread\components\net\lwip-1.4.1\src\api\sockets.c int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen) { struct lwip_sock *sock; ip_addr_t local_addr; u16_t local_port; err_t err; const struct sockaddr_in *name_in; sock = get_socket(s); if (!sock) { return -1; } /* check size, familiy and alignment of 'name' */ name_in = (const struct sockaddr_in *)(void*)name; inet_addr_to_ipaddr(&local_addr, &name_in->sin_addr); local_port = name_in->sin_port; err = netconn_bind(sock->conn, &local_addr, ntohs(local_port)); if (err != ERR_OK) { sock_set_errno(sock, err_to_errno(err)); return -1; } sock_set_errno(sock, 0); return 0; } connect 函数原型:int connect(int s, const struct sockaddr *name, socklen_t namelen)

该函数功能与bind函数相对应:将套接字与目的地址信息进行绑定。该函数本质上是对Sequential API函数netconn_connect的封装,作为客户端程序,通常需要使用该函数来绑定服务器的地址信息。对于TCP连接,调用这个函数会导致客户端与服务器之间发生连接握手过程,并最终建立一条稳定的连接;对于UDP连接,该函数调用不会有任何数据包被发送,只是在连接结构中记录下服务器的地址信息。当调用成功时,函数返回0;否则返回-1。

getsockname / getpeername 函数原型:int getsockname (int s, struct sockaddr *name, socklen_t *namelen); int getpeername (int s, struct sockaddr *name, socklen_t *namelen)

该函数功能是获取该套接字的本地地址信息 / 远端地址信息。该函数本质上是对Sequential API函数netconn_addr / netconn_peer的封装,参数name保存了从套接字获取的地址信息,参数namelen保存了从套接字获取的name结构体长度信息。当调用成功时,函数返回0;否则返回-1。

listen 函数原型:int listen(int s, int backlog)

该函数只能在TCP服务器程序中使用,作用是将一个套接字置为侦听状态,以等待客户端的连接请求。该函数本质上是对Sequential API函数netconn_listen的封装,内核同时接收到多个连接请求时,需要对这些请求进行排队处理,参数backlog指明了该套接字上连接请求队列的最大长度。当调用成功时,函数返回0;否则返回-1。

accept 函数原型:int accept(int s, struct sockaddr *addr, socklen_t *addrlen)

该函数也只能在TCP服务器程序中使用,作用是从套接字的连接请求队列中获取一个新建立的连接,如果请求队列为空,该函数会阻塞,直至新连接到来。该函数本质上是对Sequential API函数netconn_accept的封装,当接收到新连接后,连接另一端(客户端)的地址信息会被填入到地址结构addr中,而对应地址信息的长度被记录到addrlen中。函数返回新连接的套接字描述符,若调用失败,函数返回-1。

send / sendto 函数原型:int send(int s, const void *dataptr, size_t size, int flags); int sendto(int s, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen)

函数sendto主要在UDP连接中使用,作用是向另一端发送UDP报文。该函数本质上是对Sequential API函数netconn_send的封装,参数data和size分别指出了待发送数据的起始地址和长度;flags指明数据发送时的特殊处理,例如带外数据、紧急数据等,通常设置为0;参数to和tolen分别指明了目的地址信息及信息的长度,地址信息包含了目的IP地址和目的端口号。调用成功后,函数返回成功发送的字节数,出错则返回-1。

另一个函数send主要用于在一条已建立的连接上发送数据,因此不需要在参数中包含目的地址信息。该函数即可用于TCP程序,也可用于UDP程序,其本质是对Sequential API函数netconn_write和netconn_send的封装。调用成功后,函数返回成功发送的字节数,出错则返回-1。该函数的实现代码如下:

// rt-thread\components\net\lwip-1.4.1\src\include\lwip\sockets.h #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) /* Flags we can use with send and recv. */ #define MSG_PEEK 0x01 /* Peeks at an incoming message */ #define MSG_WAITALL 0x02 /* Unimplemented: Requests that the function block until the full amount of data requested can be returned */ #define MSG_OOB 0x04 /* Unimplemented: Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific */ #define MSG_DONTWAIT 0x08 /* Nonblocking i/o for this operation only */ #define MSG_MORE 0x10 /* Sender will send more */ // rt-thread\components\net\lwip-1.4.1\src\api\sockets.c int lwip_send(int s, const void *data, size_t size, int flags) { struct lwip_sock *sock; err_t err; u8_t write_flags; size_t written; sock = get_socket(s); if (!sock) { return -1; } if (sock->conn->type != NETCONN_TCP) { return lwip_sendto(s, data, size, flags, NULL, 0); } write_flags = NETCONN_COPY | ((flags & MSG_MORE) ? NETCONN_MORE : 0) | ((flags & MSG_DONTWAIT) ? NETCONN_DONTBLOCK : 0); written = 0; err = netconn_write_partly(sock->conn, data, size, write_flags, &written); sock_set_errno(sock, err_to_errno(err)); return (err == ERR_OK ? (int)written : -1); } int lwip_sendto(int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen) { struct lwip_sock *sock; err_t err; u16_t short_size; const struct sockaddr_in *to_in; u16_t remote_port; struct netbuf buf; sock = get_socket(s); if (!sock) { return -1; } if (sock->conn->type == NETCONN_TCP) { return lwip_send(s, data, size, flags); } /* @todo: split into multiple sendto's? */ short_size = (u16_t)size; to_in = (const struct sockaddr_in *)(void*)to; /* initialize a buffer */ buf.p = buf.ptr = NULL; buf.flags = 0; if (to) { inet_addr_to_ipaddr(&buf.addr, &to_in->sin_addr); remote_port = ntohs(to_in->sin_port); netbuf_fromport(&buf) = remote_port; } else { remote_port = 0; ip_addr_set_any(&buf.addr); netbuf_fromport(&buf) = 0; } /* Allocate a new netbuf and copy the data into it. */ if (netbuf_alloc(&buf, short_size) == NULL) { err = ERR_MEM; } else { if (sock->conn->type != NETCONN_RAW) { u16_t chksum = LWIP_CHKSUM_COPY(buf.p->payload, data, short_size); netbuf_set_chksum(&buf, chksum); err = ERR_OK; } else err = netbuf_take(&buf, data, short_size); } if (err == ERR_OK) { /* send the data */ err = netconn_send(sock->conn, &buf); } /* deallocated the buffer */ netbuf_free(&buf); sock_set_errno(sock, err_to_errno(err)); return (err


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有