IPv6 Programming on Windows Tutorial Part 3

IPv6 Programming on Windows Tutorial Part 3

tags:
Junya Kato
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
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
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);


When the getaddrinfo( ) function is called with this flag, it checks the protocol types the OS has and creates a list of ADDRINFO structures based on the number of protocols.  For example, if the OS has both IPv6 and IPv4 protocol stacks, the getaddrinfo( ) function creates a list that looks like one in the Figure 3.  Please note that this is not a list of addresses that were resolved from host names; this is a list of wildcard addresses for the number of protocols that the OS has.

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)


Create a socket for each item (protocol) in the ADDRINFO structure list created in Figure 3, and wait (listen) for a connection from the client.

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.


Source code for an IPv6-ready TCP echo server

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:    }


• Lines 26-37. This is the process shown in block (1) of Figure 2. The AI_PASSIVE flag is set in the hints structure in line 33. If you call the getaddrinfo( ) function using NULL for the first argument, which is host name, in line 34, an ADDRINFO structure list is created based on what protocol types are installed on the OS. If the OS is Windows XP with IPv6 enabled, the structure shown in Figure 3 is created; if it is Windows XP without IPv6 enabled (supports only IPv4), only the second item (IPv4 related ADDRINFO) of the list in Figure 3 is returned. You can reference the top of the list by using the ai0 variable.

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:    }


•Lines 39-74. This is the process shown in block (2) of Figure 2. In this for loop, listening sockets are created for every item in the ADDRINFO structure list created in lines 26-37. If the OS has both IPv6 and IPv4 protocol stacks, listening sockets will be created for both IPv6 and IPv4. The TCP client that I introduced in Part 2 uses only the first socket in the ADDRINFO structure list that connects for communication; the server, however, creates sockets for all the items in the list.

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:


• Lines 75-81 When no single listening socket was created, since the program cannot function as a server, shut down here. Normally, it is rare for errors to occur here.

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:    }


• Lines 83-86. This part is in preparation for block (3) of Figure 2. In the sample code this time, we use the select( ) function to multiplex the connection request from the client. (see endnote) WinSock API provides features similar to those in UNIX. Here, it registers the socket that monitors the connection requests in rfd0. On Windows XP with IPv6 enabled, nsocks is normally 2. Here, two sockets, one each for IPv6 and IPv4, are registered to monitor connection requests.

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);


• Lines 88-123. This is the process shown in block (3) of Figure 2. The while statement in line 90 creates an infinite loop and the server will simply continue to accept client requests. In line 99, the select( ) function waits until one of the sockets registered in lines 83-86 becomes readable. In other words, the select( ) function waits until a client connection request comes in to either the IPv4 or IPv6 listening socket.

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:    }


• Lines 104-122. Once there is a connection request, whether it is IPv6 or IPv4, the select( ) function in line 99 returns. When it does, it records which socket became readable (which socket the connection request came to) in rfd. Using the FD_ISSET( ) macro in 107, which socket became readable is determined, and the connection request to the socket is accepted in line 111. When this happens, the value that the accept( ) function returns is the socket to use for sending and receiving data (I/O). The value is put into the iosock variable. By the way, the socket address that identifies the connecting client is stored in the second argument in line 111. In the code below, a value is stored in the SOCKADDR_STORAGE type variable created in line 93. As for the detailed meaning of the second argument, I will explain it in the z[a href="#002">About the SOCKADDR_STORAGE structurezÛÔection.

In line 118, a thread is created that actually performs the I/O and passes the send/receive data (I/O) socket value (iosock variable) in. The actual substance of the thread is the tcp_echo_io( ) function. Every time there is a client connection request, one thread is created. On UNIX, it is also possible to create a process, instead of a thread, using the fork( ) function, and multiplex the I/O.

The method of multiplexing discussed here is not dependent on the IPv6 protocol; it is a method that has been used for a long time also in IPv4-specific programming. There are no protocol-dependent statements in the code.

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:    }


• Lines 126-158. This is the main body of the thread for sending and receiving data (I/O) that was created in line 118. Every time there is a client connection request, a thread is created to do this. The process is very simple; it just receives a character string from the client (the recv( ) function in line 141) and returns the same character string as is (the send( ) function in 147).

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:


About the SOCKADDR_STORAGE structure

In the accept( ) function in line 111, a pointer to a SOCKADDR_STORAGE variable was specified in the second argument.  The sample program that I am showing this time doesn't reference the ss variable.  However, when obtaining the IP address of the connecting client, you need to reference the content of the client’s socket address.  Now, when the call to the accept( ) function in line 111 completes, the content that is set up for ss will be different depending on if s[i] is an IPv6 socket or an IPv4 socket.

Figure 4: Comparison of IPv6 and IPv4 socket addresses
Figure 4: Comparison of IPv6 and IPv4 socket addresses

Figure 4 shows a comparison between the SOCKADDR_IN structure, which stores an IPv4 socket address, and the SOCKADDR_IN6 structure, which stores an IPv6 socket address.  They differ in their internal structures, and, moreover, you can tell the SOCKADDR_IN6 structure is bigger in size.  You won’t know which connection, IPv6 or IPv4, is accepted until the accept( ) function is called, so you need to prepare a big enough buffer, which will be passed into the second argument, in order to store the SOCKADDR_IN6 structure.  If you pass in a buffer which is just big enough to store SOCKADDR_IN as the second argument of the accept( ) function and call the function, the memory space following the SOCKADDR_IN structure will be destroyed.

Figure 5: Structure of SOCKADDR_STORAGE
Figure 5: Structure of SOCKADDR_STORAGE

Now, import the SOCKADDR_STORAGE structure shown in Figure 5.  The SOCKADDR_STORAGE structure is big enough to store the entire socket address for all the protocols that the OS has.  In WinSock2, SOCKADDR_STORAGE is 128 bytes in size, so it can store the address within its space regardless of whether it is SOCKADDR_IN or SOCKADDR_IN6.  The top member of SOCKADDR_STORAGE is ss_family.  If you compare it with Figure 4, the space that shows the protocol type is the same.  As shown below, it is possible to know the type of the socket address that is stored inside of SOCKADDR_STORAGE by inspecting ss_family.  By casting the pointer to the socket address type for each protocol, it becomes possible to access the members inside.

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


If the program deals only with IPv6 and IPv4, you may think that you can just pass in the SOCKADDR_IN6 structure pointer to the accept( ) function in line 111.  If it supports ONLY IPv6 and IPv4, this works okay.  However, our goal this time is to create a protocol-independent program.  Suppose IPv7 emerges in the future and the socket address size (sizeof(SOCKADDR_IN7)) becomes 100 bytes.  Then, since the size of SOCKADDR_IN6 is only 28 bytes, memory corruption occurs.  The SOCKADDR_STORAGE structure guarantees that it stores complete socket addresses for all the protocols that the OS has, so there won’t be a problem even when a new protocol socket address emerges.


Testing the TCP echo program

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”)

Figure 6: Windows Important Security Warning
Figure 6: Windows Important Security Warning

Figure 7: Network Services screen
Figure 7: Network Services screen

Figure 8: Sample execution of the TCP echo program
Figure 8: Sample execution of the TCP echo program


Part 3 summary

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


Differences between WinSock and Berkeley sockets

It is possible to use the select( ) function, which UNIX-based OSs have, in the WinSock API as well, however, sockets are the only things that  can monitor I/O.  On UNIX, you can register both files and sockets to monitor I/O interchangeably.  However, WinSock API cannot do that.  This is because the select( ) function on Windows is considered part of the WinSock API.


Simple TCP/IP service

On Windows XP, there is a “simple TCP/IP service” feature.  (Figure 8)  Since TCP echo server features are also included in this service (IPv4 only), if you run it with the TCP echo server program that we created this time at the same time, a conflict will occur.  Please uninstall the “simple TCP/IP service” or disable it.



この記事のトラックバックURL

http://www.ipv6style.jp/trackback/630
Ads by Google