NTT Information Sharing Platform Laboratories
Introduction
In Part 2, I discussed protocol-independent programming through the creation of an IPv6-ready TCP echo client. In Part 3, we will create an IPv6-ready TCP echo server. Our goal will be to create a program that does not depend on specific protocols, such as IPv6, IPv4, etc.
Core design of an IPv6-ready TCP server
In order to run a server that supports both IPv6 and IPv4, there are roughly two possible designs. Figure 1 shows an example of a web server. In j on the left hand side, both httpd, which accepts IPv4 connection requests, and httpd6, which accepts IPv6 connection requests, are running as independent protocols and the two processes share the contents. In k on the right hand side, a single httpd process is accepting both IPv6 and IPv4 connection requests.
Figure 1: Design for an IPv6-ready web server

Also, in order to avoid adding dependencies on a specific protocol as much as possible when creating a TCP echo server, we will use an outline that is the same as k on the right hand side of Figure 1 so that a single process can accept any connection, whether it is IPv6 or IPv4. With this design, even when a new protocol, such as IPv7, emerges in the future, we can easily support it without modifying the source code, or by making only minor modifications.
Rough outline of the server process
In order for the server to accept any connection request, whether it is IPv6 or IPv4, you need to create a socket for each protocol and wait (listen) for the connection. There was only one socket used for the TCP echo client, which I introduced in Part 2. However, the server will open two sockets at the same time, one for IPv6 and one for IPv4, and wait (listen) for a client connection. Figure 2 shows the general outline of a server program.
Figure 2: Rough process of a TCP server

The wildcard address for IPv6 is “::”, and for IPv4, it is “0.0.0.0”. If you bind these addresses to a listening socket, it can accept client requests from any interface on the server. In block (1), an ADDRINFO list is created, and the getaddrinfo( ) function is used here as well.
In Parts 1 and 2, we used it as an API for name resolution; however, when you use a wildcard address on a server, you do not have to resolve the IP address from the host name. This is because it is known that the IP address is “::” or “0.0.0.0”. So, we will need to run the getaddrinfo( ) function differently than we did in Parts 1 and 2. As shown below, do not pass the host name into the getaddrinfo( ) function (pass NULL instead), and set the hints flag to AI_PASSIVE.
hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; e = getaddrinfo(NULL, "echo", &hints, &ai0); |
Figure 3: Listing of the ADDRINFO structure created by the getaddrinfo( ) function with AI_PASSIVE flag |
ai0
|
|
↓
ADDRINFO ------------------------> ADDRINFO ----------------------> NULL
ai_family = AF_INET6 ai_family = AF_INET
ai_socktype = SOCK_STREAM ai_socktype = SOCK_STREAM
ai_protocol = IPPROTO_TCP ai_protocol = IPPROTO_TCP
ai_addr ai_addr
->SOCKADDR_IN6 ->SOCKADDR_IN
+sin6_addr = "::" +sin_addr = "0.0.0.0"
+sin6_port = 7 (echo) +sin_port = 7 (echo) |
Next, in block (3) of Figure 2, input and output are multiplexed. If there is a connection request from a client using EITHER IPv6 or IPv4, the connection is accepted. However, it does not mean that there is a rule as to which protocol will connect first. Also, an IPv4 connection request may come in while the server is accepting an IPv6 connection request. Or, multiple IPv6 connection requests may arrive at the same time. The necessary process in block (3) is multiplexing, which processes multiple IPv6 or IPv4 connection requests. For example, almost every web server can respond even when it receives multiple connection requests from browsers at the same time. Multiplexing is not a process that depends on the IPv6 protocol, and it can be implemented by using conventional methods that we have used in the past. Although there are several possible ways to do this, in the sample program we are using this time, multiplexing is implemented using the select( ) function that is often used in UNIX and Windows threading.
Listing 1 shows the source code for a TCP echo server. We will take a look at how the general outline shown in Figure 2 is implemented in the source code. • Lines 20-24. This is the initialization of WinSock. The version of WinSock used here is 2.2. |
020: /** Initialize WinSock */
021: if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
022: fprintf(stderr, "can not initilize WinSock\n");
023: exit(1);
024: }
|
|
026: /** Create wildcard address ADDRINFO list. */
027: /** If AF_UNSPEC is assigned to ai_family, */
028: /** it will create a list that has */
029: /** IPv6 and IPv4 wildcard addresses as items. */
030: memset(&hints, 0, sizeof(hints));
031: hints.ai_family = AF_UNSPEC;
032: hints.ai_socktype = SOCK_STREAM;
033: hints.ai_flags = AI_PASSIVE;
034: if (e = getaddrinfo(NULL, servname, &hints, &ai0)) {
035: fprintf(stderr, "%s\n", gai_strerror(e));
036: exit(1);
037: }
|
A socket is created in lines 46-48. Since the argument for the socket( ) function passes the variables created by the getaddrinfo( ) function as is, programmers do not have to write protocol information in the source code. In lines 52-55, a wildcard address is bound to a socket. Since socket addresses here also differ depending on the protocol (for IPv6 “::”, for IPv4 “0.0.0.0”), programmers normally write different code for each protocol, but the difference among protocols is completely hidden by ai_addr variables. In lines 61-65, the socket is put into listening mode by using the listen( ) function and listens for a client connection. If the socket completed the steps from creation to being put into listening mode, the number of sockets is incremented using the nsocks variable in line 68. Normally, nsocks is equal to the number of items in the ADDRINFO structure list; in other words, it is normally equal to the number of protocols that an OS has. |
039: /** Create a socket that accepts client connection requests */
040: /** for each item in the ADDRINFO list. */
041: /** Even after it succeeds in creating a socket, */
042: /** it does not stop and continues to create a socket for each item. */
043: for (nsocks = 0, ai = ai0; ai; ai = ai->ai_next) {
044: /* Attempt to create a socket. If creation fails, */
045: /* attempts connection with the next item in the ADDRINFO list. */
046: s[nsocks] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
047: if (s[nsocks] == INVALID_SOCKET)
048: continue;
049:
050: /* Bind socket. If binding fails, */
051: /* attempts connection with the next item in the ADDRINFO list. */
052: if (bind(s[nsocks], ai->ai_addr, ai->ai_addrlen) == SOCKET_ERROR) {
053: closesocket(s[nsocks]);
054: s[nsocks] = INVALID_SOCKET;
055: continue;
056: }
057:
058: /* Put the socket into listening mode and */
059: /* listen for a client connection. If listening fails, */
060: /* attempts connection with the next item in the ADDRINFO list. */
061: if (listen(s[nsocks], 5) == SOCKET_ERROR) {
062: closesocket(s[nsocks]);
063: s[nsocks] = INVALID_SOCKET;
064: continue;
065: }
066:
067: /* If a listening socket is created, increment the number of sockets. */
068: nsocks++;
069: |
|
076: /** If no single listening socket was created, nsocks is 0. */
077: /** Since a listening socket was not created for either IPv6 or IPv4, shut down the server. */
078: if (nsocks == 0) {
079: fprintf(stderr, "can not create listen socket with any protocol\n");
080: exit(1);
081: }
|
|
083: /** Register the listening socket that listens for a client connection in rfd0. */ 084: FD_ZERO(&rfd0); 085: for (i = 0; i < nsocks; ++i) 086: FD_SET(s[i], &rfd0); |
|
088: /** If there is a client connection request, accept it. */
089: /** Create one thread per connection and send and receive data. */
090: while (1) {
091: fd_set rfd;
092: SOCKET iosock;
093: SOCKADDR_STORAGE ss;
094: int sslen;
095:
096: /* Wait until there is a client connection */
097: /* for either the IPv4 or IPv6 listening socket. */
098: rfd = rfd0;
099: if (select(FD_SETSIZE, &rfd, NULL, NULL, NULL) == SOCKET_ERROR) {
100: fprintf(stderr, "select error\n");
101: exit(1);
102: }
.
.
.
123: }
|
|
090: while (1) {
091: fd_set rfd;
092: SOCKET iosock;
093: SOCKADDR_STORAGE ss;
094: int sslen;
.
.
104: /* Create an I/O thread that sends and receives data with clients, */
105: /* and pass the socket that received a connection request into that thread. */
106: for (i = 0; i < nsocks; ++i)
107: if (FD_ISSET(s[i], &rfd)) {
108:
109: /* Accept the connection from the client. */
110: sslen = sizeof(ss);
111: iosock = accept(s[i], (LPSOCKADDR)&ss, &sslen);
112: if(iosock == INVALID_SOCKET) {
113: fprintf(stderr, "accept error\n");
114: exit(1);
115: }
116:
117: /* Create a thread for I/O. */
118: if (_beginthread(tcp_echo_io, 0, &iosock) == -1) {
119: fprintf(stderr, "can not create thread\n");
120: exit(1);
121: }
122: }
123: }
|
|
126:/** Receives the socket that performs I/O with clients. */
127:/** The main body of the I/O thread that sends and receives data. */
128:/** The argument arg is a pointer to the socket that performs I/O. */
129:void tcp_echo_io(void *arg)
130:{
131: SOCKET s;
132: char buf[BUFSIZ];
133: int bufsiz;
134:
135: printf("start thread\n");
136:
137: /** Obtain the socket that performs I/O with clients. */
138: s = *(SOCKET *)arg;
139:
140: /** Process receipt and return of character strings from clients (sent from the server). */
141: while ((bufsiz = recv(s, buf, sizeof(buf) - 1, 0)) != 0) {
142: if (bufsiz == SOCKET_ERROR)
143: break;
144: buf[bufsiz] = '\0';
145: printf("recv string: %s\n", buf);
146:
147: if (send(s, buf, bufsiz, 0) == SOCKET_ERROR)
148: break;
149: printf("send string: %s\n", buf);
150:
151: }
152:
|
LPSOCKADDR_STORAGE ss; LPSOCKADDR_IN sin; LPSOCKADDR_IN6 sin6; if (ss->ss_family == AF_INET) sin = (SOCKADDR_IN *)ss else if (ss->ss_family == AF_INET6) sin6 = (SOCKADDR_IN6 *)ss |
We will now try pairing together and running the client program (tcp-echo-client.exe) that we created in Part 2 and the server program that we created this time around. First, following the steps shown in Figure 9, please compile tcp-echo-server.c in Listing 1. Since tcp-echo-server uses threads, you need to link a library for multi-threading. Please add “/MT” to the compiler switches. It would be ideal if you can use two different Windows XP PCs. However, you can also open two command prompts on one Windows XP machine, and then start the server in one prompt and the client in the other. In this case, give a loop back address (“::1” for IPv6, “127.0.0.1” for IPv4) to the client as the server address. Also, as for tcp-echo-server, when you start it on Windows XP SP2 for the first time, Figure 6 will be displayed. So, please tell the Windows firewall to unblock it. Figure 8 shows the tcp-echo-server and tcp-echo-client programs exchanging character strings. They are running on both IPv6 and IPv4. (see “Simple TCP/IP service”)
We created an IPv6-ready TCP echo server this time. However, we did not design it to be IPv6-specific, and we created a program that excludes protocol-dependent parts as much as possible. By creating a protocol-independent program, supporting possibly emerging new protocols will become easy, and we now know that such a program can be compactly written. In the next article, I will explain how to create a TCP echo client and server with asynchronous I/O that uses window messages, which is fundamental to Windows programming. Listing 1:tcp-echo-server.c |
001:#include ‹winsock2.h›
002:#include ‹ws2tcpip.h›
003:#include ‹stdio.h›
004:#include ‹process.h› /* for _beginthread() */
005:
006:void tcp_echo_io(void *arg);
007:
008:int main(int argc, char *argv[])
009:{
010: WSADATA wsaData;
011: int i;
012: char *servname = "echo";
013: ADDRINFO hints;
014: LPADDRINFO ai, ai0;
015: int e;
016: SOCKET s[64];
017: int nsocks;
018: fd_set rfd0;
019:
020: /** Initialize WinSock */
021: if (WSAStartup(MAKEWORD(2, 2), &wsaData)) {
022: fprintf(stderr, "can not initilize WinSock\n");
023: exit(1);
024: }
025:
026: /** Create wildcard address ADDRINFO list. */
027: /** If AF_UNSPEC is assigned to ai_family, */
028: /** it will create a list that has */
029: /** IPv6 and IPv4 wildcard addresses as items. */
030: memset(&hints, 0, sizeof(hints));
031: hints.ai_family = AF_UNSPEC;
032: hints.ai_socktype = SOCK_STREAM;
033: hints.ai_flags = AI_PASSIVE;
034: if (e = getaddrinfo(NULL, servname, &hints, &ai0)) {
035: fprintf(stderr, "%s\n", gai_strerror(e));
036: exit(1);
037: }
038:
039: /** Create a socket that accepts client connection requests */
040: /** for each item in the ADDRINFO list. */
041: /** Even after it succeeds in creating a socket, */
042: /** it does not stop and continues to create a socket for each item. */
043: for (nsocks = 0, ai = ai0; ai; ai = ai->ai_next) {
044: /* Attempt to create a socket. If creation fails, */
045: /* attempts connection with the next item in the ADDRINFO list. */
046: s[nsocks] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
047: if (s[nsocks] == INVALID_SOCKET)
048: continue;
049:
050: /* Bind socket. If binding fails, */
051: /* attempts connection with the next item in the ADDRINFO list. */
052: if (bind(s[nsocks], ai->ai_addr, ai->ai_addrlen) == SOCKET_ERROR) {
053: closesocket(s[nsocks]);
054: s[nsocks] = INVALID_SOCKET;
055: continue;
056: }
057:
058: /* Put the socket into listening mode and */
059: /* listen for a client connection. If listening fails, */
060: /* attempts connection with the next item in the ADDRINFO list. */
061: if (listen(s[nsocks], 5) == SOCKET_ERROR) {
062: closesocket(s[nsocks]);
063: s[nsocks] = INVALID_SOCKET;
064: continue;
065: }
066:
067: /* If a listening socket is created, increment the number of sockets. */
068: nsocks++;
069:
070: /* Display the protocol types of the listening sockets that were created. */
071: printf("create %s listen socket\n",
072: (ai->ai_family == AF_INET) ? "IPv4" :
073: (ai->ai_family == AF_INET6) ? "IPv6" : "Unknown");
074: }
075:
076: /** If no single listening socket was created, nsocks is 0. */
077: /** Since a listening socket was not created for either IPv6 or IPv4, shut down the server. */
078: if (nsocks == 0) {
079: fprintf(stderr, "can not create listen socket with any protocol\n");
080: exit(1);
081: }
082:
083: /** Register the listening socket that listens for a client connection in rfd0. */
084: FD_ZERO(&rfd0);
085: for (i = 0; i < nsocks; ++i)
086: FD_SET(s[i], &rfd0);
087:
088: /** If there is a client connection request, accept it. */
089: /** Create one thread per connection and send and receive data. */
090: while (1) {
091: fd_set rfd;
092: SOCKET iosock;
093: SOCKADDR_STORAGE ss;
094: int sslen;
095:
096: /* Wait until there is a client connection */
097: /* for either the IPv4 or IPv6 listening socket. */
098: rfd = rfd0;
099: if (select(FD_SETSIZE, &rfd, NULL, NULL, NULL) == SOCKET_ERROR) {
100: fprintf(stderr, "select error\n");
101: exit(1);
102: }
103:
104: /* Create an I/O thread that sends and receives data with clients, */
105: /* and pass the socket that received a connection request into that thread. */
106: for (i = 0; i < nsocks; ++i)
107: if (FD_ISSET(s[i], &rfd)) {
108:
109: /* Accept the connection from the client. */
110: sslen = sizeof(ss);
111: iosock = accept(s[i], (LPSOCKADDR)&ss, &sslen);
112: if(iosock == INVALID_SOCKET) {
113: fprintf(stderr, "accept error\n");
114: exit(1);
115: }
116:
117: /* Create a thread for I/O. */
118: if (_beginthread(tcp_echo_io, 0, &iosock) == -1) {
119: fprintf(stderr, "can not create thread\n");
120: exit(1);
121: }
122: }
123: }
124:}
125:
126:/** Receives the socket that performs I/O with clients. */
127:/** The main body of the I/O thread that sends and receives data. */
128:/** The argument arg is a pointer to the socket that performs I/O. */
129:void tcp_echo_io(void *arg)
130:{
131: SOCKET s;
132: char buf[BUFSIZ];
133: int bufsiz;
134:
135: printf("start thread\n");
136:
137: /** Obtain the socket that performs I/O with clients. */
138: s = *(SOCKET *)arg;
139:
140: /** Process receipt and return of character strings from clients (sent from the server). */
141: while ((bufsiz = recv(s, buf, sizeof(buf) - 1, 0)) != 0) {
142: if (bufsiz == SOCKET_ERROR
143: break;
144: buf[bufsiz] = '\0';
145: printf("recv string: %s\n", buf);
146:
147: if (send(s, buf, bufsiz, 0) == SOCKET_ERROR)
148: break;
149: printf("send string: %s\n", buf);
150:
151: }
152:
153: printf("end thread\n");
154:
155: /** Close the I/O socket and finish the thread. */
156: closesocket(s);
157: _endthread();
158:}
|
Figure 9: How to compile tcp-echo-server.c |
C:\>cl /MT tcp-echo-server.c ws2_32.lib Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86 Copyright (C) Microsoft Corporation 1984-2002. All rights reserved. tcp-echo-server.c Microsoft (R) Incremental Linker Version 7.10.3077 Copyright (C) Microsoft Corporation. All rights reserved. /out:tcp-echo-server.exe tcp-echo-server.obj ws2_32.lib |
|







