TCP/IP协议栈Lwip的设计与实现:之七

接上文:TCP/IP协议栈Lwip的设计与实现:之六_龙赤子的博客-CSDN博客

目录

17.BSD套接字库

17.1 socket的表示

17.2 分配一个socket

17.2.1 socket()调用

17.3连接建立

17.3.1 bind()调用

17.3.2 connect()调用

17.3.3 listen()调用

17.3.4 accept()调用

17.4发送和接收数据

17.4.1 send()调用

17.4.2 sendto()和sendmsg()调用

17.4.3 write()调用

17.4.4 recv()和read()调用

17.4.5 recvfrom()和recvmsg()调用

18.代码实例

18.1使用API

18.2直接使用栈接口

参考


17.BSD套接字库

这部分利用LWIP API实现了一些简单的BSD socket API。这里所提供的实现仅仅作为参考,不期望在实际程序中使用。这里没有错误捕获的示例。

同样,这里的实现不支持BSD socket API的select()和poll()函数,因为LWIP API中没有任何函数可以用来实现它们。为了实现这些函数,BSD socket实现将不得不直接与LWIP协议栈通信而不是使用API。

17.1 socket的表示

在BSD socket API中,sockets作为普通文件描述符来表示。文件描述符是能够唯一标识文件或网路连接的整数。在这里的BSD socket API实现中,sockets被一个netconn结构体在内部表示。因为BSD sockets被一个整数标识,netconn变量被保存在一个数组sockets[]中,这里,BSD socket标识符是数组中的索引。

17.2 分配一个socket

17.2.1 socket()调用

scket()调用分配一个BSD socket。scket()函数的参数用来确定被请求socket的类型。既然这里的socket API实现仅仅关系与网络sockets,因此这里的类型仅仅是被支持的socket类型。同样,仅仅UDP(SOCK_DGRAM)或者TCP(SOCK_STREAM)sockets可以使用。实现如下:       

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

int socket(int domain, int type, int protocol)

{

         struct netconn *conn;

         int i;

         /* create a new netconn */

         switch(type)

         {

         case SOCK_DGRAM:

            conn = netconn_new(NETCONN_UDP);

            break;

         case SOCK_STREAM:

            conn = netconn_new(NETCONN_TCP);

            break;

        }

        /* find an empty place in the sockets[] list */

        for(i = 0; i < sizeof(sockets); i++)

        {

         if(sockets[i] == NULL)

           {

            sockets[i] = conn;

            return i;

           }

        }

        return -1;

}

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

17.3连接建立

BSD socket API建立一个连接的调用非常类似于最小API的连接建立函数。这些调用的实现主要包括从一个socket的整数表示到最小API中使用的抽象连接的转换。

17.3.1 bind()调用

调用bind()函数绑定BSD socket到一个本地地址。在bind()调用中,本地IP地址和端口号已经被确定,这里的bind()函数非常类似于LWIP API中的netconn_bind()函数。

      

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

int bind(int s, struct sockaddr *name, int namelen)

{

    /* define the needed var */

    struct netconn *conn;

    struct ip_addr *remote_addr;

    unsigned short remote_port;

    /* the value is set */

    remote_addr = (struct ip_addr *)name->sin_addr;

    remote_port = name->sin_port;

    conn = sockets[s];

    /* call netconn_bind to bind */

    netconn_bind(conn, remote_addr, remote_port);

    return 0;

}

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

17.3.2 connect()调用

具体实现与上面类似。      

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

int connect(int s, struct sockaddr *name, int namelen)

{

    /* define the needed var */

    struct netconn *conn;

    struct ip_addr *remote_addr;

    unsigned short remote_port;

    /* the value is set */

    remote_addr = (struct ip_addr *)name->sin_addr;

    remote_port = name->sin_port;

    conn = sockets[s];

    /* call netconn_connect to connect to remote host */

    netconn_connect(conn, remote_addr, remote_port);

    return 0;

}

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

17.3.3 listen()调用

listen()函数基本上等同于LWIP API的netconn_listen()函数,并且仅仅能够用于TCP连接。唯一的不同在于BSD socket API允许应用确定待确定的连接队列(累积的)的大小。这对于LWIP不太可能,累积参数会被忽略。      

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

int listen(int s, int backlog)

{

    netconn_listen(sockets[s]);

    return 0;

}

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

17.3.4 accept()调用

accept()调用用来在一个TCP socket上等待连接进入,该socket通过之前调用listen()已经被设置为LISTEN状态。accept()会阻塞直到与远程主机的连接被建立,参数对listen而言是结果参数,通过调用accept()设置。它们由远程主机的地址来填充。

当新的连接建立后,LWIP函数netconn_accept()将返回该新连接的连接句柄。当远程主机的IP地址和端口号被填入后,一个新的socket标识符被分配并被返回。

     

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

int accept(int s, struct sockaddr *addr, int *addrlen)

{

    struct netconn *conn, *newconn;

    struct ip_addr *addr;

    unsigned short port;

    int i;

    conn = sockets[s];

    newconn = netconn_accept(conn);

    /* get the IP address and port of the remote host */

    netconn_peer(conn, &addr, &port);

    addr->sin_addr = *addr;

    addr->sin_port = port;

    /* allocate a new socket identifier */

    for(i = 0; i < sizeof(sockets); i++) {

        if(sockets[i] == NULL) {

           sockets[i] = newconn;

           return i;

        }

    }

    return -1;

}

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

17.4发送和接收数据

17.4.1 send()调用

在BSD socket API中,send()调用可以用于UDP和TCP来发送数据。在调用send()之前,数据的接收者必须已经使用connect()建立。对于UDP会话,send()调用类似于LWIP API中的netconn_send(),但是因为LWIP API要求应用明确的分配缓冲,一个缓冲在send()调用中必须分配和释放。所以,一个缓冲会被分配,数据会被复制进分配的缓冲中。

LWIP API的netconn_send()函数不能够用于TCP连接,因此send()使用netconn_write()作为TCP连接的实现方案。在BSD socket API中,在调用send()后应用程序被允许直接修改发送的数据,所以NETCONN_COPY标识被传给netconn_write()以便数据被拷贝进栈的内部缓冲中。      

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

int send(int s, void *data, int size, unsigned int flags)

{

    struct netconn *conn;
    
    struct netbuf *buf;
    
    conn = sockets[s];
    
    switch(netconn_type(conn)) {
    
    case NETCONN_UDP:
    
    /* create a buffer */
    
    buf = netbuf_new();
    
    /* make the buffer point to the data that should be sent */
    
    netbuf_ref(buf, data, size);
    
    /* send the data */
    
    netconn_send(sock->conn.udp, buf);
    
    /* deallocated the buffer */
    
    netbuf_delete(buf);
    
    break;
    
    case NETCONN_TCP:
    
    netconn_write(conn, data, size, NETCONN_COPY);
    
    break;
    
    }
    
    return size;

}

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

17.4.2 sendto()和sendmsg()调用

sendto()和sendmsg()调用类似于send()调用,但是它们允许应用程序在调用的参数中确定数据的接收者。同时,sendto()和sendmsg()仅仅能够用于UDP连接。在实现中,使用netconn_connect()来设置数据报的接收者,因此必须复位远程IP地址和端口号,如果socket在之前被连接。sendmsg()的实现没有在这里引入。      

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

int sendto(int s, void *data, int size, unsigned int flags, struct sockaddr *to, int tolen)

{

     /* var defined */
    
    struct netconn *conn;
    
    struct ip_addr *remote_addr, *addr;
    
    unsigned short remote_port, port;
    
    int ret;
    
    conn = sockets[s];
    
    /* get the peer if currently connected */
    
    netconn_peer(conn, &addr, &port);
    
    /* set remote addr and port using to argument */
    
    remote_addr = (struct ip_addr *)to->sin_addr;
    
    remote_port = to->sin_port;
    
    /* use netconn_connect to set up connection with remote host */
    
    netconn_connect(conn, remote_addr, remote_port);
    
    /* send data */
    
    ret = send(s, data, size, flags);
    
    /* reset the remote address and port number of the connection */
    
    netconn_connect(conn, addr, port);

}

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

17.4.3 write()调用

在BSD socket API中,write()调用在一个连接上发送数据,并且既可以用于UDP也可以用于TCP连接。对于TCP连接,这将直接映射到LWIP API的函数netconn_write()。对于UDP,BSD socket函数write()与send()函数等价。      

/*******************************************/
int write(int s, void *data, int size)

{

     /* var declaration */
    
    struct netconn *conn;
    
    conn = sockets[s];
    
    switch(netconn_type(conn)) {
    
    case NETCONN_UDP:
    
    send(s, data, size, 0);
    
    break;
    
    case NETCONN_TCP:
    
    netconn_write(conn, data, size, NETCONN_COPY);
    
    break;
    
    }
    
    return size;

}

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

17.4.4 recv()和read()调用

在BSD socket API中,recv()和read()调用用于在一个已连接的socket上来接收数据。它们同样可以用于TCP和UDP连接二者。许多标识通过调用recv()能够被传送。这里没有实现任何一个,标识参数被忽略。

如果接收到的消息大于提供的内存区域,超出的数据会被无情的丢弃。      

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

int recv(int s, void *mem, int len, unsigned int flags)

{

    struct netconn *conn;
    
    struct netbuf *buf;
    
    int buflen;
    
    conn = sockets[s];
    
    buf = netconn_recv(conn);
    
    buflen = netbuf_len(buf);
    
    /* copy the contents of the received buffer into the supplied memory pointer mem */
    
    netbuf_copy(buf, mem, len);
    
    netbuf_delete(buf);
    
    /* if the length of the received data is larger than len, this data is discarded and we return len.otherwise we return the actual     length of the received data */
    
    if(len > buflen) {
    
        return buflen;
    
    } else {
    
        return len;
    
    }

}

int read(int s, void *mem, int len)

{

    return recv(s, mem, len, 0);

}

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

17.4.5 recvfrom()和recvmsg()调用

Recvfrom()和recvmsg()调用类似于recv()调用,但是不同之处在于,数据发送者的IP地址和端口号通过该调用可以获得。

recvmsg()的实现不再包含。      

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

int recvfrom(int s, void *mem, int len, unsigned int flags, struct sockaddr *from, int *fromlen)

{

    /* var needed declaration */
    
    struct netconn *conn;
    
    struct netbuf *buf;
    
    struct ip_addr *addr;
    
    unsigned short port;
    
    int buflen;
    
    /* set the var */
    
    conn = sockets[s];
    
    buf = netconn_recv(conn);
    
    buflen = netbuf_len(conn);
    
    /* copy the contents of the received buffer into the supplied memory pointer */
    
    netbuf_copy(buf, mem, len);
    
    /* extract the information from the received data */
    
    addr = netbuf_fromaddr(buf);
    
    port = netbuf_fromport(buf);
    
    from->sin_addr = *addr;
    
    from->sin_port = port;
    
    *fromlen = sizeof(struct sockaddr);
    
    netbuf_delete(buf);
    
    /* if the length of the received data is larger than len, this data is discarded and we return len.otherwise we return the actual     length of the received data */
    
    if(len > buflen) {
    
        return buflen;
    
    } else {
    
        return len;
    
    }

}

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

18.代码实例

18.1使用API

这部分使用LWIP API写了一个简单的web服务器。应用程序代码在下面列出。该应用程序仅仅实现了一个HTTP/1.0[BLFF96]服务的骨干,在这里列出仅仅用于展示在一个实际的应用中使用LWIP API的原则。

该应用程序由一个单个的进程组成,该进程接收来自网络的连接,响应HTTP请求,以及关闭连接。程序中有两个函数,main()完成必需的初始化以及连接的建立,process_ connection()实现HTTP/1.0的小子集。连接建立的过程是如何使用最小API进行连接初始化的一个相当直接的例子。在使用netconn_new()创建连接后,连接就被绑定到TCP端口80并设置为LISTEN状态,在该状态中,将等待连接的到来。调用netconn_accept(),一旦远程主机已经建立将返回一个netconn连接。使用process_connection()处理完连接后,必须使用netconn_delete()来释放该netconn。

在process_connection()中,通过调用netconn_recv()一个netbuf被接收,一个指向实际请求数据的指针通过调用netbuf_data()获得。这将返回指向netbuf中的第一个分片的指针,并且我们希望它将含有该请求。因为我们仅仅读请求的开始7个字节,这并非一个不切实际的假设。如果我们想要读取更多数据,最简单的方式就是使用netbuf_copy()将请求复制进一个连续的内存,并在那里对它进行处理。

这个简单的web server仅仅响应HTTP GET请求并使用文件分隔符‘/’,当请求被确认,就发送对于它的响应。我们发送对于HTML数据以及带有两个对函数netconn_write()的调用的HTTP头。因为我们既不改变HTTP头也不改变HTML数据,所以可以在netconn_write()调用中使用NETCONN_NOCOPY标识来避免任何形式的复制。

最后,连接被关闭,函数process_connection()返回。调用之后,连接结构体就被释放。

应用程序的C代码如下:      

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

/* A simple HTTP/1.0 server using the minimal API. */

#include "api.h"

/* This is the data for the actual web page.Most compilers would place this in ROM. */

const static char indexdata[] =

" \

A test page \

 \

This is a small test page. \

 \

";



const static char http_html_hdr[] =

"Content-type: text/html\r\n\r\n";

/* This function processes an incomming connection. */

static void process_connection(struct netconn *conn)

{

    struct netbuf *inbuf;
    
    char *rq;
    
    int len;
    
    /* Read data from the connection into the netbuf inbuf.We assume that the full request is in the netbuf. */
    
    inbuf = netconn_recv(conn);
    
    /* Get the pointer to the data in the first netbuf fragment which we hope contains the request. */
    
    netbuf_data(inbuf, &rq, &len);
    
    /* Check if the request was an HTTP "GET /\r\n". */
    
    if(rq[0] == 'G' && rq[1] == 'E' &&
    
        rq[2] == 'T' && rq[3] == ' ' &&
        
        rq[4] == '/' && rq[5] == '\r' &&
        
        rq[6] == '\n') {
        
        /* Send the header. */
        
        netconn_write(conn, http_html_hdr, sizeof(http_html_hdr),
        
        NETCONN_NOCOPY);
        
        /* Send the actual web page. */
        
        netconn_write(conn, indexdata, sizeof(indexdata),NETCONN_NOCOPY);
        
        /* Close the connection. */
        
        netconn_close(conn);
    
    }//if

}//process_connection



/* The main() function. */

int main()

{

    struct netconn *conn, *newconn;
    
    /* Create a new TCP connection handle. */
    
    conn = netconn_new(NETCONN_TCP);
    
    /* Bind the connection to port 80 on any local IP address. */
    
    netconn_bind(conn, NULL, 80);
    
    /* Put the connection into LISTEN state. */
    
    netconn_listen(conn);
    
    /* Loop forever. */
    
    while(1) {
    
        /* Accept a new connection. */
        
        newconn = netconn_accept(conn);
        
        /* Process the incomming connection. */
        
        process_connection(newconn);
        
        /* Deallocate connection handle. */
        
        netconn_delete(newconn);

    }

    return 0;

}

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

18.2直接使用栈接口

由于基本的wev server机制是非常简单的,因为它仅仅接收一个请求并通过发送一个文件到远程主机来完成服务,所以可以使用基于栈接口的内部事件来很好的实现它。同样,因为它没有包含复杂的计算,TCP/IP处理不会延迟。下面的示例展示了如何来实现这样一个应用。该应用的实现非常类似于上面的例子。      

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

/* A simple HTTP/1.0 server direcly interfacing the stack. */

#include "tcp.h"

/* This is the data for the actual web page. */

static char indexdata[] =

"HTTP/1.0 200 OK\r\n\

Content-type: text/html\r\n\

\r\n\

 \

A test page \

 \

This is a small test page. \

 \

";

/* This is the callback function that is called when a TCP segment has arrived in the connection. */

static void http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p)

{

    char *rq;
    
    /* If we got a NULL pbuf in p, the remote end has closed the connection. */
    
    if(p != NULL) {
    
        /* The payload pointer in the pbuf contains the data in the TCP segment. */
        
        rq = p->payload;
        
        /* Check if the request was an HTTP "GET /\r\n". */
        
        if(rq[0] == 'G' && rq[1] == 'E' &&
        
        rq[2] == 'T' && rq[3] == ' ' &&
        
        rq[4] == '/' && rq[5] == '\r' &&
        
        rq[6] == '\n') {
        
            /* Send the web page to the remote host. A zero in the last argument means that the data should not be copied into         internal     buffers. */
            
            tcp_write(pcb, indexdata, sizeof(indexdata), 0);
    
        }
    
        /* Free the pbuf. */
    
        pbuf_free(p);

    }

    /* Close the connection. */

    tcp_close(pcb);

}

/* This is the callback function that is called when a connection has been accepted. */

static void http_accept(void *arg, struct tcp_pcb *pcb)

{

    /* Set up the function http_recv() to be called when data arrives. */
    
    tcp_recv(pcb, http_recv, NULL);

}

/* The initialization function. */

void http_init(void)

{

    struct tcp_pcb *pcb;
    
    /* Create a new TCP PCB. */
    
    pcb = tcp_pcb_new();
    
    /* Bind the PCB to TCP port 80. */
    
    tcp_bind(pcb, NULL, 80);
    
    /* Change TCP state to LISTEN. */
    
    tcp_listen(pcb);
    
    /* Set up http_accet() function to be called when a new connection arrives. */
    
    tcp_accept(pcb, http_accept, NULL);

}

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

参考

[ABM95] B. Ahlgren, M. BjÄorkman, and K. Moldeklev. The performance of a no-copy api for communication (extended abstract). In IEEE Workshop on the Architecture and Im-plementation of High Performance Communication Subsystems, Mystic, Connecticut,USA, August 1995.

[APS99] M. Allman, V. Paxson, and W. Stevens. TCP congestion control. RFC 2581, Internet Engineering Task Force, April 1999.

[BIG+97] C. Brian, P. Indra, W. Geun, J. Prescott, and T. Sakai. IEEE-802.11 wireless local area networks. IEEE Communications Magazine, 35(9):116{126, September 1997.

[BLFF96] T. Berners-Lee, R. Fielding, and H. Frystyk. Hypertext transfer protocol { HTTP/1.0.RFC 1945, Internet Engineering Task Force, May 1996.

[Cla82a] D. D. Clark. Modularity and e±ciency in protocol implementation. RFC 817, Internet Engineering Task Force, July 1982.

[Cla82b] D. D. Clark. Window and acknowledgement strategy in TCP. RFC 813, Internet

Engineering Task Force, July 1982.

[HNI+98] J. Haartsen, M. Naghshineh, J. Inouye, O. Joeressen, and W. Allen. Bluetooth: Vision,goals, and architecture. Mobile Computing and Communications Review, 2(4):38{45,October 1998.

[Jac88] V. Jacobson. Congestion avoidance and control. In Proceedings of the SIGCOMM '88Conference, Stanford, California, August 1988.

[LDP99] L. Larzon, M. Degermark, and S. Pink. UDP Lite for real-time multimedia appli-cations. In Proceedings of the IEEE International Conference of Communications,Vancouver, British Columbia, Canada, June 1999.

[MD92] Paul E. McKenney and Ken F. Dove. E±cient demultiplexing of incoming TCP

packets.In Proceedings of the SIGCOMM '92 Conference, pages 269{279, Baltimore,Maryland, August 1992.

[MK90] T. Mallory and A. Kullberg. Incremental updating of the internet checksum. RFC1141, Internet Engineering Task Force, January 1990.

[MMFR96] M. Mathis, J. Mahdavi, S. Floyd, and A. Romanow. TCP selective acknowledgmentoptions. RFC 2018, Internet Engineering Task Force, October 1996.

[Mog92] J. Mogul. Network locality at the scale of processes. ACM Transactions on ComputerSystems, 10(2):81{109, May 1992.

[Nab] M. Naberezny. The 6502 microprocessor resource. Web page. 2000-11-30.

URL: http://www.6502.org/[PP93] C. Partridge and S. Pink. A faster UDP. IEEE/ACM Transactions in Networking,1(4):429{439, August 1993.

[Rij94] A. Rijsinghani. Computation of the internet checksum via incremental update. RFC1624, Internet Engineering Task Force, May 1994.

[vB] U. von Bassewitz. cc65 - a freeware c compiler for 6502 based systems. Web page.

2000-11-30.URL: http://www.cc65.org/

[Zak83] R. Zaks. Programming the 6502. Sybex, Berkeley, California, 1983.

=====全文完=====

你可能感兴趣的:(LwIP,tcp/ip,网络,服务器)