a single data buffer is used for // incoming and outgoing data // LONG CMainView::OnAsyncSelectWPARAM wParam, LPARAM lParam { char pszMessage[100]; // informational message static char
Trang 1/////////////////////////////////////////////////////////////////////////////// CMainView message handlers
/////////////////////////////////////////////////////////////////////////////// CMainView::OnAsyncSelect()
//
// Receives data from a client and echoes the data back to the sending client.// While there is data yet to be sent back to the sending client, the server// will not receive any more data (a single data buffer is used for
// incoming and outgoing data)
//
LONG CMainView::OnAsyncSelect(WPARAM wParam, LPARAM lParam)
{
char pszMessage[100]; // informational message
static char pBuf[101]; // send/recv buffer
int nBytesRecv; // number of bytes received
int nBytesSent; // number of bytes sent
static int nBytesToSend = 0; // number of bytes to send
int nError; // WinSock error
static SOCKADDR_IN addrFrom; // address of client
static int nAddrFromLen = sizeof(addrFrom); // length of client address struct static IN_ADDR inFrom; // IP address of client
// get pointer to list box used for status messages
CListBox *plb = (CListBox *)GetDlgItem(IDC_LIST_STATUS);
// check for an error
// send the data
nBytesSent = sendto(m_s, pBuf, nBytesToSend, 0,
Trang 2// don’t do anything we’ll get another FD_WRITE soon
nError = WSAGetLastError();
if (nError != WSAEWOULDBLOCK)
{
wsprintf(pszMessage, “Error %d sending data to %s, %d”,
nError, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
plb->InsertString(0, pszMessage);
nBytesToSend = 0;
// just in case the FD_READ was called but it didn’t read
// because the buffer still contained data to send
wsprintf(pszMessage, “Data sent (%s) to %s, %d”,
pBuf, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
// if there are still bytes waiting to be sent back (echoed)
// to the client, don’t do anything here (the FD_WRITE handler will
// generate FD_READ when it is through with sending)
if (nBytesToSend == 0)
{
// receive data
nBytesRecv = recvfrom(m_s, pBuf, 100, 0, (LPSOCKADDR)&addrFrom, &nAddrFromLen);
// check for receive error
if (nBytesRecv == SOCKET_ERROR)
{
// if the error is just that the receive would block,
// don’t do anything we’ll get another FD_READ soon
Trang 3pBuf[nBytesToSend] = ‘\0’;
wsprintf(pszMessage, “Data received (%s) from %s, %d”,
pBuf, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
plb->InsertString(0, pszMessage);
// just in case the FD_WRITE was called but it didn’t have
// any data to send at that time (it has data to send now)
PostMessage(WM_USER_ASYNC_SELECT, m_s, WSAMAKESELECTREPLY(FD_WRITE, 0)); }
Datagram Echo Client DECLIENT
The datagram echo client, DECLIENT, follows the same basic outline as DESERV It also uses a CFormView object as its main interface The primary difference lies in the implementation of the CMainView object.
The header file for the CMainView object is shown in Listing 8.16 Its implementation is shown in Listing 8.17 This object performs most of the work for the DECLIENT application CMainView::OnInitialUpdate() is called soon after the object is created This function is responsible for creating a socket, waiting to send and receive data, and setting a timer to be used for sending data When data is ready to be received or data can be sent, the CMainView::OnAsyncSelect() member function is called due to the message mapping of the user-defined message WM_USER_ASYNC_SELECT The
CMainView::OnTimer() function is called every five seconds to format a string to send to the echo server.
Listing 8.16 MAINVIEW.H for DECLIENT.
// mainview.h : header file
Trang 4class CMainView : public CFormView
{
DECLARE_DYNCREATE(CMainView)
public:
SOCKET m_s; // socket
SOCKADDR_IN m_addr; // address to send to
IN_ADDR m_in; // IP address of address to send to
int m_nBytesToSend; // number of bytes to send
char m_pBuf[101]; // buffer to send
enum { IDD = IDD_DIALOG_MAIN };
// NOTE: the ClassWizard will add data members here
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual void OnInitialUpdate();
// Generated message map functions
//{{AFX_MSG(CMainView)
afx_msg LONG OnAsyncSelect(WPARAM wParam, LPARAM lParam);
afx_msg void OnTimer(UINT nIDEvent);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
#define WM_USER_ASYNC_SELECT (WM_USER + 1)
Listing 8.17 MAINVIEW.CPP for DECLIENT.
// mainview.cpp : implementation file
Trang 5void CMainView::OnInitialUpdate()
{
// get pointer to list box used for status messages
CListBox *plb = (CListBox *)GetDlgItem(IDC_LIST_STATUS);
// prompt for server information (IP and port)
Listing 8.17 continued
Trang 6FD_READ | FD_WRITE) == SOCKET_ERROR)
plb->InsertString(0, “Datagram echo client could not do async select”);
char pszMessage[100]; // informational message
static char pBuf[101]; // send/recv buffer
int nBytesRecv; // number of bytes received
int nBytesSent; // number of bytes sent
int nError; // WinSock error
// get pointer to list box used for status messages
CListBox *plb = (CListBox *)GetDlgItem(IDC_LIST_STATUS);
// check for an error
Trang 7// is there any data to send?
if (m_nBytesToSend != 0)
{
// send the data
nBytesSent = sendto(m_s, m_pBuf, m_nBytesToSend, 0,
(LPSOCKADDR)&m_addr, sizeof(m_addr));
// check for send error
if (nBytesSent == SOCKET_ERROR)
{
// if the error is just that the send would block,
// don’t do anything we’ll get another FD_WRITE soon
nError = WSAGetLastError();
if (nError != WSAEWOULDBLOCK);
{
wsprintf(pszMessage, “Error %d sending data to %s, %d”,
nError, inet_ntoa(m_in), ntohs(m_addr.sin_port));
wsprintf(pszMessage, “Data sent (%s) to %s, %d”,
m_pBuf, inet_ntoa(m_in), ntohs(m_addr.sin_port));
nBytesRecv = recvfrom(m_s, pBuf, 100, 0, NULL, NULL);
// check for receive error
if (nBytesRecv == SOCKET_ERROR)
{
// if the error is just that the receive would block,
// don’t do anything we’ll get another FD_READ soon
Trang 8pBuf, inet_ntoa(m_in), ntohs(m_addr.sin_port));
// Timer to generate a string to send Won’t send
// unless the previous string is completely sent
Running the Datagram Echo Server and Client
A sample sequence of events of running the datagram echo server and client is as
fol-lows:
Run DESERV It displays on which port it’s waiting for data to arrive.
Run DECLIENT on the same or a different computer It prompts for the IP
address and port DESERV is using.
In five seconds the timer will trigger in DECLIENT, causing
CMainView::OnTimer() to get called No bytes are waiting to be sent yet, so the
outgoing buffer is filled and a WinSock FD_WRITE message is faked to trigger the
sending of the data This has to be done because the real FD_WRITE may have
been missed or it may have occurred when there was nothing to send yet.
CMainView::OnAsyncSelect() is called in DECLIENT with an FD_WRITE
event There are bytes to be sent, so an attempt to transmit them to the
Trang 9datagram echo server is made If the attempt succeeds and bytes are written, the number of bytes to send is reset to 0 (zero) If there was an error sending but the error was simply that the send would block, the count of bytes to send is retained because you’ll get a real FD_WRITE eventually.
Assuming that DECLIENT sends a buffer, CMainView::OnAsyncSelect() is called in DESERV with an FD_READ notice If the data is read successfully, a byte count is recorded, the originator of the data is noted, and a fake FD_WRITE
message is generated to force the sending of the just-received data back to the originator (DECLIENT).
CMainView::OnAsyncSelect() is called in DESERV with an FD_WRITE event There are bytes to be sent, so an attempt to transmit them to the datagram echo client is made If the attempt succeeds and bytes are written, the number
of bytes to send is reset to 0 (zero) If there was an error sending but the error was simply that the send would block, the count of bytes to send is retained because you’ll get a real FD_WRITE eventually.
Assuming that DESERV sends a buffer, CMainView::OnAsyncSelect() is called
in DECLIENT with an FD_READ notice and the client reads the echoed data Another timer goes off in DECLIENT and the process repeats.
Figure 8.9 shows DESERV and DECLIENT running on the same computer, which has the IP address 166.78.16.150 The server and client were assigned ports 1059 and
1060, respectively Notice that after each application initialized, it received an FD_WRITE
notification WinSock is simply telling the application that the socket is in a writeable state The applications ignore the message unless they have bytes to send.
Trang 10Stream Echo Client and Server
These programs, SESERV and SECLIENT, demonstrate the use of nonblocking stream
sockets They use the following WinSock functions: WSAStartup() , WSACleanup() ,
WSAGetLastError() , socket() , closesocket() , bind() , connect() , accept() ,
WSAAsyncSelect() , send() , recv() , getsockname() , ntohs() , and inet_ntoa() The
SESERV server application accepts a connection from a client, receives data, and sends
the data back to the client The SECLIENT client application connects to the server,
sends data to the server, and receives the echoed reply.
Stream Echo Server SESERV
These programs are generated using Visual C++’s AppWizard feature Implementation
is similar to DESERV, with the primary difference being in the CMainView object.
CMainView ’s header is shown in Listing 8.18 Listing 8.19 shows the implementation of
the CMainView object When CMainView::OnInitialUpdate() is called, soon after the
CMainView object is created, it creates a socket, binds it to a name, and waits for a
con-nection request When a concon-nection is requested, data is ready to be received, or data
can be sent, the CMainView::OnAsyncSelect() member function is called due to the
message mapping of the user-defined message WM_USER_ASYNC_SELECT This
stream socket server application communicates with only one client at a time due to the
single m_sClient variable in the CMainView class Once m_sClient is connected, all other
requests to connect are ignored until the connection is closed by the originating client.
Listing 8.18 MAINVIEW.H for SESERV.
// mainview.h : header file
SOCKET m_s; // socket to listen for connections on
SOCKADDR_IN m_addr; // address of socket to listen on
SOCKET m_sClient; // socket to client
continues
Trang 11enum { IDD = IDD_DIALOG_MAIN };
// NOTE: the ClassWizard will add data members here
#define WM_USER_ASYNC_SELECT (WM_USER + 1)
Listing 8.19 MAINVIEW.CPP for SESERV.
// mainview.cpp : implementation file
IMPLEMENT_DYNCREATE(CMainView, CFormView)
CMainView::CMainView()
Listing 8.18 continued
Trang 12char pszMessage[100]; // informational message
// get pointer to list box used for status messages
CListBox *plb = (CListBox *)GetDlgItem(IDC_LIST_STATUS);
// create the socket and prepare it
// for receiving data
// bind the socket, allowing WinSock to assign the service port
m_addr.sin_family = AF_INET; // Internet address family
m_addr.sin_port = 0; // let WinSock assign a port
m_addr.sin_addr.s_addr = htonl(INADDR_ANY); // any network interface
continues
Trang 13if (bind(m_s, (LPSOCKADDR)&m_addr, sizeof(m_addr)) == SOCKET_ERROR) plb->InsertString(0, “Stream echo server could not bind socket”); else
{
// find out the port number WinSock assigned
SOCKADDR_IN addr;
int nAddrLen = sizeof(addr);
if (getsockname(m_s, (LPSOCKADDR)&addr, &nAddrLen) == SOCKET_ERROR) plb->InsertString(0, “Stream echo server could not get socket’s port”); else
{
wsprintf(pszMessage, “Stream echo server using port %d”,
ntohs(addr.sin_port));
plb->InsertString(0, pszMessage);
// do the asynchronous select to wait for a connection
if (WSAAsyncSelect(m_s, m_hWnd, WM_USER_ASYNC_SELECT, FD_ACCEPT) == SOCKET_ERROR)
plb->InsertString(0, “Stream echo server could not do async select”); else
/////////////////////////////////////////////////////////////////////////////// CMainView::OnAsyncSelect()
//
// Receives data from a client and echoes the data back to the sending client.// While there is data yet to be sent back to the sending client, the server// will not receive any more data (A single data buffer is used for
// incoming and outgoing data)
//
LONG CMainView::OnAsyncSelect(WPARAM wParam, LPARAM lParam)
{
char pszMessage[100]; // informational message
static char pBuf[101]; // send/recv buffer
Listing 8.19 continued
Trang 14int nBytesRecv; // number of bytes received
int nBytesSent; // number of bytes sent
static int nTotalBytesToSend;// total number of bytes to send
static int nBytesToSend = 0; // number of bytes to send
int nError; // WinSock error
static SOCKADDR_IN addrFrom; // address of client
static int nAddrFromLen = sizeof(addrFrom); // length of client address struct
static IN_ADDR inFrom; // IP address of client
// get pointer to list box used for status messages
CListBox *plb = (CListBox *)GetDlgItem(IDC_LIST_STATUS);
// check for an error
Trang 15// send the data
nBytesSent = send(m_sClient, &pBuf[nTotalBytesToSend - nBytesToSend], nBytesToSend, 0);
// check for send error
if (nBytesSent == SOCKET_ERROR)
{
// if the error is just that the send would block,
// don’t do anything we’ll get another FD_WRITE soon
nError = WSAGetLastError();
if (nError != WSAEWOULDBLOCK)
{
wsprintf(pszMessage, “Error %d sending data to %s, %d”,
nError, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
wsprintf(pszMessage, “Data sent (%s) to %s, %d”,
pBuf, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
// there are more bytes to send so we’ll trigger
// the FD_WRITE event again
PostMessage(WM_USER_ASYNC_SELECT, m_s,
WSAMAKESELECTREPLY(FD_WRITE, 0));
}
// just in case the FD_READ was called but it didn’t read
// because the buffer still contained data to send
if (nBytesToSend == 0)
PostMessage(WM_USER_ASYNC_SELECT, m_sClient,
Listing 8.19 continued
Trang 16// if there are still bytes waiting to be sent back (echoed)
// to the client, don’t do anything here (the FD_WRITE handler will
// generate FD_READ when it is through with sending)
if (nBytesToSend == 0)
{
// receive data
nBytesRecv = recv(m_sClient, pBuf, 100, 0);
// check for receive error
if (nBytesRecv == SOCKET_ERROR)
{
// if the error is just that the receive would block,
// don’t do anything we’ll get another FD_READ soon
wsprintf(pszMessage, “Data received (%s) from %s, %d”,
pBuf, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
plb->InsertString(0, pszMessage);
// just in case the FD_WRITE was called but it didn’t have
// any data to send at that time (it has data to send now)