Winsock(1) TCP Server

struct sockaddr_in
{
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

The sin_family field must be set to AF_INET, which tells Winsock we are using the IP address family.

The sin_port field defines which TCP or UDP communication port will be used to identify a server service.

The sin_addr field of the SOCKADDR_IN structure is used for storing an IPv4 address as a four-byte quantity,

which is an unsigned long integer data type. Depending on how this field is used, it can represent a local or a remote IP address. IP addresses are normally specified in Internet standard dotted notation as a.b.c.d. Each letter represents a number (in decimal, octal, or hexadecimal format) for each byte and is assigned, from left to right, to the four bytes of the unsigned long integer.
The final field, sin_zero, functions only as padding to make the SOCKADDR_IN structure the same size as the SOCKADDR structure.

A useful support function named inet_addr converts a dotted IP address to a 32-bit unsigned long integer quantity.

The inet_addr function is defined as

unsigned long inet_addr(
    const char FAR *cp
);

The cp field is a null-terminated character string that accepts an IP address in dotted notation. Note that this function returns an IP address as a 32-bit unsigned long integer in network-byte order.

Byte Ordering

When an IP address and port number are specified as multibyte quantities in a computer, they are represented in host-byte order. However, when IP addresses and port numbers are specified over a network, Internet networking standards specify that multibyte values must be represented in big-endian form (most significant byte to least significant), normally referred to as network-byte order.

A series of functions can be used to convert a multibyte number from host-byte order to network-byte order and vice versa. The following four API functions convert a number from host-byte to network-byte order:

u_long htonl(u_long hostlong);
int WSAHtonl(
    SOCKET s,
    u_long hostlong,
    u_long FAR * lpnetlong
);
u_short htons(u_short hostshort);
int WSAHtons(
    SOCKET s,
    u_short hostshort,
    u_short FAR * lpnetshort
);

The hostlong parameter of htonl and WSAHtonl is a four-byte number in host-byte order. The htonl function returns the number in network-byte order, whereas the WSAHtonl function returns the number in network-byte order through the lpnetlong parameter. The hostshort parameter of htons and WSAHtons is a two-byte number in host-byte order. The htons function returns the number as a two-byte value in network-byte order, whereas the WSAHtons function returns the number through the lpnetshort parameter.
The next four functions are the opposite of the preceding four functions; they convert network-byte order to host-byte order.

u_long ntohl(u_long netlong);
int WSANtohl(
    SOCKET s,
    u_long netlong,
    u_long FAR * lphostlong
);
u_short ntohs(u_short netshort);
int WSANtohs(
    SOCKET s,
    u_short netshort,
    u_short FAR * lphostshort
);

We will now demonstrate how to address IPv4 by creating a SOCKADDR_IN structure using the inet_addr and htons functions described previously.

SOCKADDR_IN InternetAddr;
INT nPortId = 5150;
InternetAddr.sin_family = AF_INET;
// Convert the proposed dotted Internet address 136.149.3.29
// to a four-byte integer, and assign it to sin_addr
InternetAddr.sin_addr.s_addr = inet_addr("136.149.3.29");
// The nPortId variable is stored in host-byte order. Convert
// nPortId to network-byte order, and assign it to sin_port.
InternetAddr.sin_port = htons(nPortId);

Creating a socket

A socket is a handle to a transport provider. In Windows, a socket is not the same thing as a file descriptor and therefore is a separate type: SOCKET in WINSOCK2.H. There are two functions that can be used to create a socket: socket and WSASocket.
For simplicity, we will briefly describe socket:

SOCKET socket (
    int af,
    int type,
    int protocol
);

The first parameter, af, is the protocol's address family. Since we describe Winsock in this chapter using only the IPv4 protocol, you should set this field to AF_INET.

The second parameter, type, is the protocol's socket type. When you are creating a socket to use TCP/IP, set this field to SOCK_STREAM, for UDP/IP use SOCK_DGRAM.

The third parameter is protocol and is used to qualify a specific transport if there are multiple entries for the given address family and socket type. For TCP you should set this field to IPPROTO_TCP; for UDP use IPPROTO_UDP.

Connection-Oriented Communication

In this section, we'll cover the Winsock functions necessary for both receiving connections and establishing connections. We'll first discuss how to develop a server by listening for client connections and explore the process for accepting or rejecting a connection. Then we'll describe how to develop a client by initiating a connection to a server.
Finally, we'll discuss how data is transferred in a connection-oriented session.

A server is a process that waits for any number of client connections with the purpose of servicing their requests. A server must listen for connections on a well-known name. In TCP/IP, this name is the IP address of the local interface and a port number. Every protocol has a different addressing scheme and therefore a different naming method. The first step in Winsock is to create a socket with either the socket or WSASocket call and bind the socket of the given protocol to its well-known name, which is accomplished with the bind API call. The next step is
to put the socket into listening mode, which is performed (appropriately enough) with the listen API function. Finally, when a client attempts a connection, the server must accept the connection with either the accept or WSAAccept call. In the next few sections, we will discuss each API call that is required for binding, listening, and accepting a
client connection. Figure 1-1 illustrates the basic calls a server and a client must perform in order to establish a communication channel.

 Once the socket of a particular protocol is created, you must bind it to a well-known address. The bind function associates the given socket with a well-known address. This function is declared as

int bind(
    SOCKET s,
    const struct sockaddr FAR* name,
    int namelen
);

The first parameter, s, is the socket on which you want to wait for client connections. The second parameter is of type struct sockaddr, which is simply a generic buffer. You must actually fill out an address buffer specific to the protocol you are using and cast that as a struct sockaddr when calling bind. The Winsock header file defines the type SOCKADDR as struct sockaddr. We'll use this type throughout the chapter for brevity. The third parameter is simply the size of the protocol-specific address structure being passed. For example, the following code illustrates
how this is done on a TCP connection:

SOCKET s;
SOCKADDR_IN tcpaddr;
int port = 5150;
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
tcpaddr.sin_family = AF_INET;
tcpaddr.sin_port = htons(port);
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));

From the example, you'll see a stream socket being created, followed by setting up the TCP/IP address structure on which client connections will be accepted. In this case, the socket is being bound to the default IP interface by using a special address, INADDR_ANY, and occupies port number 5150. We could have specified an explicit IP address available on the system, but INADDR_ANY allows us to bind to all available interfaces on the system so that any incoming client connection on any interface (but the correct port) will be accepted by our listening socket. The call to bind formally establishes this association of the socket with the local IP interface and port.

On error, bind returns SOCKET_ERROR. The most common error encountered with bind is WSAEADDRINUSE. With TCP/IP, the WSAEADDRINUSE error indicates that another process is already bound to the local IP interface and port number or that the IP interface and port number are in the TIME_WAIT state. If you call bind again on a socket that is already bound, WSAEFAULT will be returned.

The next piece of the equation is to put the socket into listening mode. The bind function merely associates the socket with a given address. The API function that tells a socket to wait for incoming connections is listen, which is defined as

int listen(
    SOCKET s,
    int backlog
);

Again, the first parameter is a bound socket. The backlog parameter specifies the maximum queue length for pending connections. This is important when several simultaneous requests are made to the server. For example, let's say the backlog parameter is set to two. If three client requests are made at the same time, the first two will be placed in a pending queue so that the application can service their requests. The third connection request will fail with WSAECONNREFUSED. Note that once the server accepts a connection, the request is removed from the queue so that others can make a request. The backlog parameter is silently limited to a value that the underlying protocol provider determines. Illegal values are replaced with their nearest legal values. In addition, there is no standard provision for finding the actual backlog value.

The errors associated with listen are fairly straightforward. By far the most common is WSAEINVAL, which usually indicates that you forgot to call bind before listen. Otherwise, it is possible to receive the WSAEADDRINUSE error on the listen call as opposed to the bind call. This error occurs most often on the bind call.

Now you're ready to accept client connections. This is accomplished with the accept, WSAAccept, or AcceptEx function. The prototype for accept is

SOCKET accept(
    SOCKET s,
    struct sockaddr FAR* addr,
    int FAR* addrlen
);

Parameter s is the bound socket that is in a listening state. The second parameter should be the address of a valid SOCKADDR_IN structure, while addrlen should be a reference to the length of the SOCKADDR_IN structure. For a socket of another protocol, substitute the SOCKADDR_IN with the SOCKADDR structure corresponding to that protocol. A call to accept services the first connection request in the queue of pending connections. When the accept function returns, the addr structure contains the IPv4 address information of the client making the connection request, and the addrlen parameter indicates the size of the structure. In addition, accept returns a new socket descriptor that corresponds to the accepted client connection. For all subsequent operations with this client, the new socket should be used. The original listening socket is still open to accept other client connections and is still in listening mode.

If an error occurs, INVALID_SOCKET is returned. The most common error encountered is WSAEWOULDBLOCK if the listening socket is in asynchronous or non-blocking mode and there is no connection to be accepted. Winsock 2 introduced the function WSAAccept, which has the capability to conditionally accept a connection based on the return value of a condition function.

At this point, we have described all the necessary elements to construct a simple Winsock TCP/IP server application. The following program fragment demonstrates how to write a simple server that can accept one TCP/IP connection.

#include <winsock2.h>
void main(void)
{
    WSADATA wsaData;
    SOCKET ListeningSocket;
    SOCKET NewConnection;
    SOCKADDR_IN ServerAddr;
    SOCKADDR_IN ClientAddr;
    int Port = 5150;
    // Initialize Winsock version 2.2
    WSAStartup(MAKEWORD(2,2), &wsaData);
    // Create a new socket to listen for client connections.
    ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    // Set up a SOCKADDR_IN structure that will tell bind that we
    // want to listen for connections on all interfaces using port
    // 5150. Notice how we convert the Port variable from host byte
    // order to network byte order.
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(Port);
    ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    // Associate the address information with the socket using bind.
    bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr));
    // Listen for client connections. We used a backlog of 5, which
    // is normal for many applications.
    listen(ListeningSocket, 5);
    // Accept a new connection when one arrives.
    NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr, &ClientAddrLen));
// At this point you can do two things with these sockets. Wait
// for more connections by calling accept again on ListeningSocket
// and start sending or receiving data on NewConnection. We will
// describe how to send and receive data later in the chapter.
// When you are finished sending and receiving data on the
// NewConnection socket and are finished accepting new connections
// on ListeningSocket, you should close the sockets using the
// closesocket API. We will describe socket closure later in the
// chapter.
    closesocket(NewConnection);
    closesocket(ListeningSocket);
// When your application is finished handling the connections,
// call WSACleanup.
    WSACleanup();
}

 Client

The client is much simpler and involves fewer steps to set up a successful connection. There are only three steps for a client:

  1. Creating a socket
  2. Set up a SOCKADDR address structure with the name of server you are going to connect to. For TCP/IP, this is the server's IP address and port number its application is listening
    on.
  3. Initiate the connection with connect or WSAConnect.

As a Winsock programmer, you are not required to know the actual TCP states, but by knowing them you will gain a better understanding of how the Winsock API calls affect change in the underlying protocol. In addition, many programmers run into a common problem when closing sockets: the TCP states surrounding a socket closure are of the most interest.
The start state of every socket is the CLOSED state. When a client initiates a connection, it sends a SYN packet to the server and puts the client socket in the SYN_SENT state. When the server receives the SYN packet, it sends a SYN-ACK packet, which the client responds to with an ACK packet. At this point, the client's socket is in the
ESTABLISHED state. If the server never sends a SYN-ACK packet, the client times out and reverts to the CLOSED state.
When a server's socket is bound and is listening on a local interface and port, the state of the socket is LISTEN. When a client attempts a connection, the server receives a SYN packet and responds with a SYN-ACK packet. The state of the server's socket changes to SYN_RCVD. Finally, the client sends an ACK packet, which causes the state of the server's socket to change to ESTABLISHED. 

Once the application is in the ESTABLISHED state, there are two paths for closure. If your application initiates the closure, it is known as an active socket closure; otherwise, the socket closure is passive. Figure 1-2 illustrates both an active and a passive closure. If you actively initiate a closure, your application sends a FIN packet. When your application calls closesocket or shutdown (with SD_SEND as its second argument), your application sends a FIN packet to the peer, and the state of your socket changes to FIN_WAIT_1. Normally, the peer responds with an
ACK packet, and your socket's state becomes FIN_WAIT_2. If the peer also closes the connection, it sends a FIN packet and your computer responds by sending an ACK packet and placing your socket in the TIME_WAIT state.

The TIME_WAIT state is also called the 2MSL wait state. MSL stands for Maximum Segment Lifetime and represents the amount of time a packet can exist on the network before being discarded. Each IP packet has a time-to-live (TTL) field, which when decremented to 0 causes the packet to be discarded. Each router on the network that handles the packet decrements the TTL by 1 and passes the packet on. Once an application enters the TIME_WAIT state, it remains there for twice the MSL time. This allows TCP to re-send the final ACK in case it's lost, causing the FIN to be retransmitted. After the 2MSL wait state completes, the socket goes to the CLOSED
state.

On an active close, two other paths lead to the TIME_WAIT state. In our previous discussion, only one side issues a FIN and receives an ACK response, but the peer is still free to send data until it too closes. This is where the other two paths come into play. In one path the simultaneous close a computer and its peer at the other side of a connection issue a close at the same time; the computer sends a FIN packet to the peer and receives a FIN packet from the peer. Then the computer sends an ACK packet in response to the peer's FIN packet and changes its socket to the CLOSING state. Once the computer receives the last ACK packet from the peer, the computer's socket state becomes TIME_WAIT.

The other path for an active closure is just a variation on the simultaneous close: the socket transitions from the FIN_WAIT_1 state directly to the TIME_WAIT state. This occurs when an application sends a FIN packet but shortly thereafter receives a FIN-ACK packet from the peer. In this case, the peer is acknowledging the application's FIN packet and sending its own, to which the application responds with an ACK packet.
The major effect of the TIME_WAIT state is that while a TCP connection is in the 2MSL wait state, the socket pair defining that connection cannot be reused. A socket pair is the combination of local IP local port and remote IP remote port. Some TCP implementations do not allow the reuse of any port number in a socket pair in the TIME_WAIT state. Microsoft's implementation does not suffer from this deficiency. However, if a connection is attempted in which the socket pair is already in the TIME_WAIT state, the connection attempt will fail with error WSAEADDRINUSE. One way around this (besides waiting for the socket pair that is using that local port to leave the TIME_WAIT state) is to use the socket option SO_REUSEADDR. Chapter 7 covers the SO_REUSEADDR option in detail. 

Reference: Network Programming for Microsoft Windows
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值