Basic winsock functions

In this chapter of the winsock tutorial, I will show you the basic winsock functions that operate on sockets. It is important to remember that this chapter is only an introduction to the socket functions, so you will be able to follow the next tutorials. Do not start coding immediately after you've read this chapter, the next chapters are just as important.

The basic functionality of each function is relatively simple, but things like the blocking mode make it more complicated than it looks at first sight. The next chapters will cover the details, but first you need to be familiar with the functions.

This chapter is quite long and you might not remember everything but that's okay. Just read it carefully so you know what I'm talking about in the next chapters, you can always look back here and use it as a quick reference.

1. WSAStartup & WSACleanup

WSAStartup PROTO wVersionRequested:DWORD, lpWSAData:DWORD
WSACleanup PROTO

Before calling any winsock function, you need to initialize the winsock library. This is done with WSAStartup. It takes two parameters:

wVersionRequested
Highest version of Windows Sockets support that the caller can use. The high-order byte specifies the minor version (revision) number; the low-order byte specifies the major version number.
lpWSAData
Pointer to the WSADATA data structure that is to receive details of the Windows Sockets implementation.

As explained in the introduction, I will use winsock 2. This means you need to set the low byte of wVersionRequested to 2, the high byte can be zero (the revision number is not important). The WSADATA structure specified with the lpWSAData parameter will receive some information about the winsock version installed.

The function returns zero if it succeeded, otherwise you can call WSAGetLastError to see what went wrong. WSAGetLastError is the winsock equivalent of the win32 APIs GetLastError, it retrieves the code of the last occurred error.

It is important to note that you might not get the version you requested in the wVersionRequested parameter. This parameter specifies the highest winsock version your application *supports*, not 'requires'. Winsock will try hard to give you the version you requested but if that is not possible, it uses a lower version. This version is available after the call, in the wVersion member of the WSADATA structure. You should check this version after the call to see if you really got the winsock version you wanted. There is also a member called wHighVersion that gives the highest winsock version supported by the system. In short:

wVersionRequested parameter: The highest winsock version your application supports.
wHighVersion in WSADATA: The highest winsock version the system supports.
wVersion in WSADATA: min(wVersionRequested, wHighVersion).

Each call to WSAStartup has to match a call to WSACleanup, which cleans up the winsock library. Although useless, WSAStartup may be called more than once, as long as WSACleanup is called the same number of times.

An example of initializing and cleaning up winsock:

.data?
    wsaData     WSADATA     <?>

.code
    invoke  WSAStartup, 0002h, addr wsaData
    test    eax, eax
    jz      _startupSucceeded

        ; error handling code

_startupSucceeded:

    ; check lower byte of wVersion (major version number)
    cmp     byte ptr [wsaData.wVersion], 2
    jae     _versionOK

        ; error: version < 2
        ; Winsock still has to be cleaned up though:
        jmp     _doCleanup

_versionOK:

    ; ------ call winsock functions here ------

_doCleanup:
    invoke  WSACleanup
    test    eax, eax
    jz      _cleanupSucceeded

        ; error handling code

_cleanupSucceeded:

2. socket

socket PROTO af:DWORD, type:DWORD, protocol:DWORD

The socket function creates a new socket and returns a handle to it. The handle is of type SOCKET (an unsigned DWORD) and is used by all functions that operate on the socket. The only invalid socket handle value is INVALID_SOCKET (defined as 'NOT 0'), all other values are legal (this includes the value zero!). Its parameters are:

af
The address family to use. Use AF_INET to use the address family of TCP & UDP.
type
The type of socket to create. Use SOCK_STREAM to create a streaming socket (using TCP), or SOCK_DGRAM to create a diagram socket (using UDP). For more information on socket types, see the previous chapter.
protocol
The protocol to be used, this value depends on the address family. You can specify IPPROTO_TCP here to create a TCP socket.

The return value is a handle to the new socket, or INVALID_SOCKET if something went wrong. The socket function can be used like this:

.data?
    hSocket     dd  ?

.code
    invoke  socket, AF_INET, SOCK_STREAM, IPPROTO_TCP
    cmp     eax, INVALID_SOCKET
    jne     _socket_created

        ; error handling code

_socket_created:
    mov     [hSocket], eax

3. closesocket

closesocket PROTO s:DWORD

Closesocket closes a socket. It returns zero if no error occurs, SOCKET_ERROR otherwise. Each socket you created with socket has to be closed with an appropriate closesocket call.

s
Handle to the socket to be closed. Do not use this socket handle after you called this function.

The use of closesocket is pretty straightforward:

invoke closesocket, [hSocket]

However, in real situations some more operations are necessary to close the socket properly. This will be discussed later in the tutorial.

4. sockaddr and byte ordering

Because winsock was made to be compatible with several protocols including ones that might be added later (using the SPI) a general way of addressing has to be used. TCP/IP uses an IP and port number to specify an address, but other protocols might do it differently. If winsock forced a certain way of addressing, adding other protocols may not have been possible. The first version of winsock solved this with the sockaddr structure:

sockaddr STRUCT
  sa_family     WORD        ?
  sa_data       BYTE 14 dup(?)
sockaddr ENDS

In this structure, the first member (sa_family) specifies the address family the address is for. The data stored in the sa_data member can vary among different address families. We will only use the internet address family (TCP/IP) in this tutorial, winsock has defined a structure sockaddr_in that is the TCP/IP version of the sockaddr structure. They are essentially the same structure, but the second is obviously easier to manipulate.

sockaddr_in STRUCT
  sin_family    WORD        ?
  sin_port      WORD        ?
  sin_addr      in_addr     <>
  sin_zero      BYTE 8 dup (?)
sockaddr_in ENDS

The last 8 bytes of the structure are not used but are padded (with sin_zero) to give the structure the right size (the same size as sockaddr).

Before proceeding, it is important to know about the network byte order. In case you don't know, byte ordering is the order in which values that span multiple bytes are stored. For example, a 32-bit integer value like 0x12345678 spans four 8-bit bytes. Intel x86 machines use the 'little-endian' order, which means the least significant byte is stored first. So the value 0x12345678 would be stored as the byte sequence 0x78, 0x56, 0x34, 0x12. Most machines that don't use little-endian use big-endian, which is exactly the opposite: the most significant byte is stored first. The same value would then be stored as 0x12, 0x34, 0x56, 0x78. Because protocol data can be transferred between machines with different byte ordering, a standard is needed to prevent the machines from interpreting the data the wrong way.

Network byte ordering
Because protocols like TCP/IP have to work between different type of systems with different type of byte ordering, the standard is that values are stored in big-endian format, also called network byte order. For example, a port number (which is a 16-bit number) like 12345 (0x3039) is stored with its most significant byte first (ie. first 0x30, then 0x39). A 32-bit IP address is stored in the same way, each part of the IP number is stored in one byte, and the first part is stored in the first byte. For example, 216.239.51.100 is stored as the byte sequence '216,239,51,100', in that order.

Apart from the sin_family value of sockaddr and sockaddr_in, which is not part of the protocol but tells winsock which address family to use, all the values in both structures have to be in network byte order. Winsock provides several functions to deal with the conversion between the byte order of the local host and the network byte order:

; Convert a u_short from host to TCP/IP network byte order.
htons PROTO hostshort:DWORD

; Convert a u_long from host to TCP/IP network byte order.
htonl PROTO hostlong:DWORD

; Convert a u_long from TCP/IP network order to host byte order.
ntohs PROTO netlong:DWORD

; Convert a u_long from TCP/IP network order to host byte order.
ntohl PROTO netlong:DWORD

You might question why we should need four API functions for such simple operations as swapping the bytes of a short or long (as that's enough to convert from little-endian (intel) to big-endian (network)). This is because these APIs will work even if you are running your program on a machine with other byte ordering than an intel machine (that is, the APIs are platform independent), like Windows CE on a handheld using a big-endian processor. As you are reading the win32asm version of this tutorial, you are programming for an intel processor so you can safely assume it is little-endian. In this case, a simple byte swap (for example "bswap" for longs and "ror reg16, 8" for shorts) will do. Whether you use these APIs or your own macros/functions is up to you. Just know that the API way is guaranteed to work on all systems.

Back to the sockaddr_in structure, as said above, all members except for sin_family have to be in network byte order. For sin_family use AF_INET. sin_port is the port number of the address (16-bit), sin_addr is the IP address (32-bit), declared as an union to manipulate the full 32-bit word, the two 16-bit parts or each byte separately. sin_zero is not used.

Here are several examples of initializing sockaddr_in structures:

.data?
    sockAddr1   sockaddr_in     <?>
    sockAddr2   sockaddr_in     <?>

.data
    szIPAddress db "127.0.0.1",0

.code
    ; Set address family
    mov     [sockAddr1.sin_family], AF_INET

    ; Convert port number 80 to network byte order and store it in sin_port
    invoke  htons, 80
    mov     [sockAddr1.sin_port], ax

    ; inet_addr converts a string with an IP address in dotted format to
    ; a long value which is the IP in network byte order.
    ; sin_addr.S_un.S_addr specifies the long value in the address union

    invoke  inet_addr, addr szIPAddress
    mov     [sockAddr1.sin_addr.S_un.S_addr], eax ;store IP

    ; Set address of sockAddr2 by setting the 4 byte parts:
    mov     [sockAddr2.sin_addr.S_un.S_un_b.s_b1], 127
    mov     [sockAddr2.sin_addr.S_un.S_un_b.s_b2], 0
    mov     [sockAddr2.sin_addr.S_un.S_un_b.s_b3], 0
    mov     [sockAddr2.sin_addr.S_un.S_un_b.s_b4], 1

The inet_addr function in the example above can convert an IP address in dotted string format to the appropriate 32-bit value in network byte order. There is also a function called inet_ntoa, which does exactly the opposite.

As a side note, winsock 2 does not require that the structure used to address a socket is the same size of sockaddr, only that the first short is the address family and that the right structure size is passed to the functions using it. This allows new protocols to use larger structures. The sockaddr structure is provided for backwards compatibility. However, since we will only use TCP/IP in this tutorial, the sockaddr_in structure can be used perfectly.

5. connect

connect PROTO s:DWORD, name:DWORD, namelen:DWORD

The connect function connects a socket with a remote socket. This function is used on the client side of a connection, as you are the one initiating it. A short description of its parameters:

s
The unconnected socket you want to connect.
name
Pointer to a sockaddr structure that contains the name (address) of the remote socket to connect to.
namelen
Size of the structure pointed to by name.

The first parameter s is the client socket used for the connection. For example, a socket you've just created with the socket function. The other two parameters, name and namelen are used to address the remote socket (the server socket that is listening for incoming connections). This is done by using a sockaddr structure (or sockaddr_in for TCP/IP), as described in the previous section.

A possible use of this function is connecting to a webserver to request a page. To address the server, you can use sockaddr_in structure and fill it with the server's IP and port number. You might wonder how you get the IP of a hostname like www.madwizard.org, I will show you how to do that later. For now, just assume you know the server's IP number.

Assuming a webserver is running on a local network PC with IP number 192.168.0.5, using the default HTTP port 80, this would be the code to connect to the server:

.data?
    sockAddr    sockaddr_in     <?>

.data
    szIPAddress db "192.168.0.5",0

.code

    ; This code assumes a socket has been created and its handle
    ; is stored in a variable called hSocket

    mov     [sockAddr.sin_family], AF_INET
    invoke  htons, 80
    mov     [sockAddr.sin_port], ax
    invoke  inet_addr, addr szIPAddress
    mov     [sockAddr.sin_addr.S_un.S_addr], eax ;store IP

    ; Connect to the server
    invoke  connect, [hSocket], addr sockAddr, sizeof sockAddr
    test    eax, eax
    jz      _connectSucceeded

        ; error handling code

_connectSucceeded:

6. bind

bind PROTO s:DWORD, name:DWORD, namelen:DWORD

Binding a socket has been explained in the previous chapter. By binding a socket you assign an address to a socket. Bind's parameters are:

s
The unbound socket you want to bind.
name
Pointer to a sockaddr structure that contains the address to assign to the socket.
namelen
Size of the structure pointed to by name.

For TCP/IP, the sockadrr_in structure can be used as usually. Let's look at an example first:

.data?
    sockAddr    sockaddr_in     <?>

.code
    mov     [sockAddr.sin_family], AF_INET
    invoke  htons, 80
    mov     [sockAddr.sin_port], ax
    mov     [sockAddr.sin_addr.S_un.S_addr], INADDR_ANY ;use default

    ; bind socket to port 80
    invoke  bind, [hSocket], addr sockAddr, sizeof sockAddr
    test    eax, eax
    jz      _bindSucceeded

        ; error handling code

_bindSucceeded:

As you can see, a sockaddr_in structure is filled with the necessary information. The address family is AF_INET for TCP/IP. In the example, we bind the socket to port number 80, but not to an IP number. By specifying the INADDR_ANY value as IP address, winsock will choose an address for you. This can be very useful for PCs with multiple network adapters (and thus multiple IPs). If you do want to bind to a specific IP, just convert the IP to a DWORD in network byte order and put it in the structure. Something similar is possible with the port number; when you specify 0 as the port number winsock will assign a unique port with a value between 1024 and 5000. However, most of the time you want to bind to a specific port number.

Binding is usually done before putting the socket in a listening state, to make the socket listen on the right port number (and optionally an IP number). Although you can also bind a socket before connecting it, this is not commonly done because the address of the socket on the client side is not important most of the time.

7. listen

listen PROTO s:DWORD, backlog:DWORD

The listen function puts a socket in the listening state, that is it will be listening for incoming connections. It has two parameters:

s
The bound, unconnected socket you want to set into the listening state.
backlog
Maximum length of the queue of pending connections.

The backlog parameter can be set to specify the length of the queue of pending connections that have not yet been accepted. Usually, you can use the default value SOMAXCONN, allowing the underlying service provider to choose a reasonable value.

Before listen is called, the socket must have been bound to an address, as shown in the previous section. For example, if you bind a socket to port 80 and then call listen on the socket, all incoming connections on port 80 will be routed to your application. To actually accept the connection, another function called accept is available, it will be explained in the next section.

The following code snippet shows how to call the listen function on a socket that has been bound already:

;  This code assumes the socket specified by
;  hSocket is bound with the bind function
invoke  listen, [hSocket], SOMAXCONN
test    eax, eax
jz      _listenSucceeded

        ; error handling code

_listenSucceeded:

8. accept

accept PROTO s:DWORD, sockaddr:DWORD, addrlen:DWORD

When the socket is in the listening state and an incoming connection arrives, you can accept it with the accept function.

s
The socket that has been placed in a listening state with the listen function.
addr
Optional pointer to a buffer that receives the address of the remote socket. This parameter is a pointer to a sockaddr structure, but its exact structure is determined by the address family.
addrlen
Optional pointer to an integer that contains the length of addr. Before calling the function, the value should be the size of the buffer pointed to by addr. On return, the value is the size of the data returned in the buffer.

As you know, when a connection is accepted a new socket is created on the server side. This new socket is connected to the client socket, all operations on that connection are done with that socket. The original listening socket is not connected, but instead listens for more incoming connections.

.data?
    remoteAddr      sockaddr_in     <?>
    remoteAddrLen   dd              ?
    hRemoteSocket   dd              ?

.code
    mov     [remoteAddrLen], sizeof remoteAddr
    invoke  accept, [hSocket], addr remoteAddr, addr remoteAddrLen
    cmp     eax, INVALID_SOCKET
    jne     _accepted

            ; error handling code

_accepted:
    mov     [hRemoteSocket], eax

If accept succeeds, a connection is established and the return value is a new socket handle that is the server side of the new connection. Optionally, you can set the addr and addrlen parameters that will receive a sockaddr structure containing the remote address information (IP & port number).

9. send and recv

send PROTO s:DWORD, buf:DWORD, len:DWORD, flags:DWORD

s
The connected socket to send data on.
buf
Pointer to a buffer containing the data to send
len
Length of the data pointed to by buf.
flags
Specifies the way in which the call is made.

int recv(SOCKET s, char *buf, int len, int flags);

s
The connected socket to receive data from.
buf
Pointer to a buffer that will receive the data.
len
Length of the buffer pointed to by buf.
flags
Specifies the way in which the call is made.

To transfer data on a connection, you use the send and recv functions. Send sends the data in the buffer on the socket and returns the number of bytes sent. Recv receives the data that is currently available at the socket and stores it in the buffer. The flags parameter can usually be set to zero for both recv and send.

In blocking mode, send will block until all data has been sent (or an error occurred) and recv will return as much information as is currently available, up to the size of the buffer specified.

Although these functions may seem simple at first, they become more complicated in non-blocking mode. When a socket is in non-blocking mode, these functions cannot block until the operation is finished so they may not perform the operation fully (ie. not all data is sent), or not at all. The next chapter will explain these issues in great detail, I won't discuss it here since this only a function overview.

This example of recv and send on a connected socket in blocking mode will just send back all data it receives.

.data?
    buffer db 128 dup (?)

.code
_sendrecvLoop:
    ; Receive data
    invoke  recv, [hRemoteSocket], addr buffer, 128, 0
    test    eax, eax
    jz      _connectionClosed
    cmp     eax, SOCKET_ERROR
    jne     _recvSucceeded

        ; error handling code

_recvSucceeded:
    ; Send received data back
    invoke  send, [hRemoteSocket], addr buffer, eax, 0
    cmp     eax, SOCKET_ERROR
    jne     _sendSucceeded

        ; error handling code

_sendSucceeded:
    jmp     _sendrecvLoop

_connectionClosed:

10. Usage

As stated in this chapter's introduction, this was only an overview of the main winsock functions. Just knowing how the functions is not enough to program correctly with winsock. The next chapters will tell you how to use them correctly, which I/O strategies exist and how blocking and non-blocking mode works.