// // Text Server Version 1.0, a Windows Sockets Server // // Copyright 1993, Lee Murach // // Permission to use, modify, and distribute this software and its // documentation for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and this permission notice appear in // supporting documentation. Lee makes no claims as to the suitability // of this software for any purpose. // // Module TXTSRV, the only module of the Text Server, (TS) is both the // user interface and the network interface. Since TS is a server, its // 'users' are TS clients, so the user interface requirements are // rather simple. TS simply updates its display with outbound buffer // traffic. We also display winsock errors. // // TS is not a concurrent server; it processes one client request at a // time. This simplifies the design considerably. TS uses // WSAAsyncSelect() to receive the messages that notify of pending // client requests, and prompt TS to deliver a reply. However, TS // switches to synchronous operation when retrieving a request, or // delivering a reply. TS reenables request notification (FD_ACCEPT) // once it has finished its reply. Meanwhile, other connection // requests remain in the listen() queue. // // TS speaks the finger protocol, and will reply to finger (e.g., the // winsock finger 3.x client). We take the lazy way out, and assume // the text file has terminators. (as DOS files do) // // 3/26/93 Lee Murach wrote this thing. // #include #include #include #include #include "winsock.h" #include "txtsrv.h" #define WSVERSION 0x101 // windows sockets version #define SERVERPORT 79 // server listens on this port #define DEFAULTFILE "default.txt" // send this file for null queries #define REQLEN 255 // max length of request string #define XFERBUFLEN 1024 // max # of character for send/recv #define APIENTRY PASCAL #define WNDPROC FARPROC typedef struct // associates messages (or menu ids) { // with a handler function UINT Code; LONG (*Fxn)(HWND, UINT, UINT, LONG); } DECODEWORD; typedef struct // associates an error code with text { UINT err; char *sztext; } ERRENTRY; #define dim(x) (sizeof(x) / sizeof(x[0])) HWND hFrame; // handle of main window HANDLE hInst; // our instance char szAppName[] = "Text Server"; // the server named text SOCKET ListenSocket; // awaits connection requests SOCKET ConnectSocket; // is connected to client char Request[REQLEN + 1]; // holds request from client int ReqLen; // length of request string char Buf[XFERBUFLEN]; // transfer buffer holds in/outbound text int BufLen = 0; // # of chars in xfer buffer BOOL InitApp(HANDLE hInstance); BOOL InitInstance(HANDLE hInstance, int nCmdShow); BOOL FAR APIENTRY AboutDlgProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG FAR APIENTRY FrameWndProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoMenuAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoPaint(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoCommand(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoListen(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoClose(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoDestroy(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoConnection(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoGetRequest(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); LONG DoReply(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam); void SendError(char *errstr); void SendFile(FILE *infile); void Blocking(SOCKET s); void DebugOut(char FAR *lps, ...); void DisplayWSError(void); void CloseSockets(void); void UpdateDisplay(int len); void SizeWindow(); char *WSErrorString(UINT err); #define WM_LISTEN (WM_USER + 1) // listen for connections #define WM_CONNECTION (WM_USER + 2) // connection request awaits #define WM_GETREQUEST (WM_USER + 3) // request is waiting #define WM_REPLY (WM_USER + 4) // start reply DECODEWORD frameMsgs[] = // windows messages & handlers { WM_LISTEN, DoListen, WM_CONNECTION, DoConnection, WM_GETREQUEST, DoGetRequest, WM_REPLY, DoReply, WM_COMMAND, DoCommand, WM_PAINT, DoPaint, WM_CLOSE, DoClose, WM_DESTROY, DoDestroy }; DECODEWORD menuItems[] = // menu items & associated handlers { IDM_ABOUT, DoMenuAbout, 0, 0 }; ERRENTRY WSErrors[] = // error text for windows sockets errors { WSAVERNOTSUPPORTED, "This version of Windows Sockets is not supported", WSASYSNOTREADY, "Windows Sockets is not present or is not responding", }; // // WinMain -- Windows calls this to start the application. // int APIENTRY WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WSADATA WSAData; // windows sockets info return MSG msg; // holds current message int err; hInst = hInstance; // save the instance handle if (hPrevInstance) // only one server, thank you return(FALSE); if (!(InitApp(hInstance) && InitInstance(hInstance, nCmdShow))) return(FALSE); // can't start, so bail if (err = WSAStartup(WSVERSION, &WSAData))// connect to winsock { MessageBox(hFrame, WSErrorString(err), szAppName, MB_ICONSTOP | MB_OK); DestroyWindow(hFrame); // kill application window & } // signal app exit else PostMessage(hFrame, WM_LISTEN, 0, 0); // get ready for clients while (GetMessage(&msg, NULL, 0, 0)) // loop til WM_QUIT { TranslateMessage(&msg); DispatchMessage(&msg); } WSACleanup(); // disconnect from winsock return(msg.wParam); // return to windows } // // InitApp -- initialization for all instances of application. // This registers the main window class. // BOOL InitApp(HANDLE hInstance) { WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wndclass.lpfnWndProc = FrameWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(hInst, "Icon"); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_WINDOW)); wndclass.lpszMenuName = "Menu"; wndclass.lpszClassName = szAppName; return(RegisterClass(&wndclass)); } // // InitInstance -- initializes this instance of app, creates windows. // BOOL InitInstance(HANDLE hInstance, int nCmdShow) { hFrame = CreateWindow( szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (!hFrame) return(FALSE); SizeWindow(); // set window & char sizes ShowWindow(hFrame, nCmdShow); UpdateWindow(hFrame); return(TRUE); // connections } // // SizeWindow -- sets window's character and external dimensions. // void SizeWindow() { HDC hdc; TEXTMETRIC tm; RECT rect; int ychar, xchar; hdc = GetDC(hFrame); SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); GetTextMetrics(hdc, &tm); ychar = tm.tmHeight + tm.tmExternalLeading; xchar = tm.tmAveCharWidth; ReleaseDC(hFrame, hdc); // set initial window width & height in chars GetWindowRect(hFrame, &rect); MoveWindow( hFrame, rect.left, rect.top, 80 * xchar, 24 * ychar + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYMENU), FALSE); } // // FrameWndProc -- callback function for application frame (main) window. // Decodes message and routes to appropriate message handler. If no handler // found, calls DefWindowProc. // LONG FAR APIENTRY FrameWndProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { int i; for (i = 0; i < dim(frameMsgs); i++) { if (wMsg == frameMsgs[i].Code) return(*frameMsgs[i].Fxn)(hWnd, wMsg, wParam, lParam); } return(DefWindowProc(hWnd, wMsg, wParam, lParam)); } // // DoCommand -- demultiplexes WM_COMMAND messages resulting from menu // selections, and routes to corresponding menu item handler. Sends back // any unrecognized messages to windows. // LONG DoCommand(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { int i; for (i = 0; i < dim(menuItems); i++) { if (wParam == menuItems[i].Code) return(*menuItems[i].Fxn)(hWnd, wMsg, wParam, lParam); } return(DefWindowProc(hWnd, wMsg, wParam, lParam)); } // // DoClose -- responds to close message by refusing pending client connects // and terminating connections in progress, then kills window. // LONG DoClose(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { CloseSockets(); DestroyWindow(hWnd); return(FALSE); } // // DoDestroy -- posts a WM_QUIT message to the task's win queue, which // causes the main translate & dispatch loop to exit, and the app to // terminate. // LONG DoDestroy(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { PostQuitMessage(0); return(FALSE); } // // DoPaint -- Paint the client window with the contents of the // transfer buffer. // LONG DoPaint(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { RECT rect; // client window dimensions HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rect); DrawText(hdc, Buf, BufLen, &rect, DT_EXPANDTABS); EndPaint(hWnd, &ps); return(FALSE); } // // DoMenuAbout -- respond to "About..." menu selection by invoking the // "About" dialog box. // LONG DoMenuAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { WNDPROC lpProcAbout; lpProcAbout = MakeProcInstance((WNDPROC)AboutDlgProc, hInst); DialogBox(hInst, "AboutBox", hWnd, lpProcAbout); FreeProcInstance(lpProcAbout); return(FALSE); } // // AboutDlgProc -- callback for the "About" dialog box // BOOL FAR APIENTRY AboutDlgProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { if ((wMsg == WM_COMMAND) && (wParam == IDOK)) // dismiss dialog if OK EndDialog(hWnd, 0); return(FALSE); // otherwise just sit there } // // UpdateDisplay -- display is xfer buffer, so we store the size, and // force a repaint. // void UpdateDisplay(int len) { BufLen = len; InvalidateRect(hFrame, NULL, TRUE); } // // DoListen -- set up a listen socket to listen for client connection // requests. The WM_CONNECTION will signal that such is pending. // LONG DoListen(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { SOCKADDR_IN sin; ListenSocket = ConnectSocket = INVALID_SOCKET; if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { DisplayWSError(); return(FALSE); } sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(SERVERPORT); if (bind(ListenSocket, (LPSOCKADDR) &sin, sizeof(sin))) { DisplayWSError(); return(FALSE); } if (listen(ListenSocket, 5)) { DisplayWSError(); return(FALSE); } WSAAsyncSelect(ListenSocket, hFrame, WM_CONNECTION, FD_ACCEPT); return(FALSE); } // // DoConnection -- opens a connection to requesting client, and // signals interest in data for read. We'll receive a WM_GETREQUEST // message when data has arrived. // LONG DoConnection(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { struct sockaddr sad; int len = sizeof(sad); /* take connection request off queue, and disable further notifications. we can process only one request at a time. */ ConnectSocket = accept(ListenSocket, &sad, &len); WSAAsyncSelect(ListenSocket, hFrame, 0, 0); if (ConnectSocket == INVALID_SOCKET) { DisplayWSError(); return(FALSE); } // notify us when a request (or part of one) has arrived WSAAsyncSelect(ConnectSocket, hFrame, WM_GETREQUEST, FD_READ); return(FALSE); } // // CloseSockets -- shuts down listen, and any transfer in progress. // void CloseSockets(void) { if (ListenSocket != INVALID_SOCKET); closesocket(ListenSocket); if (ConnectSocket != INVALID_SOCKET); closesocket(ConnectSocket); } // // DoGetRequest -- synchronously receives the client's query, and places // it in the request buffer. We expect terminator, but will accept // end-of-stream (zero recv return). We then ask for a write-space-available // notification. // LONG DoGetRequest(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { char *p; int nchars; Request[0] = ReqLen = 0; Blocking(ConnectSocket); // going synchronous now do // until we've received the whole request { nchars = recv(ConnectSocket, (LPSTR) Buf, sizeof(Buf), 0); if (nchars == SOCKET_ERROR) { DisplayWSError(); return(FALSE); } Buf[nchars] = 0; // null terminate the buffer if ((ReqLen += nchars) > REQLEN) // prevent request overflow { SendError("query too long"); // tell client return(FALSE); // request overflow, bail } strcat(Request, Buf); // append to request p = strstr(Request, "\r\n"); // end-of-request? } while (nchars && !p); // stop for either end-of-stream // or terminator if (p) *p = 0; // chop the terminator if (!Request[0]) // translate null request to default strcpy(Request, DEFAULTFILE); // going asychronous now, this is will notify us of when to reply WSAAsyncSelect(ConnectSocket, hFrame, WM_REPLY, FD_WRITE); return(FALSE); } // // DoReply -- synchronously replies to client with either the requested // file or an error text, then reenables accept notifies, so we can // process the next request. // LONG DoReply(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam) { FILE *infile; Blocking(ConnectSocket); // going synchronous now if (infile = fopen(Request, "rb")) // open for read, binary SendFile(infile); else SendError(_strerror(NULL)); // file access error text fclose(infile); closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; // we're ready for the next request now WSAAsyncSelect(ListenSocket, hFrame, WM_CONNECTION, FD_ACCEPT); return(FALSE); } // // Blocking -- sets socket to block. // void Blocking(SOCKET s) { u_long nonblock = FALSE; // yes, we have no bananas WSAAsyncSelect(s, hFrame, 0, 0); // turn off message notifications ioctlsocket(s, FIONBIO, &nonblock); // set socket to blocking } // // SendFile -- sends the infile down the ConnectSocket // stream. // void SendFile(FILE *infile) { int nchars; do { nchars = fread(Buf, 1, sizeof(Buf), infile); if (ferror(infile)) { SendError(_strerror(NULL)); UpdateDisplay(0); return; } if (nchars > 0) { UpdateDisplay(nchars); // buffer changed, so repaint if (send(ConnectSocket, Buf, nchars, 0) == SOCKET_ERROR) { DisplayWSError(); return; } } } while (!feof(infile)); } // // SendError -- sends an error string to client. // void SendError(char *errstr) { static char *prefix = "\r\nserver error: "; send(ConnectSocket, prefix, strlen(prefix), 0); send(ConnectSocket, errstr, strlen(errstr), 0); closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } // // DisplayWSError -- displays the winsock error in the client window. // void DisplayWSError(void) { strcpy(Buf, WSErrorString(WSAGetLastError())); UpdateDisplay(strlen(Buf)); closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } // // WSErrorString -- translates winsock error to appropriate string. // char *WSErrorString(UINT err) { int i; static char szerr[80]; for (i = 0; i < dim(WSErrors); i++) if (err == WSErrors[i].err) return(WSErrors[i].sztext); sprintf(szerr, "Windows Sockets reports error %04x", err); return(szerr); } // // DebugOut -- for outputting debug info to the AUX device (or debugger). // void DebugOut(char FAR *lps, ...) { static char buf[80]; static char FAR *args; args = (char FAR *) &lps + sizeof(lps); wvsprintf(buf, lps, args); OutputDebugString(buf); }