> ## 6. Dynamic C TCP/IP Implementation

> The Dynamic C TCP/IP protocol suite is contained in a number of Dynamic C libraries. The main library file is DCRTCP.LIB. IP version 4 is supported, not version 6. This chapter will describe the configuration macros and the functions used to initialize and drive TCP/IP.

> The implementation details that are discussed here pertain to versions of Dynamic C prior to 7.05. Improvements and additions to the TCP/IP suite of protocols are fully documented in the Dynamic C TCP/IP User's Manual.

> ### Physical Connections

> The TCP/IP Development Board can be connected to your computer using a hub and standard cable or directly to the computer using a cross-over cable. The Development Board can also be a host connected directly to an Ethernet network. For details on the physical connections, please refer to the TCP/IP Getting Started Manual.

> ### 6.1 TCP/IP Configuration Macros

> TCP/IP can be configured by defining configuration macros at compile time, by using the tcp_config function (and other functions) at runtime or by using the Dynamic Host Configuration Protocol (DHCP). Some ISPs require that the user provide them with a MAC address from the controller. Run the utility program, display_mac.c to display the MAC address.

> #### 6.1.1 IP Addresses Set Manually

> Four pieces of information are needed by any host on a network:

> 1. The IP address of the host (e.g., the TCP/IP Development Board).
> 2. The part of the IP address that distinguishes machines on the host's network from machines on other networks (the netmask).
> 3. The IP address of the router that connects the host's network to the rest of the world (the default gateway).
> 4. The IP address of the local DNS server for the host's network. This is only necessary if DNS backups are needed.

> MY_IP_ADDRESS, MY_NETMASK, MY_GATEWAY and MY_NAMESERVER respectively correspond to these four critical addresses.

> #### 6.1.2 IP Addresses Set Dynamically

> The macro USE_DHCP enables the Dynamic Host Configuration Protocol (DHCP). If this option is enabled, a DHCP client (e.g., TCP/IP Development Board) contacts a DHCP server for the values of MY_IP_ADDRESS, MY_NETMASK, MY_GATEWAY and MY_NAMESERVER.

> DHCP servers are usually centrally located on a local network and operated by the network administrator.

> #### 6.1.3 Default Buffer Size

> There are two macros used to define the size of the buffer that is used for UDP datagram reads and TCP packet reads and writes: tcp_MaxBufSize and SOCK_BUF_SIZE.

> tcp_MaxBufSize is deprecated in Dynamic C version 6.57 and higher and is being kept for backwards compatibility. It has been replaced by SOCK_BUF_SIZE.

> If SOCK_BUF_SIZE is 4096 bytes, the UDP buffer is 4096 bytes, the TCP read buffer is 2048 bytes and the TCP write buffer is 2048 bytes.

> In Dynamic C versions 6.56 and earlier, tcp_MaxBufSize determines the size of the input and output buffers for TCP/IP sockets. The sizeof(tcp_Socket) will be about 200 bytes more than double tcp_MaxBufSize. The optimum value for local Ethernet connections is greater than the Maximum Segment Size (MSS). The MSS is 1460 bytes. You may want to lower tcp_MaxBufSize, which defaults to 2048 bytes, to reduce RAM usage. It can be reduced to as little as 600 bytes.

> tcp_MaxBufSize will work slightly differently in Dynamic C versions 6.57 and higher. In these later versions the buffer for the UDP socket will be tcp_MaxBufSize * 2, which is twice as large as before.

> #### 6.1.4 Delay a Connection

> Sometimes it is appropriate to accept a connection request when the resources to do so are not available. This happens with web servers when web pages have several graphic images, each requiring a separate socket.

> The macro USE_RESERVEPORTS is defined by default. It allows the use of the function tcp_reserveport(port number). When a connection to the port specified in tcp_reserveport is attempted, the 3-way handshaking is done even if there is not yet a socket available. This is done by setting the window parameter in the TCP header to zero, meaning, "I can take 0 bytes of data at this time." The other side of the connection will wait until the value in the window parameter indicates that data can be sent.

> When using tcp_reserveport, the 2MSL (for Maximum Segment Lifetime) waiting period for closing a socket is avoided.

> Using the companion function, tcp_clearreserve(port number), causes the connection to the port to be done in the conventional way.

> #### 6.1.5 Runtime Configuration

> Functions are provided to change configuration values at runtime. The most general one is tcp_config. It takes two strings. The first string is the setting to be changed and the second string is the value to change it to. The configuration macros MY_IP_ADDRESS, MY_NETMASK, MY_GATEWAY, and MY_NAMESERVER can all be overridden by this function.

>     tcp_config("MY_IP_ADDRESS","10.10.6.101");

> Some of the tcp_config functionality is duplicated by other Dynamic C TCP/IP functions. tcp_config can override the macro MY_IP_ADDRESS, and so can the sethostid function.

> ### 6.2 Skeleton Program

> The following program is a general outline for a Dynamic C TCP/IP program. The first couple of defines set up the default IP configuration information. The "memmap" line causes the program to compile as much code as it can in the extended code window. The "use" line causes the compiler to compile in the Dynamic C TCP/IP code using the configuration data provided above it.

>     #define MY_IP_ADDRESS "10.10.6.101"
>     #define MY_NETMASK "255.255.255.0"
>     #define MY_GATEWAY "10.10.6.19"
>     #memmap xmem
>     #use dcrtcp.lib

>     main() {
>         sock_init();
>         for (;;) {
>             tcp_tick(NULL);
>         }
>     }

> To run this program, start Dynamic C and open the SAMPLES\TCPIP\PINGME.C file. Edit the MY_IP_ADDRESS, MY_NETMASK, and MY_GATEWAY macros to reflect the appropriate values for your device. Run the program and try to run ping 10.10.6.101 from a command line on a computer on the same physical network, replacing 10.10.6.101 with your value for MY_IP_ADDRESS.

> The main() function first initializes the DCRTCP.LIB TCP/IP stack with a call to sock_init. This call initializes internal data structures and enables the Ethernet chip, which will take a couple of seconds with the RealTek chip. At this point, DCRTCP.LIB is ready to handle incoming packets.

> DCRTCP.LIB processes incoming packets only when tcp_tick is called. Internally, the functions tcp_open, udp_open, sock_read, sock_write, sock_close, and sock_abort all call tcp_tick. It is a good practice to make sure that tcp_tick is called periodically in your program to insure that the system has had a chance to process packets.

> When you ping your device, the Ethernet chip temporarily stores the packet, waiting for DCRTCP.LIB to process it. DCRTCP.LIB removes the packet the next time tcp_tick gets called, and responds appropriately.

> A rule of thumb is to call tcp_tick around 10 times per second, although slower or faster call rates should also work. The Ethernet interface chip has a large buffer memory, and TCP/IP is adaptive to the data rates that both end of the connection can handle; thus the system will generally keep working over a wide variety of tick rates.

> A more difficult question is how much computing time is consumed by each call to tcp_tick. Rough numbers are less than a millisecond if there is nothing to do, 10s of milliseconds for typical packet processing, and 100s of milliseconds under exceptional circumstances.

> ### 6.3 TCP Socket

> For Dynamic C version 6.57 and above, each socket must have an associated tcp_Socket of 145 bytes or a udp_Socket of 62 bytes. The I/O buffers are in extended memory.

> For earlier versions of Dynamic C, each socket must have a tcp_Socket data structure that holds the socket state and I/O buffers. These structures are, by default, around 4200 bytes each. The majority of this space is used by the input and output buffers.

> There are two ways to open a TCP socket: passive or active.

> #### 6.3.1 Passive Open

> To wait for someone to contact your device, open a socket with tcp_listen. This type of open is commonly used for Internet servers that listen on a well-known port, like 80 for HTTP. You supply tcp_listen with a pointer to a tcp_Socket data structure, the local port number others will be contacting on your device, and the IP address and port number that are valid for the device. If you want to be able to accept connections from any IP address or any port number, set one or both to zero.

> To handle multiple simultaneous connections, each new connection will require its own tcp_Socket and a separate call to tcp_listen, but using the same local port number (lport value.)

> The tcp_listen call will immediately return, and you must poll for the incoming connection. You can use the sock_wait_established macro, which will call tcp_tick and block until the connection is established or you can manually poll the socket using sock_established.

> #### 6.3.1.1 Example of Passive Open

> The following example waits for a connection on port 7, and echoes back each line as you enter it. To test this program, change the configuration information and start it running. From a connected PC, TELNET to the device port 7.

>     #define MY_IP_ADDRESS "10.10.6.101"
>     #define MY_NETMASK "255.255.255.0"
>     #define MY_GATEWAY "10.10.6.19"

>     #memmap xmem
>     #use "dcrtcp.lib"

>     #define PORT 7

>     tcp_Socket echosock;

>     main() {
>         char buffer[2048];
>         int status;

>         sock_init();

>         while(1) {
>             tcp_listen(&echosock,PORT,0,0,NULL,0);
>             sock_wait_established(&echosock,0,NULL,&status);

>             printf("Receiving incoming connection\n");
>             sock_mode(&echosock,TCP_MODE_ASCII);

>             while(tcp_tick(&echosock)) {
>                 sock_wait_input(&echosock,0,NULL,&status);
>                 if(sock_gets(&echosock,buffer,2048))
>                 sock_puts(&echosock,buffer);
>             }

>             sock_err:
>             switch(status) {
>                 case 1: /* foreign host closed */
>                 printf("User closed session\n");
>                 break;

>                 case -1: /* timeout */
>                 printf("\nConnection timed out\n");
>                 break;
>             }
>         }
>     }

> #### 6.3.2 Active Open

> When your Web browser retrieves a page, it is actively opening one or more connections to the Web server's passively opened sockets. To actively open a connection, you use the tcp_open call, which uses parameters that are similar to the tcp_listen call. It is necessary to supply exact parameters for ina and port, but the lport parameter can be zero, which tells DCRTCP.LIB to select an unused port between 1024 and 65536.

> When you call tcp_open, Dynamic C tries to contact the other device to establish the connection. The tcp_open function will fail and return a zero if the connection could not be opened due to routing difficulties, such as an inability to resolve the remote computer's hardware address with ARP.

>     #define MY_IP_ADDRESS "10.10.6.101"
>     #define MY_NETMASK "255.255.255.0"
>     #define MY_GATEWAY "10.10.6.19"
>     #define MY_NAMESERVER "209.233.102.12"

>     #define WEBSITE "www.zweng.com"
>     #define FILE "/"
>     #define PORT 80

>     #memmap xmem
>     #use "dcrtcp.lib"

>     main() {
>         int status;
>         tcp_Socket s;
>         char buffer[2048];
>         longword ip;

>         sock_init();

>         ip=resolve(WEBSITE);
>         tcp_open(&s,0,ip,PORT,NULL);
>         sock_wait_established(&s,0,NULL,&status);

>         sock_mode(&s,TCP_MODE_ASCII);

>         sprintf(buffer,"GET %s\r\n",FILE);
>         sock_puts(&s,buffer);

>         while(tcp_tick(&s)) {
>             sock_wait_input(&s,0,NULL,&status);
>             if(sock_gets(&s,buffer,2048))
>             printf("%s\n",buffer);
>         }
>         return 0;

>         sock_err:
>         switch(status) {
>             case 1: /* foreign host closed */
>             printf("User closed session\n");
>             break;

>             case -1: /* timeout */
>             printf("\nConnection timed out\n");
>             break;
>         }
>     }

> #### 6.3.3 TCP Socket Functions

> There are many functions that can be applied to an open TCP socket. They fall into three main categories: control, status, and I/O. Each function is explained in the Dynamic C TCP/IP User's Manual.

> #### 6.3.3.1 Control Functions

>     tcp_open
>     sock_close
>     sock_abort
>     sock_flush
>     sock_flushnext

> The tcp_open and tcp_listen commands have already been explained in the active and passive sections. The sock_close command should be called when you want to end a connection.

> The sock_close command may not immediately close the connection because it may take some time to send the request to end the connection and receive the acknowledgements. If you want to be sure that the connection is completely closed before continuing your program, you can call tcp_tick with the socket's address. When tcp_tick returns zero, then the socket is completely closed. Please note that if there is data left to be read on the socket, the socket will not completely close.

> There may be some reason that you want to cancel an open connection. In this case, you can call sock_abort. This function will cause a TCP reset to be sent to the other end, and other future packets sent on this connection will be ignored.

> For performance reasons, data may not be immediately sent from a socket to its destination. If your application requires the data to be sent immediately, you can call the sock_flush command. This function will cause DCRTCP.LIB to try sending any pending data immediately. If you know ahead of time that data will need to be sent immediately, call the sock_flushnext function on the socket. This function will cause the next set of data written to the socket to be sent immediately, and is more efficient than sock_flush.

> #### 6.3.3.2 Status Functions

>     tcp_tick
>     sock_tbsize
>     sock_rbsize
>     sock_tbused
>     sock_rbused
>     sock_tbleft
>     sock_rbleft
>     sock_bytesready
>     sock_established

> When you supply tcp_tick with a pointer to a TCP socket, it will first process the packets and then check to see if the socket has an established connection. It returns a zero if the socket is no longer open because of an error condition or if the socket has been closed. You can use this functionality after calling sock_close on the socket to determine whether the socket is completely closed.

>     sock_close(&my_socket);
>     while(tcp_tick(&my_socket)) {
>         // check timeout, do idle work...
>     }

> These functions can be used to avoid blocking when using sock_write and some of the other I/O functions. The following blocks of code illustrate a way of using the buffer management and socket management functions to avoid blocking. The first block of code checks to make sure that there is enough room in the buffer before adding data with a blocking function. The second makes sure that there is a string terminated with a new line in the buffer, or that the buffer is full before calling sock_gets.

>     if(sock_tbleft(&my_socket,size)) {
>         sock_write(&my_socket,buffer,size);
>     }

> or:

>     sock_mode(&my_socket,TCP_MODE_ASCII);
>     if(sock_bytesready(&my_socket) != -1) {
>         sock_gets(buffer,MAX_BUFFER);
>     }

> #### 6.3.3.3 I/O Functions

>     sock_read
>     sock_fastread
>     sock_preread
>     sock_write
>     sock_fastwrite
>     sock_getc
>     sock_gets
>     sock_putc
>     sock_puts

> There are two modes of reading and writing to TCP sockets: ASCII and binary. By default, a socket is opened in binary mode, but you can change that with a call to sock_mode.

> When a socket is in ASCII mode, DCRTCP.LIB assumes that the data is an ASCII stream with record boundaries on the newline characters for some of the functions. This behavior means sock_bytesready will return >=0 only when a complete newline-terminated string is in the buffer or the buffer is full. The sock_puts function will automatically place a newline character at the end of a string, and the sock_gets function will strip the newline character.

> When in binary mode, do not use the sock_scanf (currently not implemented) or the sock_gets functions.

> ### 6.4 UDP Interface

>     udp_open
>     sock_read
>     sock_write
>     sock_fastread
>     sock_fastwrite
>     sock_gets
>     sock_puts
>     sock_getc
>     sock_putc
>     sock_recv_init
>     sock_recv
>     sock_recv_from

> The UDP protocol is useful when sending messages where either a lost message does not cause a system failure or is handled by the application. Since UDP is a simple protocol and you have control over the retransmissions, you can decide if you can trade low latency for high reliability. Another advantage of UDP is the ability to broadcast packets to a number of computers on the same network. When done properly, broadcasts can reduce overall network traffic because information does not have to be duplicated when there are multiple destinations.

> #### 6.4.1 Opening and Closing

> The udp_open function takes a remote IP address and port number. If they are set to a specific value, all incoming and outgoing packets are filtered on that value (i.e., you talk only to the one socket).

> If the remote IP address is set to -1, it receives any packet, and outgoing packets are broadcast. If the remote IP address is set to 0, no outgoing packets may be sent until a packet has been received. This first packet completes the socket, filling in the remote IP address and port number with the return address of the incoming packet. Multiple sockets can be opened on the same local port, with the remote address set to 0, to accept multiple incoming connections from separate remote hosts. When you are done communicating on a socket that was started with a 0 IP address, you can close it with sock_close and reopen to make it ready for another source.

> #### 6.4.2 Writing

> The normal socket functions you used for writing to a TCP socket will work for a UDP socket, but since UDP is a significantly different service, the result could be different. Each atomic write--sock_putc, sock_puts, sock_write, or sock_fastwrite--places its data into a single UDP packet. Since UDP does not guarantee delivery or ordering of packets, the data received may be different either in order or content than the data sent.

> #### 6.4.3 Reading

> There are two ways to read packets using DCRTCP.LIB. The first method uses the normal sock_getc, sock_gets, sock_read, and sock_fastread functions. These functions will read the data as it came into the socket, which is not necessarily the data that was written to the socket.

> The second mode of operation for reading uses the sock_recv_init, sock_recv, and sock_recv_from functions. The sock_recv_init function installs a large buffer area that gets divided into smaller buffers. Whenever a datagram arrives, DCRTCP.LIB stuffs that datagram into one of these new buffers. The sock_recv and sock_recv_from functions scan these buffers. After calling sock_recv_init on the socket, you should not use sock_getc, sock_read, or sock_fastread.

> The sock_recv function scans the buffers for any datagrams received by that socket. If there is a datagram, the length is returned and the user buffer is filled, otherwise it returns zero.

> The sock_recv_from function works like sock_recv, but it allows you to record the IP address where the datagram originated. If you want to reply, you can open a new UDP socket with the IP address modified by sock_recv_from. There is no way to send UDP packets without a socket.

> #### 6.4.4 Checksums

> There is an optional checksum field inside the UDP header. This field verifies only the header portion of the packet and doesn't cover any part of the data. This feature can be disabled on a reliable network where the application has the ability to detect transmission errors. Disabling the UDP checksum can increase the performance of UDP packets moving through DCRTCP.LIB. This feature can be modified by:

>     sock_mode(s, UDP_MODE_CHK);    // enable checksums
>     sock_mode(s, UDP_MODE_NOCHK);  // disable checksums

> ### 6.5 Program Design

> When designing your program, you must place some thought into how it will be structured. If you plan on using the state-based approach, you need to select the appropriate functions.

> #### 6.5.1 State-Based Program Design

> One strategy for designing your program with Dynamic C is to create a state machine within a function where you pass it the socket. This method allows you to handle multiple sockets without the services of a multitasking kernel. This is the way the HTTP.LIB functions are organized (see HTTP in the Dynamic C TCP/IP User's Manual). The general states are waiting to be initialized, waiting for a connection, a bunch of connected states, and waiting for the socket to be closed. Many of the common Internet protocols fit well into this state machine model. An example of state-based programming is SAMPLES\TCPIP\STATE.C. This program is a basic Web server that should work with most browsers. It allows a single connection at a time, but could easily be extended to allow multiple connections.

> #### 6.5.2 Blocking vs. Non-Blocking

> The sock_fastread and sock_preread functions read as much data as is available in the buffers, and return immediately. Similarly, the sock_fastwrite function fills the buffers and returns the number of characters that were written. When using these functions, it is your responsibility to ensure that all of the data were written completely.

>     offset=0;
>     while(offset<length) {
>         bytes_written=sock_fastwrite(&socket,buffer+offset,lenght-offset);
>         if(bytes_written<0) {
>             // error handling
>         }
>         offset+=bytes_written;
>     }

> The other functions do not return until they have completed or there is an error. If it is important to avoid blocking, you can check the conditions of an operation to insure that it will not block.

>     sock_mode(socket,TCP_MODE_ASCII);
>     // ...
>     if (sock_bytesready(&my_socket) != -1){
>         sock_gets(buffer,MAX_BUFFER);
>     }

> In this case sock_gets will not block because it will be called only when there is a complete new line terminated record to read.

> #### 6.5.3 Blocking Macros

> To block at a certain point and wait for a condition, DCRTCP.LIB provides some macros to make this task easier. In this program fragment, sock_wait_established is used to block the program until a connection is established. Notice the timeout (second parameter) value of zero. This tells Dynamic C to never timeout. Associated with these macros is a sock_err label to jump to when there is an error. If you supply a pointer to a status integer, it will set the status to an error code. Valid error codes are -1 for timeout and 1 for a reset connection.

>     tcp_open(&s,0,ip,PORT,NULL);
>     sock_wait_established(&s,0,NULL,&status);

>     //...

>     sock_err:
>     switch(status) {
>         case 1: /* foreign host closed */
>         printf("User closed session\n");
>         break;

>         case -1: /* timeout */
>         printf("\nConnection timed out\n");
>         break;
>     }

> ### 6.6 Multitasking and TCP/IP

> The TCP/IP engine may be used with the µC/OS real-time kernel. The line

>     #use ucos2.lib

> must appear before the line

>     #use dcrtcp.lib

Z-World http://www.zworld.com Voice: (530) 757-3737 FAX: (530) 753-5141 sales@zworld.com