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.
In IP, connection-oriented communication is accomplished through the TCP/IP protocol. TCP provides reliable error-free data transmission between two computers. When applications communicate using TCP, a virtual connection is established between the source computer and the destination computer. Once a connection is established, data can be exchanged between the computers as a two-way stream of bytes.
Server API Functions :
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. 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 :
Binding :
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
- );
- 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));
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.
Listening :
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
- );
The errors associated with listen are straightforward and the most common is WSAEINVAL, which usually indicates that you forgot to call bind before listen.
Accepting Connections :
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
- );
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. Block, non-blocking, and other socket modes are covered in Chapter 5. Winsock 2 introduced the function WSAAccept, which has the capability to conditionally accept a connection based on the return value of a condition function. Chapter 10 will describe WSAAccept in greater detail.
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. We did not perform any error checking on the calls to make reading the code less confusing :
- void winsock_accept()
- {
- WSADATA wsaData;
- SOCKET ListeningSocket;
- SOCKET NewConnection;
- SOCKADDR_IN ServerAddr;
- SOCKADDR_IN ClientAddr;
- int Port = 5150;
- int ClientAddrLen = sizeof(ClientAddr);
- // Initialize Winsock version 2.2
- if(WSAStartup(MAKEWORD(2,2), &wsaData)<0)
- {
- printf("Call WSAStartup() failed!\n");
- return ;
- }
- // 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.
- if(bind(ListeningSocket, (SOCKADDR *)&ServerAddr,
- sizeof(ServerAddr))<0)
- {
- printf("Call bind() failed!\n");
- goto CLEAN_UP;
- }
- // Listen for client connections. We used a backlog of 5, which
- // is normal for many applications.
- if(listen(ListeningSocket, 5))
- {
- printf("Call listen() failed!\n");
- goto CLEAN_UP;
- }
- // Accept a new connection when one arrives.
- NewConnection = accept(ListeningSocket, (SOCKADDR *)
- &ClientAddr, &ClientAddrLen);
- if(NewConnection==NULL)
- {
- printf("Call accept() failed!\n");
- goto CLEAN_UP;
- }
- // 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);
- printf("winsock_accept() return success!\n");
- // When your application is finished handling the connections,
- // call WSACleanup.
- CLEAN_UP:
- WSACleanup();
- }
Client API Functions :
The client is much simpler and involves fewer steps to set up a successful connection. There are only three steps for a client :
You already know how to create the socket and construct a SOCKADDR structure, so the only remaining step is establishing a connection. If you are using TCP/IP, you may need to know about TCP States. For more information regarding the TCP/IP protocol, consult RFC 793. This RFC and others can be found athttp://www.rfc-editor.org. Below is the TCP closure states :
- connect
Connecting a socket is accomplished by calling connect, WSAConnect, or ConnectEx. We'll look at the Winsock 1 version, which is defined as :
- int connect(
- SOCKET s,
- const struct sockaddr FAR* name,
- int namelen
- );
If the computer you're attempting to connect to does not have a process listening on the given port, the connect call fails with the WSAECONNREFUSEDerror. The other error you might encounter is WSAETIMEDOUT, which occurs if the destination you're trying to reach is unavailable (either because of a communication-hardware failure on the route to the host or because the host is not currently on the network). The following program fragment demonstrates how to write a simple client that can connect to the server application described earlier :
- void winsock_connect()
- {
- WSADATA wsaData;
- SOCKET s;
- SOCKADDR_IN ServerAddr;
- int Port = 5150;
- // Initialize Winsock version 2.2
- if(WSAStartup(MAKEWORD(2,2), &wsaData)<0)
- {
- printf("Call WSAStartup() failed!\n");
- goto CLEAN_UP;
- }
- // Create a new socket to make a client connection.
- s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- // Set up a SOCKADDR_IN structure that will be used to connect
- // to a listening server on port 5150. For demonstration
- // purposes, let's assume our server's IP address is 136.149.3.29.
- // Obviously, you will want to prompt the user for an IP address
- // and fill in this field with the user's data.
- ServerAddr.sin_family = AF_INET;
- ServerAddr.sin_port = htons(Port);
- ServerAddr.sin_addr.s_addr = inet_addr("10.6.7.190");
- // Make a connection to the server with socket s.
- if(connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)))
- {
- printf("Call connect() failed\n");
- int iErr;
- switch((iErr=WSAGetLastError()))
- {
- case WSAECONNREFUSED:
- {
- printf("\tError > No connection could be made because the target machine actively refused it.\n");
- break;
- }
- case WSAETIMEDOUT:
- {
- printf("\tError > A connection attempt failed because the connected party did not properly respond "
- "after a period of time, or established connection failed because connected host has failed to respond.\n");
- break;
- }
- case WSAEADDRNOTAVAIL:
- {
- printf("\tError > The requested address is not valid in its context.\n");
- break;
- }
- default:
- {
- printf("\tError > Unknown(%d)\n", iErr);
- }
- }
- goto CLEAN_UP;
- }
- // At this point you can start sending or receiving data on
- // the socket s. We will describe sending and receiving data
- // later in the chapter.
- // When you are finished sending and receiving data on socket s,
- // you should close the socket using the closesocket API. We will
- // describe socket closure later in the chapter.
- closesocket(s);
- // When your application is finished handling the connection, call
- // WSACleanup.
- CLEAN_UP:
- WSACleanup();
- }
Data Transmission :
Sending and receiving data is what network programming is all about. For sending data on a connected socket, there are two API functions: send andWSASend. The second function is specific to Winsock 2. Likewise, two functions are for receiving data on a connected socket: recv and WSARecv. An important thing to keep in mind is that all buffers associated with sending and receiving data are of the simple char type which is just simple byte-oriented data. In reality, it can be a buffer with any raw data in it—whether it's binary or string data doesn't matter.
In addition, the error code returned by all send and receive functions is SOCKET_ERROR. The two most common errors encountered areWSAECONNABORTED and WSAECONNRESET. Both of these deal with the connection being closed—either through a timeout or through the peer closing the connection. Another common error is WSAEWOULDBLOCK, which is normally encountered when either nonblocking or asynchronous sockets are used. This error basically means that the specified function cannot be completed at this time. In Chapter 5, we will describe various Winsock I/O methods that can help you avoid some of these errors.
- send
To send data on a connected socket is send, which is prototyped as :
- int send(
- SOCKET s,
- const char FAR * buf,
- int len,
- int flags
- );
On a good return, send returns the number of bytes sent; otherwise, if an error occurs, SOCKET_ERROR will be returned. A common error is WSAECO-NNABORTED, which occurs when the virtual circuit terminates because of a timeout failure or a protocol error. When this occurs, the socket should be closed, as it is no longer usable. The error WSAECONNRESET occurs when the application on the remote host resets the virtual circuit by executing a hard close or terminating unexpectedly, or when the remote host is rebooted. Again, the socket should be closed after this error occurs. The last common error isWSAETIMEDOUT, which occurs when the connection is dropped because of a network failure or the remote connected system going down without notice.
- recv
The recv function is the most basic way to accept incoming data on a connected socket. This function is defined as :
- int recv(
- SOCKET s,
- char FAR* buf,
- int len,
- int flags
- );
Message peeking is bad. Not only does it degrade performance, as you now need to make two system calls (one to peek and one without the MSG_PEEK flag to actually remove the data), but it is also unreliable under certain circumstances. The data returned might not reflect the entire amount available. Also, by leaving data in the system buffers, the system has less space to contain incoming data. As a result, the system reduces the TCP window size for all senders. This prevents your application from achieving the maximum possible throughput. The best thing to do is to copy all the data you can into your own buffer and manipulate it there.
There are some considerations when using recv on a message- or datagram-based socket such as UDP, which we will describe later. If the data pending is larger than the supplied buffer, the buffer is filled with as much data as it will contain. In this event, the recv call generates the error WSAEMSGSIZE. Note that the message-size error occurs with message-oriented protocols. Stream protocols such as TCP buffer incoming data and will return as much data as the application requests, even if the amount of pending data is greater. Thus, for streaming protocols you will not encounter the WSAEMSGSIZE error.
Stream Protocols :
Because most connection-oriented communication, such as TCP, is streaming protocols, we'll briefly describe them here. A streaming protocol is one that the sender and receiver may break up or coalesce data into smaller or larger groups. The main thing to be aware of with any function that sends or receives data on a stream socket is that you are not guaranteed to read or write the amount of data you request. Let's say you have a character buffer with 2048 bytes of data you want to send with the send function. The code to send this is :
- char sendbuff[2048];
- int nBytes = 2048;
- // Fill sendbuff with 2048 bytes of data
- // Assume s is a valid, connected stream socket
- ret = send(s, sendbuff, nBytes, 0);
- char sendbuff[2048];
- int nBytes = 2048,
- nLeft,
- idx;
- // Fill sendbuff with 2048 bytes of data
- // Assume s is a valid, connected stream socket
- nLeft = nBytes;
- idx = 0;
- while (nLeft > 0)
- {
- ret = send(s, &sendbuff[idx], nLeft, 0);
- if (ret == SOCKET_ERROR)
- {
- // Error
- }
- nLeft -= ret;
- idx += ret;
- }
- char recvbuff[1024];
- int ret,
- nLeft,
- idx;
- nLeft = 512;
- idx = 0;
- while (nLeft > 0)
- {
- ret = recv(s, &recvbuff[idx], nLeft, 0);
- if (ret == SOCKET_ERROR)
- {
- // Error
- }
- idx += ret;
- nLeft -= ret;
- }
Breaking the Connection :
Once you are finished with a socket connection, you must close it and release any resources associated with that socket handle. To actually release the resources associated with an open socket handle, use the closesocket call. Be aware, however, that closesocket can have some adverse effects—depending on how it is called—that can lead to data loss. For this reason, a connection should be gracefully terminated with the shutdown function before a call to the closesocket function.
- shutdown
To ensure that all data an application sends is received by the peer, a well-written application should notify the receiver that no more data is to be sent. Likewise, the peer should do the same. This is known as a graceful close and is performed by the shutdown function, defined as :
- int shutdown(
- SOCKET s,
- int how
- );
Note that not all connection-oriented protocols support graceful closure, which is what the shutdown API performs. For these protocols (such as ATM), onlyclosesocket needs to be called to terminate the session.
- closesocket
The closesocket function closes a socket and is defined as :
- int closesocket (SOCKET s);
Pending synchronous calls issued by any thread in this process are canceled without posting any notification messages. Pending overlapped operations are also canceled. Any event, completion routine, or completion port that is associated with the overlapped operation is performed but will fail with the errorWSA_OPERATION_ABORTED. Socket I/O models are discussed in greater depth in Chapter 5.
沒有留言:
張貼留言