Sei sulla pagina 1di 29

Windows Sockets 2.

0: Write Scalable Winsock Apps Using Completion Ports SUMMARY Writing a network-aware application isn't difficult, but writing one that is scalable can be challenging. Overlapped I/O using completion ports provides true scalability on Windows NT and Windows 2000. Completion ports and Windows Sockets 2.0 can be used to design applications that will scale to thousands of connections. The article begins with a discussion of the implementation of a scalable server, discusses handling low-resource, high-demand conditions, and addresses the most common problems with scalability. earning to write network-aware applications has never been considered easy. In reality, though, there are just a few principles to mastercreating and connecting a socket, accepting a connection, and sending and receiving data. The real difficulty is writing network applications that scale from a single connection to many thousands of connections. This article will discuss the development of scalable Windows NT and Windows 2000-based applications that use Windows Sockets 2.0 (Winsock2). The primary focus will be the server side of the client/ server model; however, many of the topics discussed apply to both. Because the notion of writing a scalable Winsock application implies a server application, the following discussion is pertinent to applications running on Windows NT 4.0 and Windows 2000. We're not including Windows NT 3.x because this solution relies on the features of Winsock2 that are available only on Windows NT 4.0 and newer. APIs and Scalability The overlapped I/O mechanism in Win32 allows an application to initiate an operation and receive notification of its completion later. This is especially useful for operations that take a long time to complete. The thread that initiates the overlapped operation is then free to do other things while the overlapped request completes behind the scenes. The only I/O model that provides true scalability on Windows NT and Windows 2000 is overlapped I/O using completion ports for notification. Mechanisms like the WSAAsyncSelect and select functions are provided for easier porting from Windows 3.1 and Unix respectively, but are not designed to scale. The completion port mechanism is optimized for the operating system's internal workings. Completion Ports A completion port is a queue into which the operating system puts notifications of completed overlapped I/O requests. Once the operation completes, a notification is sent to a worker thread that can process the result. A socket may be associated with a completion port at any point after creation. Typically an application will also create a number of worker threads to process these notifications. The number of worker threads depends on the specific needs of the application. The ideal number is one per processor, but that implies that none of these threads should execute a blocking operation such as a synchronous read/write or a wait on an event. Each thread is given a certain amount of CPU time, known as the quantum, for which it can execute before another thread is allowed to grab a time slice. If a thread performs a blocking operation, the operating system will throw away its unused time slice and let other threads execute instead. Thus, the first thread has not fully utilized its quantum, and the application should therefore have other threads ready to run and utilize that time slot.

Using a completion port is a two-step process. First, the completion port is created, as shown in the following code: HANDLE hIocp; hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)0, 0); if (hIocp == NULL) { // Error } Once the completion port is created, each socket that wants to use the completion port must be associated with it. This is done by calling CreateIoCompletionPort again, this time setting the first parameter, FileHandle, to the socket handle to be associated, and setting ExistingCompletionPort to the handle of the completion port you just created. The following code creates a socket and associates it with the completion port created earlier: SOCKET s; s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { // Error if (CreateIoCompletionPort((HANDLE)s, hIocp, (ULONG_PTR)0, 0) == NULL) { // Error } } At this point, the socket s is associated with the completion port. Any overlapped operations performed on the socket will use the completion port for notification. Note that the third parameter of CreateIoCompletionPort allows a completion key to be specified along with the socket handle to be associated. This can be used to pass context information that is associated with the socket. Each time a completion notification arrives, this context information can be retrieved. Once the completion port has been created and sockets have been associated with it, one or more threads are needed to process the completion notifications. Each thread will sit in a loop that calls GetQueuedCompletionStatus each time through and returns completion notifications. Before illustrating what a typical worker thread looks like, we need to address the ways in which an application keeps track of its overlapped operations. When an overlapped call is made, a pointer to an overlapped structure is passed as a parameter. GetQueuedCompletionStatus will return the same pointer when the operation completes. With this structure alone, however, an application can't tell which operation just completed. In order to keep track of the operations that have completed, it's useful to define your own OVERLAPPED structure that contains any extra information about each operation queued to the completion port (see Figure 1).

Whenever an overlapped operation is performed, an OVERLAPPEDPLUS structure is passed as the lpOverlapped parameter (as in WSASend, WSARecv, and so on). This allows you to set operation state information for each overlapped call. When the operation completes, the OVERLAPPED pointer returned from GetQueuedCompletionStatus will now point to your extended structure. Note that the OVERLAPPED field within the extended structure does not necessarily have to be the first field. After the pointer to the OVERLAPPED structure is returned, the CONTAINING_RECORD macro can be used to obtain a pointer to the extended structure. Take a look at the example worker thread in Figure 2. The PerHandleKey variable will return anything that was passed as the CompletionKey parameter to CreateIoCompletionPort when associating a given socket handle. The Overlap parameter returns a pointer to the OVERLAPPEDPLUS structure that is used to initiate the overlapped operation. Keep in mind that if an overlapped operation fails immediately (that is, returns SOCKET_ERROR and the error is not WSA_IO_PENDING), then no completion notification will be posted to the queue. Alternately, if the overlapped call succeeds or fails with WSA_IO_PENDING, a completion event will always be posted to the completion port. For more information on using completion ports with Winsock, take a look at the Microsoft Platform SDK, which includes a Winsock completion port sample (under the Winsock section in the iocp directory). Additionally, consult Network Programming for Microsoft Windows by Anthony Jones and Jim Ohlund (Microsoft Press, 1999), which includes samples for completion ports as well as the other I/O models. The Windows NT and Windows 2000 Sockets Architecture A basic understanding of the sockets architecture of Windows NT and Windows 2000 is helpful in fully comprehending the principles of scalability. Figure 3 illustrates the current implementation of Winsock in Windows 2000. An application should not depend on the specific details mentioned here (names of drivers, DLLs, and so on), as these may change in a future release of the operating system. Figure 3 Socket Architecture The Windows Sockets 2.0 specification allows for a variety of protocols and their related providers. These user-mode service providers can be layered on top of existing providers in order to extend their functionality. For example, a proxy layered service provider (LSP) may install itself on top of the existing TCP/IP provider. This allows the proxy LSP to intercept and redirect or log calls to the base provider. Unlike some other operating systems, the Windows NT and Windows 2000 transport protocols do not have a sockets-style interface which applications can use to talk to them directly. Instead, they implement a much more general API called the Transport Driver Interface (TDI). The generality of this API keeps the subsystems of Windows NT from being tied to a particular flavor-of-the-decade network programming interface. The Winsock kernel mode driver provides the sockets emulation (currently implemented in AFD.SYS). This driver is responsible for the connection and buffer management needed to provide a sockets-style interface to an application. AFD.SYS, in turn, uses TDI to talk to the transport protocol driver.

Who Manages the Buffers? As just mentioned, AFD.SYS handles buffer management for applications that use Winsock to talk to the transport protocol drivers. This means that when an application calls the send or WSASend function to send data, the data gets copied by AFD.SYS to its internal buffers (up to the SO_SNDBUF setting) and the send or WSASend function returns immediately. The data is then sent by AFD.SYS behind the application's back, so to speak. Of course, if the application wants to issue a send for a buffer larger than the SO_SNDBUF setting, the WSASend call blocks until all the data is sent. Similarly, on receiving data from the remote client, AFD.SYS will copy the data to its own buffers as long as there is no outstanding data to receive from the application, and as long as the SO_RCVBUF setting is not exceeded. When the application calls recv or WSARecv, the data is copied from AFD.SYS's buffers to the applicationprovided buffer. In most cases, this architecture works very well. This is especially true for applications that use traditional socket paradigms with nonoverlapped sends and receives. Before going apoplectic over the buffer copying that's involved in sending and receiving data, a programmer should take great care to understand the consequences of turning off the buffering in AFD.SYS, which can be done by setting the SO_SNDBUF and SO_RCVBUF values to 0 using the setsockopt API. Consider, for example, an application that turns off buffering by setting SO_SNDBUF to 0 and issues a blocking send. In this case, the application's buffer is locked into memory by the kernel and the send API does not return until the other end of the connection acknowledges the entire buffer. That may seem like a neat way to determine whether all your data has actually been received by the other side, but in fact it is a bad thing to do. For one thing, even acknowledgment by the remote TCP is no guarantee that the data will be delivered to the client application, as there may be out-of-resource conditions that prevent it from copying the data from AFD.SYS. An even more significant problem with this approach is that your application can only do one send at a time in each thread. This is extremely inefficient, to say the least. Turning off receive buffering in AFD.SYS by setting SO_RCVBUF to 0 offers no real performance gains. Setting the receive buffer to 0 forces received data to be buffered at a lower layer than Winsock. Again, this leads to buffer copying when you actually post a receive, which defeats your purpose in turning off AFD's buffering. It should be clear by now that turning off buffering is a really bad idea for most applications. Turning off receive buffering is not usually necessary, as long as the application takes care to always have a few overlapped WSARecvs outstanding on a connection. The availability of posted application buffers removes the need for AFD to buffer incoming data. However, a high-performance server application can turn off the send buffering, yet not lose performance. Such an application must, however, take great care to ensure that it posts multiple overlapped sends, instead of waiting for one overlapped send to complete before posting another. If the application posts overlapped sends in a sequential manner, it wastes the time window between one send completion and the posting of the next send. If it had another buffer already posted, the transport would be able to use that buffer immediately and not wait for the application's next send operation. Resource Constraints

A major design goal of any server application is robustness. That is, you want your server application to ride out any transient problems that might occur, such as a spike in the number of client requests, temporary lack of available memory, or other relatively short-lived phenomena. To handle these incidents gracefully, the application developer should be aware of the resource constraints on typical Windows NT and Windows 2000-based systems. The most basic resource that you have direct control over is the bandwidth of the network on which the application is sending data. It's a fair assumption that an application that uses the User Datagram Protocol (UDP) is probably already aware of this limitation, since such a server would want to minimize packet loss. However, even with TCP connections, a server should take great care to never overrun the network for extended periods of time. Otherwise, there will be a lot of retransmissions and aborted connections. The specifics of the bandwidth management method are application-dependent and are beyond the scope of this article. Virtual memory used by the application also needs careful management. Conservative memory allocations and frees, perhaps using lookaside lists (a cache) to reuse previous allocations, will keep the server application's footprint smaller and allow the system to keep more of the application address space in memory all the time. An application can also use the SetWorkingSetSize Win32 API to increase the amount of physical memory the operating system will let it use. There are two other resource constraints that an application indirectly encounters when using Winsock. The first one is the locked page limit. Whenever an application posts a send or receive, and AFD.SYS's buffering is disabled, all pages in the buffer are locked into physical memory. They need to be locked because the memory will be accessed by kernel-mode drivers and cannot be paged out for the duration of the access. This would not be a problem in most circumstances, except that the operating system must make sure that there is always some pageable memory available to other applications. The goal is to prevent an ill-behaved application from locking up all of the physical RAM and bringing down the system. This means that your application must be conscious of hitting a system-defined limit on the number of pages locked in memory. The limit on locked memory in Windows NT and Windows 2000 is about one-eighth the physical RAM for all applications combined. This is a rough estimate and should not be used as an exact figure on which to base calculations. Just be aware that an overlapped operation may occasionally fail with ERROR_INSUFFICIENT_RESOURCES, and this limitation is a likely cause if there are too many send/receives pending. The application should take care not to have an excessive amount of memory locked in this fashion. Also note that all pages containing your buffer(s) will be locked, so it pays to have buffers that are aligned on page boundaries. The other resource limitation that an application will run into somewhere in its lifetime is the system non-paged pool limit. The Windows NT and Windows 2000 drivers have the ability to allocate memory from a special non-paged pool. The memory allocated from this region is never paged out. It is intended to store information that can be accessed by various kernel-mode components, some of which may not be able to access a location in memory that is paged out. Whenever an application creates a socket (or opens a file, for that matter), some amount of nonpaged pool is allocated. In addition, the act of binding and/or connecting a socket also results in additional non-paged pool allocations. Add to this the fact that an outstanding I/O request, such

as a send or a receive, allocates a little more non-paged pool (a small structure is required to keep track of pending I/O operations), and you can see that eventually there will be a problem. The operating system therefore limits the amount of nonpageable memory. The exact amount of non-paged pool allocated per connection is different for Windows NT 4.0 and Windows 2000 and will likely be different again for future versions of Windows. In the interests of your application's longevity, you should not calculate the exact amount of non-paged pool you need. However, the application must take care to avoid hitting the non-paged limit. When the system runs low on non-paged pool memory, you expose yourself to the risk that some driver that's completely unrelated to your application will throw a fit because it cannot allocate a non-paged pool at that particular time. In the worst case, this can lead to a system crash. This is especially likely (but impossible to predict in advance) in the presence of third-party devices and drivers on a system. You must also remember that there might be other server applications running on the same machine that consume non-paged pool memory. It is best to be very conservative in your resource estimation, and design the application accordingly. Handling the resource constraints is complicated by the fact that there is no special error code returned when either of the conditions is encountered. The application will get generic WSAENOBUFS or ERROR_INSUFFICIENT_RESOURCES errors from various calls. To handle these errors, first increase the working set of the application to some reasonable maximum. (For more information on adjusting your working set, see the Bugslayer column by John Robbins in this issue of MSDN Magazine.) Then, if you still continue to get these errors, check the possibility that you may be exceeding the bandwidth of the medium. Once you have done that, make sure you don't have too many send or receives outstanding. Finally, if you still receive out-ofresource errors, you're most probably running into non-paged pool limits. To free up a non-paged pool, the application must close a good portion of its outstanding connections and wait for the transient situation to correct itself. Accepting Connections One of the most common things a server does is accept connections from clients. The AcceptEx function is the only Winsock API capable of using overlapped I/O to accept connections on a socket. The interesting thing about AcceptEx is that it requires an additional socket as one of the parameters to the API. In a normal, synchronous accept function call, the new socket is the return value from the API. However, since AcceptEx is an overlapped operation, the accepted socket must be created (but not bound or connected) in advance, and passed to the API. A typical psuedocode snippet that uses AcceptEx might look like the following: do { -Wait for a previous AcceptEx to complete -Create a new socket and associate it with the completion port -Allocate context structure etc. -Post an AcceptEx request. }while(TRUE); A responsive server must always have enough AcceptEx calls outstanding so that any client connection can be immediately handled. The number of posted AcceptEx operations will depend on the type of traffic your server expects. A high incoming connection rate (because of short-lived connections or spurts in traffic) requires more outstanding AcceptEx calls than an application where the clients connect infrequently. It may be wise to let the number of posted AcceptEx operations vary between

application-specific low and high watermarks, and avoid deciding on one fixed number as the magic figure. On Windows 2000, Winsock provides some help in determining if the application is falling behind on posting AcceptEx requests. When creating the listening socket, associate it with an event by using the WSAEventSelect API and registering for an FD_ACCEPT notification. If there are no accept operations pending, the event will be signaled by an incoming connection. This event can thus be used as an indication that you need to post more AcceptEx requests or detect a possible misbehaving remote entity, as we'll describe shortly. This mechanism is not available on Windows NT 4.0. A significant benefit to using the AcceptEx call is the ability to receive data and accept a client connection in one call via the lpOutputBuffer parameter. This means that if a client connects and immediately sends data, AcceptEx will complete only after the connection is established and the client sends data. This can be very useful, but it can also lead to problems since the AcceptEx call will not return until data is received, even if a connection has been established. This is because an AcceptEx call with an output buffer is not one atomic operation, but a two-step process consisting of accepting a connection and waiting for incoming data. However, the application is not notified that a connection has been accepted before data is received. That means a client could connect to your server and not send any data. With enough of these connections, your server will start to refuse connections to legitimate clients because it has no more accepts pending. This is a common method of waging a denial of service attack. To prevent malicious attacks or stale connections, the accepting thread should occasionally check the sockets outstanding in AcceptEx by calling getsockopt and SO_CONNECT_TIME. The option value is set to the length of time the socket has been connected for, or -1 if it is still unconnected. The WSAEventSelect feature serves as an excellent indicator that the sockets that are outstanding in AcceptEx need their connection times checked. Any connections that have existed for a while without receiving data from the client should be terminated by closing the socket supplied to AcceptEx. An application should not, under most noncritical circumstances, close a socket that is outstanding in AcceptEx but not yet connected. For performance reasons, the kernel-mode data structures created for and associated with such an AcceptEx request will not be cleaned up until a new connection comes in or the listening socket itself is closed. It may seem that the logical thread to post AcceptEx requests is one of the worker threads that is associated with the completion port and involved in processing other I/O completion notifications. However, recall that a worker thread should not execute a blocking or high-latency system call if such an action can be avoided. One of the side effects of the layered architecture of Winsock2 is that the overhead to a socket or WSASocket API call may be significant. Every AcceptEx call requires the creation of a new socket, so it is best to have a separate thread that posts AcceptEx and is not involved in other I/O processing. You may also choose to use this thread for performing other tasks such as event logging. One last thing to note about AcceptEx is that a Winsock2 implementation from another vendor is not required to implement these APIs. This also applies to the other APIs that are specific to Microsoft, such as TransmitFile, GetAcceptExSockAddrs, and any others that Microsoft may add in a later version of Windows. On systems running Windows NT and Windows 2000, these APIs are implemented in the Microsoft provider DLL (mswsock.dll), and can be invoked by linking with

mswsock.lib, or dynamically loading the function pointers via WSAIoctl SIO_GET_EXTENSION_FUNCTION_POINTER. Calling the function without previously obtaining a function pointer (that is, by linking with mswsock.lib and calling AcceptEx directly) is costly because AcceptEx sits outside the layered architecture of Winsock2. AcceptEx must request a function pointer using WSAIoctl for every call on the off chance that the application is actually trying to invoke AcceptEx from a provider layered on top of mswsock (see Figure 3). To avoid this significant performance penalty on each call, an application that intends to use these APIs should obtain the pointers to these functions directly from the layered provider by calling WSAIoctl. TransmitFile and TransmitPackets Winsock offers two functions for transmitting data that are optimized for file and memory transfers. The TransmitFile API is present on both Windows NT 4.0 and Windows 2000, while TransmitPackets is a new Microsoft extension function that is expected to be available in a future release of Windows. TransmitFile allows the contents of a file to be transferred on a socket. Normally, if an application were to send the contents of a file over a socket, it would have to call CreateFile to open the file and then loop on ReadFile and WSASend until the entire file was read. This is very inefficient because each ReadFile and WSASend call requires a transition from user mode to kernel-mode. TransmitFile simply requires an open handle to the file to transmit and the number of bytes to transfer. The overhead is incurred when opening the file via CreateFile, followed by a single kernel-mode transition. If your app sends the contents of files over sockets, this is the API to use. The TransmitPackets API takes the TransmitFile API a step further by allowing the caller to specify multiple file handles and memory buffers to be transmitted in a single call. The function prototype looks like this: BOOL TransmitPackets( SOCKET hSocket, LPTRANSMIT_PACKET_ELEMENT lpPacketArray, DWORD nElementCount, DWORD nSendSize, LPOVERLAPPED lpOverlapped, DWORD dwFlags ); The lpPacketArray is an array of structures. Each entry can specify either a file handle or a memory buffer to be transmitted. The structure is defined as: typedef struct _TRANSMIT_PACKETS_ELEMENT { DWORD dwElFlags; DWORD cLength; union { struct { LARGE_INTEGER nFileOffset; HANDLE hFile; }; PVOID pBuffer; }; } TRANSMIT_FILE_BUFFERS; The fields are self explanatory. The dwElFlags field identifies whether the current element specifies a file handle or memory buffer via the constants

TF_ELEMENT_FILE and TF_ELEMENT_MEMORY. The cLength field dictates how many bytes to send from the given data source (a zero indicates the entire file in the case of a file element). The unnamed union then contains the memory buffer of file handle (and possible offset) of the data to be sent. Another benefit of using these two APIs is that you can reuse the socket handle by specifying the TF_REUSE_SOCKET flag in addition to the TF_DISCONNECT flag. Once the API completes the data transfer, a transport-level disconnect is initiated. The socket can then be reused in an AcceptEx call. Using this optimization would lessen the overhead associated with creating sockets in the separate accept thread, as discussed earlier. The only caveat of using either of these two extension APIs is that on Windows NT Workstation or Windows 2000 Professional only two requests will be processed at a time. You must be running on Windows NT or Windows 2000 Server, Windows 2000 Advanced Server, or Windows 2000 Data Center to get full usage of these specialized APIs. Putting it Together In the preceding sections, we covered the APIs and methods necessary for highperformance, scalable applications, as well as the resource bottlenecks that may be encountered. What does this mean to you? Well, that depends on how your server and client are structured. The more control you have over the design of both the client and server, the better you can avoid bottlenecks. Let's look at a sample scenario. In this situation we'll design a server that handles clients that connect, send a request, receive data from the server, and then disconnect. In this situation, the server will create a listening socket and associate it with a completion port, creating a worker thread for each CPU. Another thread will post the AcceptEx calls. Since you know the client will connect and immediately send data, supplying a receive buffer can make things substantially easier. Of course, you shouldn't forget to occasionally poll the client sockets used in the AcceptEx calls, using the SO_CONNECT_TIME option to make sure there are no stale connections. An important issue in this design is to determine how many outstanding AcceptEx calls are allowed. Because a receive buffer is being posted with each accept call, a significant number of pages could be locked in memory. (Remember each overlapped operation consumes a small portion of non-paged pool and also locks any data buffers into memory.) There is no real answer or concrete formula for determining how many accept calls should be allowed. The best solution is to make this number tunable so that performance tests may be run to determine the best value for the typical environment that the server will be running in. Now that you have determined how the server will accept connections, the next step is sending data. An important factor in deciding how to send data is the number of concurrent connections you expect the server to handle. In general, the server should limit the number of concurrent connections, as well as the number of outstanding send calls. More established connections mean more non-paged pool usage. The number of concurrent send calls should be limited to prevent reaching the locked pages limit. Again, both of these limits should be tunable. In this situation it is not necessary to disable the per-socket receive buffers since the only receive that occurs is in AcceptEx call. Of course it wouldn't hurt for you to guarantee that each connection has a receive buffer posted. Now, if the client/server interaction changes so that the client sends additional data after the initial request, disabling the receive buffer would be a bad idea unless, in order to receive these

additional requests, you guarantee that an overlapped receive is posted on each connection. Conclusion Developing a scalable Winsock server is not terribly difficult. It's a matter of setting up a listening socket, accepting connections, and making overlapped send and receive calls. The main challenge lies in managing resources by placing limits on the number of outstanding overlapped calls so that the non-paged pool is not exhausted. Following the guidelines we covered here will allow you to create high-performance, scalable server applications. I/O Completion Ports I/O completion ports are the mechanism by which an application uses a pool of threads that was created when the application was started to process asynchronous I/O requests. These threads are created for the sole purpose of processing I/O requests. Applications that process many concurrent asynchronous I/O requests can do so more quickly and efficiently by using I/O completion ports than by using creating threads at the time of the I/O request. The CreateIoCompletionPort function associates an I/O completion port with one or more file handles. When an asynchronous I/O operation started on a file handle associated with a completion port is completed, an I/O completion packet is queued to the port. This can be used to combine the synchronization point for multiple file handles into a single object. A thread uses the GetQueuedCompletionStatus function to wait for a completion packet to be queued to the completion port, rather than waiting directly for the asynchronous I/O to complete. Threads that block their execution on a completion port are released in last-in-first-out (LIFO) order. This means that when a completion packet is queued to the completion port, the system releases the last thread to block its execution on the port. When a thread calls GetQueuedCompletionStatus, it is associated with the specified completion port until it exits, specifies a different completion port, or frees the completion port. A thread can be associated with at most one completion port. The most important property of a completion port is the concurrency value. The concurrency value of a completion port is specified when the completion port is created. This value limits the number of runnable threads associated with the completion port. When the total number of runnable threads associated with the completion port reaches the concurrency value, the system blocks the execution of any subsequent threads that specify the completion port until the number of runnable threads associated with the completion port drops below the concurrency value. The most efficient scenario occurs when there are completion packets waiting in the queue, but no waits can be satisfied because the port has reached its concurrency limit. In this case, when a running thread calls GetQueuedCompletionStatus, it will immediately pick up the queued completion packet. No context switches will occur,

because the running thread is continually picking up completion packets and the other threads are unable to run. The best value to pick for the concurrency value is the number of CPUs on the computer. If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run. Each transaction will take longer to complete, but more transactions will be processed at the same time. It is easy to experiment with the concurrency value to achieve the best effect for your application. The PostQueuedCompletionStatus function allows an application to queue its own special-purpose I/O completion packets to the completion port without starting an asynchronous I/O operation. This is useful for notifying worker threads of external events. The completion port is freed when there are no more references to it. The completion port handle and every file handle associated with the completion port reference the completion port. All the handles must be closed to free the completion port. To close the port handle, call the CloseHandle function.

API Series -> Winsock API 1: Sockets (Page 1) Sockets: We start today with the most basic brick of networking - the socket. If you've done any Winsock programming before, you'll know that it all revolves around the idea of a "socket". If you have this handle, then you can perform network I/O on the socket with the relevant Winsock functions (discussed later). A socket is a handle to a low-level transport provider. These providers allow access to the network through a specified protocol. Many networks and the internet (which is a giant network) run using the Internet Protocol (IP). The current version of IP is 4. Version 6 has yet to become widely used, although it is available. It's usage is significantly different though - particularly in terms of addressing. IPv4 addresses look like this: 127.0.0.1 Whereas IPv6 addresses look like this: 21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A Winsock does provide functions to handle both types of addressing schemes, although IPv6 is only available in Winsock 2. As such (and because IPv4 is much more common), this series of articles will focus on IPv4 as the base protocol. Two very commonly used protocols in the Windows networking environment that run over IP are the Transport Control Protocol (TCP) and the User Datagram Protocol (UDP). We will cover both in coming chapters. TCP and UDP: There is a slight difference between TCP and UDP. Firstly, TCP is a connection-based protocol, whereas UDP is not. A connection is simply a virtual circuit between the two hosts. An agreement is reached that they will stay in this "connected" state until otherwise specified. Whilst in this state, TCP makes sure that any data is exchanged is received properly on the other side. If an error occurs, the data is re-sent. UDP is not connection based. As such, it cannot gurantee proper data delivery as TCP can. However, this does mean the protocol and thus data transfer is faster, since there is less error checking overhead. It also means that no connection need be established before sending or receiving - simply wait, and data properly addressed will be delivered to you. Each of the protocols has it's advantages, so which one to use should be made in a case by case basis. Next, we will go about creating sockets that will use TCP or UDP.

API Series -> Winsock API 1: Sockets (Page 2) The Winsock DLL: Before calling any Winsock functions you have to initialize the Winsock DLL. If you don't, any functions you call will fail, and return the WSAENOTINITIALIZED error. It is a very simple process, and cannot be avoided. It is done through a single function - WSAStartup(): Public Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequested As Integer, ByRef lpWSAData As WSADATA) As Long As mentioned before, Winsock comes in two versions - 1.1 and 2.2. At the time of writing, Winsock 2 is supported on all platforms except Windows95. However, it can be installed as a Windows update. Since Winsock 2 contains performance improvements, bug fixes and updated functionality, we will be using it throughout the series. If you want to use Winsock 1, just change the requested version. The wVersionRequested parameter is the Winsock version you wish to load. The highword is the major version, and the low-word is the minor version. The lpWSAData parameter is a WSADATA structure, defined like this: Public Type WSADATA wVersion As Integer wHighVersion As Integer szDescription(WSADESCRIPTION_LEN + 1) As Byte szSystemstatus(WSASYS_STATUS_LEN + 1) As Byte iMaxSockets As Integer iMaxUpdDg As Integer lpVendorInfo As Long End Type vVersion and wHighVersion indicate the version of Winsock that was loaded. szDescription is a byte array containing a description of the Winsock version, and szSytemStatus is a byte array containing a description of the Winsock status (normally "Running"). If WSAStartup() fails, it will return a Winsock error code which can be used to determine why. The most likely response is WSAVERNOTSUPPORTED - the requested version is not supported. If this does occur, call WSAStartup() again with a lower version. Once you've finished dealing with Winsock, the DLL needs to be released. This is very simple, just call WSACleanup(): Public Declare Function WSACleanup Lib "ws2_32.dll" () As Long Before we go any further, we should mention Winsock error handling. All Winsock functions return a VB Long value, and this should normally be ERROR_SUCCESS(0). In the case the function failed (for whatever reason), the return value will be SOCKET_ERROR(-1). If this does happen, WSAGetLastError() should be called to get a more detailed error code. I have included a helper function in

mWinsock2.bas called vbGetLastError(), and it returns a string describing the error rather than a numeric code. WSAGetLastError() is defined below: Public Declare Function WSAGetLastError Lib "ws2_32.dll" () As Long You cannot call WSAGetLastError() if WSAStartup() fails. Since the Winsock DLL was never initialized, you cannot call functions from within it. WSAGetLastError() also has a counterpart, WSASetLastError(). This is just in case you want to set or clear the last error code manually, but you probably won't need it. Public Declare Function WSASetLastError Lib "ws2_32.dll" (ByVal err As Long) As Long API Series -> Winsock API 1: Sockets (Page 3) Sockets part 2: Once the Winsock DLL has been loaded, you can start creating sockets. Each socket has an addressing family, a type and a protocol. These properties define how the socket works, and what options are available to it. To create a socket you must call socket(), defined as such: Public Declare Function socket Lib "ws2_32.dll" (ByVal af As Long, ByVal type As Long, ByVal protocol As Long) As Long This API tells the Winsock library to search through it's catalog of available providers and find one that matches our parameters. Once found, a socket is created and from that provider, and the handle returned. The parameters are described below: af is the addressing family to use and since we're using IPv4, pass in AF_INET(2). If you wanted to use IPv6, pass in AF_INET6(23). type is also fairly easy - there are a few different types, and they are closely related to the address family and protocol. They all refer to the way data is handled on the socket, so TCP sockets are of type SOCK_STREAM(1) (connection based); UDP sockets are of type SOCK_DGRAM(2) (non connection based). There is a third type - SOCK_RAW(3) which is what it says. This type of socket allows access to the underlying protocols, and allows you to send custom built packets manually, and also to intercept the protocol headers of incoming data. Raw sockets will not be covered. protocol is there to distinguish a particular Winsock provider if the first two parameters match more than one provider. When creating TCP sockets, pass in IPPROTO_TCP(6). For UDP, pass IPPROTO_UDP(17). If socket() is called successfully, you will be returned an integer socket handle. If it fails, you will be returned INVALID_SOCKET(-1), and should call WSAGetLastError() for more information. Once you've finished with a socket, you need to release the handle so that the Winsock DLL can release all the resources associated with it. This includes closing any connections, cancelling any pending operations, and freeing any system buffers in use. The API to do this is simple - closesocket():

Public Declare Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long Once a socket has been freed up throgh closesocket(), it is no longer a valid handle, and as such you should set your variable to INVALID_SOCKET. If you try to use the handle in a function call, it will fail with the error WSAENOTSOCK. API Series -> Winsock API 1: Sockets (Page 4) So, now we've been through that, you should be able to initialize Winsock 2, and create your first socket. Open up the Winsock API Template project and paste the following code into the DoWinsockStuff() private sub. ' Dim hTCPSocket As Long Dim hUDPSocket As Long ' ' Create the sockets. hTCPSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) hUDPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) ' ' Check they are valid handles. If (hTCPSocket = INVALID_SOCKET) Then MsgBox "hTCPSocket = INVALID_SOCKET" Else MsgBox "hTCPSocket = " & CStr(hTCPSocket) End If ' If (hUDPSocket = INVALID_SOCKET) Then MsgBox "hUDPSocket = INVALID_SOCKET" Else MsgBox "hUDPSocket = " & CStr(hUDPSocket) End If ' ' Free up the sockets and any resources. Call closesocket(hTCPSocket) Call closesocket(hUDPSocket)

API Series >> Winsock API 2: Clients Servers and clients: Network communication normally involves at least two parties the client and the server. There is often misconception about which is which, so here's how to work it out. Most communication is some form of question/response - one side sends a command or a question; the other side does some processing and sends a response. This is the same as when you go into a resteraunt - you (the client) ask the waiter (the server) for your meal. He/she goes off and processes your request, then returns with the meal (data). In more general terms, servers accept incoming

connections made from clients. Addressing: Making a connection using the Winsock API is very simple. Firstly, create a TCP socket using the socket() function as we learned in the previous chapter. Then, fill up a sockaddr_in structure with the address of the server you want to connect to, and call connect(). The sockaddr_in structure is used a lot in Winsock programming - whenever a host machine (either local or remote) needs to be addressed using IPv4, in fact. You will see it again in future chapters. Filling the structure gets a little complex, and means we have to sidetrack onto other matters a little bit, but first the structure definition: Public Type in_addr s_addr As Long End Type ' Public Type sockaddr_in sin_family As Integer sin_port As Integer sin_addr As in_addr sin_zero(0 To 7) As Byte End Type The sin_zero field is just padding, and can be ignored. The sin_family field should be set to AF_INET (since we're using IPv4). The sin_addr.s_addr field should be set to a network byte order long IP address, and the sin_port field should be set to a network byte order short port number we want to connect on. BUT.. and here's the tricky bit when specifying network numbers, you have to change how they are stored slightly. When numbers are stored on the host, they are stored in "host-byte" order (little endian), but when transmitted across the network they must be in "network-byte" order (big-endian). There are some handy functions to convert between the two: Public Declare Function htonl Lib "ws2_32.dll" (ByVal hostlong As Long) As Long Public Declare Function htons Lib "ws2_32.dll" (ByVal hostshort As Long) As Integer ' Public Declare Function ntohl Lib "ws2_32.dll" (ByVal netlong As Long) As Long Public Declare Function ntohs Lib "ws2_32.dll" (ByVal netshort As Long) As Integer htonl() converts a "Host TO Network Long", htons() converts a "Host TO Network Short (Integer)", ntohl() converts a "Network TO Host Long", and ntohs() converts a "Network TO Host Short (Integer)". "Short" is a C data type, and is the same as a Visual Basic integer. Here is a link which describes the endian storage a little more. Also, you may well be thinking - why do I need to use the API when I could write my own conversion function? Well, the answer is that Windows may be running on a non Intel platform, like Alpha, where host and network byte order is the same. In this case, the APIs will detect it and take it into account. While we're on the subject of conversion functions, there are two more handy conversion functions that you'll probably use, the first of which is inet_addr(). This takes an IP address in string format ("127.0.0.1") and converts it to a network-byte order long, ready to be

assigned to the sockaddr_in structure. The second is inet_ntoa(), and it does the opposite - it takes a network-byte long value and returns a pointer to an IP address string which can be retrieved with the Win32 CopyMemory() API. Public Declare Function inet_addr Lib "ws2_32.dll" (ByVal cp As String) As Long Public Declare Function inet_ntoa Lib "ws2_32.dll" (ByVal inn As Long) As Long API Series -> Winsock API 2: Clients (Page 2) Connecting: Once we've set up the sockaddr_in structure, it's as simple as calling the connect() API function to connect to the remote host. The connect() function is very simple, and looks like this: Public Declare Function connect Lib "ws2_32.dll" (ByVal s As Long, addr As sockaddr_in, ByVal addrlen As Long) As Long The s parameter is the socket to use, the addr is a sockaddr_in structure describing the remote host we want to connect to, and the addrlen parameter is the length of the addr parameter. If connect() fails it will return SOCKET_ERROR, and applications should call WSAGetLastError() for more detailed information. This will probably be something like "host not found" or "host not reachable" or "address not available" if the sockaddr_in structure has been filled incorrectly. The socket handle used in the connect is still usable, and the connect() attempt can be re-attempted. Once the connection succeeds, connect() returns ERROR_SUCCESS. Here's how it all goes together (paste this into the DoWinsockStuff() method in the Winsock API project template you've got): ' Dim hSocket As Long Dim udtAddr As sockaddr_in ' ' Create a TCP socket. hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) If (hSocket <> INVALID_SOCKET) Then ' ' Set up the remote address - the localhost (127.0.0.1) on port 10101. With udtAddr .sin_family = AF_INET .sin_addr.s_addr = inet_addr("127.0.0.1") .sin_port = htons(10101) End With ' ' Call connect. If (connect(hSocket, udtAddr, LenB(udtAddr)) = ERROR_SUCCESS) Then MsgBox "Connected to 127.0.0.1 on port 10101" Else

MsgBox "connect() failed with error: " & WSAGetLastError() End If ' ' Close and release the socket handle. Call closesocket(hSocket) ' Else MsgBox "socket() failed with error: " & WSAGetLastError() End If ' To use this, you should have an application listening on port 80 of your local machine to accept the connection request. Nothing much will happen though since the connection is dropped straight away. You will notice that the application hangs until connect() returns. This is what's called blocking. Later on you'll learn how to avoid blocking, as it can be undesirable. Whilst it may seem very simple, behind the scenes Winsock is doing a lot of work negotiating with the remote host. TCP connections are often called three-way handshakes because of the nature of how they are set up. Firstly, the client sends the server a SYN (synchronization) packet. When the server receives that, it allocates memory and prepares for the client, then sends back a SYNACK (acknowledged) packet. Once the client receives this, it sends a final ACK packet back and the connection is ready. A document describing this process in more detail can be found here (PDF). The next chapter will show you how to bind a socket to the local interface, put it in a listening state, and accept incoming connection requests.

API Series -> Winsock API 3: Servers (Page 1) Accepting Once a connection request is received, we can call accept() which returns a newly connected socket handle. Public Declare Function accept Lib "ws2_32.dll" ( _ ByVal s As Long, ByRef addr As sockaddr_in, ByRef addrlen As Long) As Long The s parameter is the socket on which to perform the operation. The addr parameter in accept() is a sockaddr_in structure that is filled with the remote host's address when the connection is accepted. The addrlen parameter contains the length of this structure. This is useful because the remote IP address and remote port number can be determined immediately, and then the socket closed before any data is sent or received if your applications conditions are not met. The addrlen is passed to the API ByRef because it is up to the API to tell us how long the passed structure is. When using VB, this won't really come into play, as we already know how long it is. It is useful when programming in a language like C++, where the memory must be allocated dynamically. Note that because accept() returns a new socket handle, the original socket is still in a listening state, and so will continue to receive connection requests. If you want it to stop, call closesocket(). Below is an example - it will listen

for a single connection, then accept and close it. accept() will block until a request is received, since we haven't set up any notification models. ' Dim udtLocalAddr As sockaddr_in Dim udtRemoteAddr As sockaddr_in Dim hListenSocket As Long Dim hAcceptedSocket As Long ' ' Create a TCP socket to listen on. hListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) If (hListenSocket <> INVALID_SOCKET) Then ' ' Bind the socket to all IP addresses on port 10101. With udtLocalAddr .sin_family = AF_INET .sin_addr.s_addr = htonl(INADDR_ANY) .sin_port = htons(10101) End With If (bind(hListenSocket, udtLocalAddr, LenB(udtLocalAddr)) = ERROR_SUCCESS) Then ' ' Set the socket into a listening state. Call listen(hListenSocket, SOMAXCONN) ' ' Block and wait for a connection request. hAcceptedSocket = accept(hListenSocket, udtRemoteAddr, LenB(udtRemoteAddr)) If (hAcceptedSocket <> INVALID_SOCKET) Then ' ' Close the accepted socket. MsgBox "Accepted socket: " & hAcceptedSocket Call closesocket(hAcceptedSocket) ' Else MsgBox "accept() failed with error: " & WSAGetLastError() End If ' Else MsgBox "bind() failed with error: " & WSAGetLastError() End If ' ' Close the listening socket. Call closesocket(hListenSocket) ' Else MsgBox "socket() failed with error: " & WSAGetLastError() End If '

Start this sample, then start the sample from Chapter 2 ("Clients"). The two should connect and then disconnect. You'll receive a successfull MsgBox on each application, and you are halfway to writing your own Winsock applications. API Series -> Winsock API 3: Servers (Page 2) Introduction As mentioned before, there are two parts to a network connection. The client initiates the connection by sending a connection request to the server. The server waits for this request, then accepts it and thus completes the connection. Servers are normally set up to provide a service, hence the name. Setting up a server is really very simple. Binding To create a server, we must firstly create a TCP socket, then tell it to listen for connection requests. Once listening, we can accept the next connection request. It sounds easy enough, but there is an extra step however - binding. Since computers can have multiple network adapters, they can have multiple IP addresses. This means that we have to associate our socket with at least one IP address before we can set the socket to a listening state, and this process is called "binding". All we have to do is setup a sockaddr_in structure just as before, with one difference. Set the sin_addr.s_addr field to INADDR_ANY. This causes the binding to occur on all IP addresses, so that incoming connection requests on any IP will be passed to our application. The bind() function looks like this, and is fairly self explanatory: Public Declare Function bind Lib "ws2_32.dll" ( _ ByVal s As Long, ByRef name As sockaddr_in, ByRef namelen As Long) As Long On error, it returns SOCKET_ERROR. Call WSAGetLastError() to retrieve more information - the most common error is WSAEADDRINUSE, which means that the IP address and port combination you've picked is already being used by another application. If this happens, the easiest way to get around it is to choose another port number. Listening Once we have a bound socket, we can call listen(). This API function puts the socket into a listening state, so that we are notified of connection requests (handling notification will be covered in the "Handling I/O" chapter. For now, we will continue to use blocking calls). The API looks like this: Public Declare Function listen Lib "ws2_32.dll" (ByVal s As Long, ByVal backlog As Long) As Long The s parameter is the socket to put into a listening state. The backlog parameter is the number of connection requests that will be automatically accepted and queued by the system. If there is a queued request and we call accept() it will return immediately. The most common value for this is SOMAXCONN(5). If the backlog is full and another client attempts to connect it will fail with error WSAECONNREFUSED. For this reason, it pays to accept and handle connection requests as fast as possible. On error listen() will return SOCKET_ERROR. Upon calling WSAGetLastError() you'll most likely receive the WSAEINVAL error, which means that the socket is unbound.

If you forget to call bind() this error will occur. WSAEISCONN means that the socket is already connected, so cannot listen. You might also get WSAEOPNOTSUPP - this means that socket is not of a correct type, in other words the underlying provider you've created the socket from does not support listening operations.

API Series -> Winsock API 4: Data transfer (Page 1) Introduction: The main purpose of Winsock is to share data across a network. Whichever side of the exchange you're on, you'll probably want to either send or receive at some point. Of course, some servers just receive data, but that's up to you. Data transmission is probably the easiest part of winsock, so this article is relatively short. However, there is one small point that you'll have to note: the data input/output (I/O) functions are different depending on which protocol you're using. If you're using TCP or any other connection based protocol, you'll use send() and recv(). If you're using UDP or any other connectionless protocol, you'll use sendto() and recvfrom(). The difference between these functions will be described. Winsock 2 introduced some new I/O functions which provide more control and can be told to run asynchronously. But since this is just a primer on the API, we won't go into them right now. For most Winsock applications, the standard functions are fine. Just in case you want to do a little more research on your own, the function names for the new APIs are: WSASend, WSASendTo, WSARecv and WSARecvFrom. API Series -> Winsock API 4: Data transfer (Page 2) TCP Sending: The send() API is declared like this: Public Declare Function send Lib "ws2_32.dll" ( _ ByVal s As Long, _ ByRef buf As Byte, _ ByVal datalen As Long, _ ByVal Flags As Long) As Long s is the connected socket you want to use to transmit the data. buf is the first byte in an array. This byte array contains the data you want sent. datalen is the length of the data, in bytes. Flags is used to pass extra information to the function. This parameter used to indicate that the data is Out Of Band data by passing the MSG_OOB flag. We won't cover this option. The function returns the number of bytes sent. Remember, a successful return does not mean that the data was successfully delivered to the recipient. It is quite possibile for send() to succeed, and then an error to occur on the socket, indicating that the data did not arrive. send() will block (wait) until all the data passed in is written onto the socket. If no buffer space is available for the socket (ie, there's already too much data waiting to be sent_\), send() will block until there is. In some cases, this can be undesirable. I/O synchronization models are described in future chapters. If send() fails it will return SOCKET_ERROR. Applications should call WSAGetLastError() for more detailed

information. Often the error code returned will indicate that the socket is not connection due to some reason, or that the network is unavailable. In this case, sending is physically impossible, and should be aborted. If send() succeeds, it will return the number of bytes sent. Note that this can be less than datalen, and if so, you should call send() again with the remaining data. UDP Sending: Since UDP sockets don't have to be connected, there is no way to associate them to the remote machine. In order to do this, there is an extra sockaddr_in structure on the end of the sendto() function. Public Declare Function sendto Lib "ws2_32.dll" ( _ ByVal s As Long, _ ByRef buf As Byte, _ ByVal datalen As Long, _ ByVal Flags As Long, _ ByRef toaddr As sockaddr_in, _ ByVal tolen As Long) As Long The first four parameters are the same as send(). toaddr is a sockaddr_in structure that describes the remote host to which the data will be sent. It is filled in exactly the same way as it is when calling connect() in the previous chapter. It can even be a broadcast address ("255.255.255.255" is all the subnets on the LAN). In order to perform broadcasting, the socket s must have the SO_BROADCAST option set with setsocketopt(). sendto() is vulnerable to all the errors described for send(), and the return value is the same. One other error can occur though - care must be taken to avoid exceeding the underlying provider's maximum packet size. This value can be retrieved using getsocketopt() with the SO_MAX_MSG_SIZE option. If the data is too long to pass automically through the underlying protocol, the error WSAEMSGSIZE is returned and no data is transmitted. API Series -> Winsock API 4: Data transfer (Page 3) TCP Receiving: Receiving is the exact same operation as sending, only the other way around. Instead of writing data onto the wire, you're reading data off it. The same parameters are needed to do this, so the API looks the same: Public Declare Function recv Lib "ws2_32.dll" ( _ ByVal s As Long, _ ByRef buf As Byte, _ ByVal datalen As Long, _ ByVal Flags As Long) As Long s is the connected socket you want to receive data on. buf is the first byte in an array into which the data will be copied. datalen is the length of the array, in bytes. Flags is not used. recv() will block until one of two conditions: a) There is no more data to be read on the socket handle, or b) the array passed into the function is full. The function returns the number of bytes copied into the array. Unfortunately there is

no way to tell how much data is pending, so - simply loop and call recv() repeatedly until it returns 0. This indicates a successful call, but with no data, which means there is no more to be read. recv() and will return SOCKET_ERROR on error and more information can be obtained by calling WSAGetLastError(). UDP Receiving: UDP receiving is again very similar to UDP sending. The parameters are the same (with slightly different names to indicate the change in direction of data), and are used in the same way. Public Declare Function recvfrom Lib "ws2_32.dll" ( _ ByVal s As Long, ByRef buf As Byte, ByVal datalen As Long, ByVal Flags As Long, ByRef fromaddr As sockaddr_in, ByRef fromlen As Long) As Long When the call returns, the fromaddr parameter is filled with the remote address of the host the data arrived from. If passed, fromlen indicates the length of this structure. Again, we won't use this since we already know how long our sockaddr_in structure is. recvfrom() will block until data is ready, then it will read it. One important thing to mention with recvfrom() is that of message sizes. UDP data travels around in fixed size packets, normally 8KB in size. If more than one packet is waiting to be read, and you pass a 10KB buffer to recvfrom(), the first 8KB packet will be copied into your array. Then, the first 2KB of the next packet will be copied in, and the rest of the second packet is lost. On top of that, recvfrom() will generate a WSAEMSGSIZE error, which means that the message sent on the datagram socket was larger than the internal data buffer, so was lost. To avoid this, always call recvfrom() with a buffer that is a multiple of the message size. The appropriate size can be found by calling getsocketopt() with the SO_MAX_MSG_SIZE flag. Socket options are discussed in more detail in later chapters. API Series -> Winsock API 4: Data transfer (Page 4) Conclusion: So there you go - you should now be able to do a lot with Winsock: Start and initialize the Winsock API service Create TCP and UDP sockets Bind the socket to a local address and listen for connection requests Accept the incoming requests to establish a connection Send and receive data over TCP Send and receive data over UDP Clear up all resources used and free the Winsock service Impressed with yourself? Good. This article comes with an attachment you can download which demonstrates all the techniques and functions learned so far. It will create four sockets, two TCP and two UDP. The TCP sockets will establish a connection and send data back and forth. The UDP sockets will do the same without the connection. From here onwards things get a little more complicated. Firstly we will go over name resolution and socket options, before moving on to I/O models. I/O

models are basically a means to give your program notification when a network event occurs, such as data arriving, or a connection request being received. This means that your API calls won't block, and so your user-interface is free to do other things. Sound good? Stay tuned. API Series -> Winsock API 5: Name resolution (Page 1) Introduction One of the common things that needs doing in Winsock applications is turning an IP address into a hostname, and the other way around. For example: Hostname....: winsockvb.com IP Address..: 217.151.99.5 Obviously, IP addresses can be hard to remember, but a nice "dot com" name is much easier. Allowing your user to enter a hostname instead of an IP address will add much to the general usability of the program. Displaying IP address to the user is also not always the best option - if you can, use a hostname. Hostnames are converted to IP addresses by the 'net's Domain Name System (DNS). It is a worldwide database that contains a list of domain names and the associated IP addresses (amongst other things). You can query the DNS directly, but it is much simple to use the Winsock name resolution functions. Winsock 2 has introduced some new functions which provide extended capabilities, such as the ability to work with IPv6 addresses, and to run asynchronously. However, there are very few IPv6 addresses in public use, and running these APIs asynchronously can often just lead to more code being written. Personally, I find the "legacy" functions are more usable, but just in case you want to research a little more on your own, the function names of the Winsock 2 versions are: WSAAsyncGetHostByAddr() and WSAAsyncGetHostByName(). All the functions mentioned rely on a hostent structure, which is defined like this: Public Type hostent h_name As Long h_aliases As Long h_addrtype As Integer h_length As Integer h_addr_list As Long End Type The hostent structure is used because it accurately represents a "host", and the various bits of information about that host - the IP addresses, the domain name, and other pieces. API Series -> Winsock API 5: Name resolution (Page 2) Hostname to an IPv4 address To convert a hostname to an IP string, we must use the gethostbyname() API function.

Public Declare Function gethostbyname Lib "ws2_32.dll" (ByVal host_name As String) As Long We pass in a dotted IP address via the host_name parameter, and are returned a pointer to a hostent structure. The important fields here are h_addr_list and h_length. h_addr_list is a null terminated array of IP addresses for the hostname (since it may have more than one), and h_length is the length of each entry in the array. All we need to do is to copy the hostent structure locally, then extract a string of length h_length from the h_addr_list pointer. There may be several IPs, but normally the default is listed first. ' Dim lngRet As Long Dim udtHost As hostent Dim strHostName As String Dim bytIPAddress() As Byte Dim lngPointer As Long Dim lngIPAddress As Long Dim strIPAddress As String Dim lngCounter As Long ' ' Set the hostname, and call gethostbyname() strHostName = "www.winsockvb.com" lngRet = gethostbyname(strHostName) If (lngRet <> 0) Then ' ' Copy the hostent structure locally. Call CopyMemory (udtHost, ByVal lngRet, LenB(udtHost)) ' ' Copy out the pointer to IP address. Call CopyMemory (lngPointer, ByVal udtHost.h_addr_list, 4) ' ' Copy the IP address out of the pointer. Call CopyMemory (lngIPAddress, ByVal lngPointer, 4) ' ' Prepare a byte array, and copy the IP into it. ReDim bytIPAddress(1 To udtHost.h_length) Call CopyMemory (bytIPAddress(1), lngIPAddress, udtHost.h_length) ' ' Build a dotted string (0.0.0.0) out of the byte array. For lngCounter = 1 To 4 strIPAddress = strIPAddress & CStr(bytIPAddress(lngCounter)) & "." Next lngCounter strIPAddress = Left(strIPAddress, Len(strIPAddress) - 1) ' ' Display. MsgBox "Resolved " & strHostName & " to " & strIPAddress '

Else MsgBox "gethostbyname() failed with error: " & WSAGetLastError() End If ' API Series -> Winsock API 5: Name resolution (Page 3) IPv4 address to hostname To convert an IP string to a hostname, we must use the gethostbyaddr() API function. Public Declare Function gethostbyaddr Lib "ws2_32.dll" (haddr As Long, _ ByVal hnlen As Long, _ ByVal addrtype As Long) As Long haddr is a network byte order IPv4 address which we want to resolve. The hnlen paramter specifies the length of the IP address, and in our case IPv4 addresses are 4 bytes long. The addrtype parameter requires us to specify the address family the address uses, and in our case it is AF_INET. After calling gethostbyaddr(), we are again returned a pointer to a hostent structure. The important field in the hostent structure this time is h_name - it's a pointer to a hostname string so all we have to do is retrieve it using the CopyMemory() Win32 API. ' Dim lngRet As Long Dim udtHost As hostent Dim lngIPAddress As Long Dim strHostName As String ' ' Convert our string IP address into a network-byte Long. lngIPAddress = inet_addr("127.0.0.1") If (lngIPAddress <> INADDR_NONE) Then ' ' Call gethostbyaddr() to retrieve a hostent structure. lngRet = gethostbyaddr(lngIPAddress, 4, AF_INET) If (lngRet <> 0) Then ' ' Copy the hostent to local memory. Call CopyMemory(udtHost, ByVal lngRet, LenB(udtHost)) ' ' Prepare a string buffer and copy the hostname into it. strHostName = String$(256, 0) Call CopyMemory(ByVal strHostName, ByVal udtHost.h_name, Len(strHostName)) ' ' Trim off trailing null characters, and display. strHostName = Mid$(strHostName, 1, Instr(1, strHostName, Chr$(0)) - 1) MsgBox "Resolved 127.0.0.1 to: " & strHostName '

Else MsgBox "gethostbyaddr() failed with error: " & WSAGetLastError() End If ' Else MsgBox "inet_addr() returned an invalid address (0.0.0.0)" End If ' The next article is coming soon, and will go over Winsock I/O notification models, specifically - WSAAsyncSelect().

Reference -> Properties (Page 1) BytesReceived [Read-only] - Long Contains a total number of bytes received by the Winsock control in any one session. Useful for calculating progressbars and download speeds. Index [Read-only] - Integer If the control is within a control array, this is the array index. Used when locating a specific connection within multiple connections, using a control array. LocalHostName [Read-only] - String Stores a string representation of the local host IP. Eg - "localhost". Useful for use on a LAN - instead of giving out your IP address, you can give out your local host name. A Winsock will connect to this in the same way as it would to an IP address. LocalIP [Read-only] - String Stores the local machines IP address. Only available if the machine is online, otherwise it will return a rubbish IP address - 127.0.0.1 (This is a special loopback IP, that always points to the local machine. It allows you to make internal connections for testing.), or 3.0.0.0. Possibly even xxx.xxx.xxx.xxx. LocalPort - Long Stores the port that this Winsock is currently attached to, whether it be connected to or listening on it. Is equal to 0 if it has not been set. Name - String This Winsock control's name, used with which to reference it. Eg - Winsock1 or Sock. Object [Read-only] - Object dunno Parent [Read-only] - Object Returns a reference to the object on which this control is located. Eg - the parent form. Useful for manipulating a parent based on a sockets actions. Protocol - ProtocolConstant

Which protocol the Winsock is currently using. Could be either sckTCPProtocol, or sckUDPProtocol. You should already know this in theory, because you should have set it. Default is TCP. 0 - TCP:Transmission Control Protocol (sckTCPProtocol) 1 - UDP:User Datagram Protocol (sckUDPProtocol) RemoteHost - String The computer the socket is associated to. Can be either an IP address, or a resolvable name eg localhost. Must be set in order to make connections (TCP) or send data (UDP). RemoteHostIP [Read-only] - String The IP address of the computer the Winsock is connected to. Only accesible at runtime once the Winsock has been connected. RemotePort - Long The port on the remote machine that the Winsock is associated to. Must be set in order to make connections (TCP) or send data (UDP). SocketHandle [Read-only] - Long Returns a handle to the internal socket being used by the Winsock. Not really used unless you want to get into API. State [Read-only] - StateConstants Contains an integer representing the current connective state of the Winsock. 0 - Closed (sckClosed) 1 - Open (sckOpen) 2 - Listening (sckListening) 3 - Connection pending (sckConnectionPending) 4 - Resolving Host (sckResolvingHost) 5 - Host Resolved (sckHostResolved) 6 - Connecting (sckConnecting) 7 - Connected (sckConnected) 8 - Closing (sckClosing) 9 - Error (sckError) Tag - String Extra property to hold any string you want. Might be useful to hold another detail about the socket.

Reference -> Methods (Page 1) Accept (requestID As Long) - TCP Accepts an incoming request for a full connection. Once this is done, the connection is open and data transfer can take place. Bind ([LocalPort], [LocalIP]) - UDP

Attaches a Winsock to a particular port. This means that the DataArrival event will fire even though the Winsock is not actually connected. Check out the Basics section for more info on using UDP. Close - TCP Resets a Winsock, closing any current conenctions. Means that no more data transfer can take place, also resets properties like RemoteHostIP. Closes both client and server. GetData (data, [type,] [maxLen]) - TCP/UDP Used in the DataArrival event to get data from the Winsock's internal buffer. Clears the buffer after retrieval. Stores data in a variant block unless the variable it is retrieved into is a string, or the data type is set to string. Listen - TCP Opens a Winsock to listen for incoming connections on the LocalPort property. Connection_Request events will only fire on a Winsock that is listening. PeekData (data, [type,] [maxLen]) - TCP/UDP Used in the DataArrival event to get data from the Winsock's internal buffer. Does not clear the buffer after retrieval. SendData (data) - TCP/UDP The meat of the winsock. Used to transmit data down an active open connection. An error will fire if you try to do this when the Winsock is not connected, and you're using TCP protocol.

Reference -> Events (Page 1) Close Occurs when the Winsock control's connection is closed remotely. You should then use the Close method to properly reset the Winsock. Connect Occurs when the Winsock control establishes a new outgoing connection. ConnectionRequest (requestID As Long) Occurs when an incoming connection request is received. It is in this event that you would accept the request to open a new active connection. Note - RemoteHost and RemoteHostIP are filled once this event fires. DataArrival (bytesTotal As Long) Occurs when data has been received into the Winsock's internal buffer. It's in this event that should use the GetData and/or PeekData methods. Error (number As Integer, Description As String, Scode As Long, Source As String, HelpFile as String, HelpContext As Long, CancelDisplay As Boolean) Occurs when the Winsock errors for any reason, for example - failing to send or receive properly.

SendComplete Occurs when a complete section of data has been sent successfully. SendProgress (bytesSent As Long, bytesRemaining As Long) Occurs while data is being sent, giving details on the status of the transmission. Useful for contructing timers, progress bars, and speed monitors.

Potrebbero piacerti anche