第5节 高级ColdFire TCP/IP客户端设计——Zero-Copy
推荐给好友
打印
加入收藏
更新于2009-01-11 23:58:06

        ColdFire TCP/IP 协议栈支持两种客户端/服务器设计方法。第一种方法是使用类似套接字的BSD接口,被称为mini-IP。使用mini-IP API 的客户端/服务器设计的详细资料参加AN3470的ColdFire TCP/UDP/IP协议栈和RTOS。AN3470还包含了完整的TCP/IP协议栈和如何进行配置。
        本节介绍在提高性能的情况下,怎样使用最少的 RAM 来设计一个 TCP/IP客户端。这可以通过zero-copy API直接进入TCP/IP协议栈来实现。
        代码的复杂度是在性能和资源优势间的一个权衡。使用mini-IP API 编写客户端或服务器要比使用zero-copy API简单,特别是如果你笔记熟悉BSD套接字编程。

5.1 ColdFire TCP/IP的Zero-Copy
        PACKET tcp_pktalloc(int datasize)
        通过调用 pk_alloc(datasize + headersize) 来分配一个包缓冲区。在定义宏mini_ip时,设置headersize等于54字节。因为pk_alloc请求一个比bigbufsiz大的帧,所以datasize必须比(bigbufsiz-54)小。PACKET返回的是一个指向netbuf类型结构体的指针。

        int tcp_send(M_SOCK so, PACKET pkt)
        发送一个tcp_pktalloc分配的分组。该程序将数据复制到*pkt->m_data,字节数复制到 pkt->m_len。正常返回 0,失败则返回错误代码,这些代码类型见msock.h。如果返回0,协议栈获取该分组并在发送完之后将其释放。如果返回错误信息,该程序仍然获取分组并将其返回。

        PACKET tcp_recv(M_SOCK so)
        返回套接字收到的下一个分组。 pkt->m 指向该分组数据,数据 长度在pkt->m_len 中。程序将数据处理完必须释放此分组。收到分组后返回一个指向netbuf结构体的指针,如果没收到分组并且套接字是非封锁的则返回null。

        void tcp_pktfree(PACKET p)
        释放p指向的netbuf。

        M_SOCK m_socket
        分配一个socket结构体,并默认是封锁的。正常返回一个MSOCK结构体,否则返回null。
        struct sockaddr_in
        {
                short sin_family;
               u_short sin_port;
                struct in_addr sin_addr;
                char sin_zero[8];
        };
        sockaddr_in结构体被广泛地应用于TCP/IP协议栈的API中,sin_family必须被设置为AF_INET。sin_port为16位的端口号。sin_addr为32位的IP地址。sin_zero[]未用。

        int m_close(M_SOCK so)
        关闭socket中打开的所有连接,并释放该socket。

        int m_connect(M_SOCK so, struct sockaddr_in * sin, M_CALLBACK(name))
        向服务器开始一个连接进程。m_connect函数试图连接到sockaddr_in结构体里指定的 IP 地址和端口。如果套接字被标定为封锁的,则 m_connect函数只有在 TCPTV_KEEP_INIT(在 mtcp.h 中)定义了一个超时时间才返回,该时间默认为75秒。如果套接字被m_ioctl函数标定为非封锁的,则m_connect函数返回EINPROGRESS,通过调用M_CALLBACK函数,M_CALLBACK参数是用来发送一个完全连接信号。m_connect函数返回的错误代码见msock.h文件。MSOCK是一个指向msocket结构体的指针:
        struct msocket
        {
                struct msocket * next; // queue link
                unshort lport; // IP/port describing connection, local port
                unshort fport; // far side's port
                ip_addr lhost; // local IP address
                ip_addr fhost; // far side's IP address
                struct tcpcb * tp; // back pointer to tcpcb
                struct m_sockbuf sendq; // packets queued for send, including unacked
                struct m_sockbuf rcvdq; // packets received but undelivered to app
                struct m_sockbuf oosq; // packets received out of sequence
                int error; // last error, from BSD list
                int state; // bitmask of SS_ values from sockvar.h
                int so_options; // bitmask of SO_ options from socket.h
                int linger; // linger time if SO_LINGER is set
                M_CALLBACK(callback); // socket callback routine
                NET ifp; // iface for packets when connected
                char t_template[40); // tcp header template, pointed to by tp->template
                void * app_data; // for use by socket application
        };

5.2 回调(Callback)函数
        协议栈的TCP状态机中的hooks在一定条件下调用用户函数。这些hook被称为回调。回调函数提供一种从协议栈异步获取信息的方法,概念类似于中断。
        回调函数可以从 TCP 状态机中直接调用,所以要确保回调函数不在睡眠状态且没有长的延时。回调函数应当被视为中断处理例程(ISR),虽然它并不总是在中断里被调用。
        程序在调用 m_connect 时提供了回调函数的地址。协议栈将该地址存放在socket结构体中,回调函数的声明如下:
        int wget_tcp_callback(int code, M_SOCK so, void * data)
        在调用时,回调函数会传递一个 code 说明被调用的原因,一个套接字处理(socket handle)和一个数据指针。将数据指针定为void型是因为它可以指向不同类型的数据。这要取决于所report的代码,并不是所有的代码都提供数据的。回调函数可以在表3所示的情况下被调用。
表3 回调函数代码

        5.2.1 Packet结构体
        当接收到一个分组,就会将void型data指向一个PACKET结构体,并调用回调函数,PACKET结构体属于 netbuf 型。PACKET结构体中的大部分数据都不会被应用程序用到的。两个关键要素是m_data和m_len。
                m_data——指向packet中实际的用户数据
                m_len——数据长度。
        typedef struct netbuf * PACKET;
        struct netbuf
        {
                struct netbuf * next; // 队列链表
                char * nb_buff; // raw 缓冲区的开始处
                unsigned nb_blen; // raw 缓冲区的大小
                char * nb_prot; // 协议/数据的开始处
                unsigned nb_plen; // 协议/数据的长度
                long nb_tstamp; // packet的时间戳
                struct net * net; // the interface (net) it came in on, 0-n
                ip_addr fhost; //与packet相关的IP 地址
                unsigned short type; // recever(rx) 或网络层(tx)设置IP==0800
                unsigned inuse; // use count, for cloning buffer
                unsigned flags; // PKF_ defines的 bitmask
        #ifdef MINI_TCP // Mini TCP没有mbuf wrappers, 所以:
                char * m_data; // 指向nb_buff内的TCP数据
                unsigned m_len; // m_data的长度
                struct netbuf * m_next; // sockbuf 队列链表
        #endif /* MINI_TCP */
                struct ip_socopts *soxopts; // socket 选项
        };

        5.2.2 Callback函数从Packet访问数据的实例
                PACKET pkt; // pkt is a pointer to a netbuf structure
                // pkt->m_len = Number of Bytes Received
                // pkt->m_data = Data Buffer
                for(i=0; i<pkt->m_len; i++)
                ns_printf(lpio, %c, pkt->m_data[i]);

        5.2.3 Callback函数返回值
        回调函数返回0或非0。除了M_RXDATA代码外,协议栈每次都忽略callback函数的返回功能。如果callback函数返回0,那么协议栈在callback返回时立即释放packet。
        这点是很重要的,因为如果协议栈立即释放packet,tcp_recv或m_recv函数就不能读这个packet了。如果打算在callback内直接处理数据则返回0。如果打算通过tcp_recv或m_recv来唤醒另一个任务处理数据,则返回非0值。

        5.2.4 Callback函数实例
        //*******************************************************************************
        //int wget_tcp_callback(int code, M_SOCK so, void * data)
        //
        // 该函数从TCP状态机中直接调用
        // 当该函数被调用时,code 被赋的值是基于TCP栈所处的状态的
        // data 作为void型计算,因为它可指向不同类型数据,取决于code
        //
        // M_CLOSED – 表示socket已关闭
        // so = Socket handle
        // data 指向NULL
        //
        // M_OPENERR – 未用
        //
        // M_OPENOK – 表示到foreign主机的连接已建立
        // so = Socket handle
        // data 指向 NULL
        //
        // M_TXDATA – 表示远程主机已ACK发送的数据
        // so = Socket handle
        // data指向NULL
        //
        // M_RXDATA – 表示数据已收到
        // so = Socket handle
        // data 指向 PACKET 结构体
        // 如果返回0,则协议栈释放了该分组
        // 如果返回非0,用户必须释放分组!!!!!
        //*******************************************************************************
        int wget_tcp_callback(int code, M_SOCK so, void * data)
        {
                PACKET pkt;
                int i, k;
                int e = 0;
                switch(code)
                {
                        case M_OPENERR: // 未用
                                break;

                        case M_OPENOK: //表示到foreign主机的连接已建立
                                break;

                        case M_CLOSED: // 表示socket已关闭
                                sclose = 1;
                                break;

                        case M_RXDATA: // 表示数据已收到
                                srx = EMG_HTTP_CLIENT_RX_TIME_SECS;
                                pkt = (PACKET)data;

                                // pkt->m_len = 接收到的字节数
                                // pkt->m_data = 数据缓冲区
                                // 由于要返回0,所以释放packet
                                for(i=0; i<pkt->m_len; i++)
                                {
                                        ns_printf(lpio, %c, pkt->m_data[i));
                                        k = emg_content_length_filter(pkt->m_data[i], &filter_index,&filter_length);
                                        if(k == 3) sclose = 1;
                                        if(k ==2)
                                        {
                                                if(filter_length)
                                                {
                                                       filter_length--;
                                                       if(filter_length == 0)
                                                               sclose = 1;
                                                }
                                        }
                                }
                                break;
                        case M_TXDATA: // 表示远程主机已ACK发送的数据
                                break;
                        default:
                        dtrap(); // 不是一个合法的case
                        return 0;
                }
                USE_VOID(data);
                return e;
        }

        5.2.5 利用Zero-Copy API连接到远程服务器
        使用zero-copy API 时,可以通过直接访问API提供的分组缓冲区来节省存储空间。通常是先在一个缓冲区创建数据,然后调用m_send函数发送该数据。而m_send函数必须将数据从用户缓冲区复制到分组缓冲区。所以执行该操作需要2倍的RAM空间。
        利用zero-copy API,必须分配一个分组缓冲区,然后可以将数据直接嵌入该分组。这样,在传送时就可以减少一个用户缓冲区。接收时RAM优势就取决于该应用程序了。zero-copy的更大的优势在callback函数中。由于收到一个分组就会触发中断,所以该分组很快就可以得到处理,而且缓冲区也会尽可能快地被释放掉。缓冲区释放得越快,需要的 RAM 就越少。这样就间接节省了 RAM,又直接提高了性能。
        请求连接到远程服务器的步骤:
                1、通过调用m_socket()创建一个socket。
                        emg_tcp_communication_socket= m_socket( );
                2、用IP地址和端口号初始化一个sockaddr_in结构体。
                        // Init a socket structure with server Port Number
                        emg_tcp_sin.sin_addr.s_addr = ipaddr;
                        emg_tcp_sin.sin_port = port;
                3、调用m_connect
                        // Socket is blocking.。The m_connect call blocks until it connects.
                        e = m_connect(emg_tcp_communication_socket, &emg_tcp_sin, cb);
                        if(e > 0)
                        {
                                e = EMG_HTTP_CLIENT_CONNECT_ERROR_CONNECT;                
                                m_close(emg_tcp_communication_socket);
                        }

        5.2.6 用Zero-Copy API发送数据
        用Zero-Copy API发送数据的步骤如下:
        1、利用tcp_pktalloc函数为数据分配一个足够大的分组缓冲区。
                for(i=0; i<EMG_HTTP_CLIENT_PACKET_WAIT_SEC; i++)
                {
                        pkt = tcp_pktalloc(llength);
                        if(pkt)
                                break;
                        tk_sleep(EMG_HTTP_CLIENT_TK_SLEEP_SEC);
                }
        2、将用户数据复制到该分组缓冲区中。
                // 将buff指向分组的数据字段
                buff = (unsigned char *)pkt->m_data;
                // 用url、扩展名和附加的HTTP首部构造GET请求
                temp = emgstrcpy((unsigned char *)http_get_request, buff, &buff[llength]);
        3、将数据的长度设置在分组缓冲中。
                // 将分组的长度字段设置为(int)(temp-buff)
                pkt->m_len = (unsigned int)(temp-buff);
        4、用tcp_send函数发送数据。
                // 发送数据
                emg = tcp_send(emg_tcp_communication_socket, pkt); /* 将分组传到TCP层*/
        5、如果tcp_send发送失败,则必须释放该分组。
                // 如果tcp_send返回错误,则需要释放该分组。
                if(emg)
                        tcp_pktfree(pkt);
                return(emg);


<<上一页    下一页>>
相关链接


 
关于我们 | 诚邀加盟 | 客户服务 | 相关法律 | 网站地图 | 友情链接 | 服务信箱:service@eefocus.com
© 2006 与非门科技信息咨询(北京)有限公司 All Rights Reserved.