// -*- C++ -*- // generic socket DLL, winsock version // disclaimer: a C programmer wrote this. // $Id: gensock.cpp 1.15 1994/11/23 22:38:10 rushing Exp $ #include #include #include #include extern "C" { #include #include "gensock.h" } #define SOCKET_BUFFER_SIZE 512 /* This is for NT */ #ifdef WIN32 HANDLE dll_module_handle; #define GET_CURRENT_TASK dll_module_handle #define TASK_HANDLE_TYPE HANDLE #define GENSOCK_EXPORT /* This is for WIN16 */ #else HINSTANCE dll_module_handle; #define GET_CURRENT_TASK GetCurrentTask() #define TASK_HANDLE_TYPE HTASK #define GENSOCK_EXPORT _export #endif int init_winsock (void); void deinit_winsock (void); // // // #ifdef _DEBUG void complain (char * message) { OutputDebugString (message); } #else void complain (char * message) { MessageBox (NULL, message, "GENSOCK.DLL Error", MB_OK|MB_ICONHAND); } #endif // // --------------------------------------------------------------------------- // container for a buffered SOCK_STREAM. class connection { private: SOCKET the_socket; char * in_buffer; char * out_buffer; unsigned int in_index; unsigned int out_index; unsigned int in_buffer_total; unsigned int out_buffer_total; unsigned int last_winsock_error; TASK_HANDLE_TYPE owner_task; fd_set fds; struct timeval timeout; public: connection (void); ~connection (void); int get_connected (char * hostname, char * service); SOCKET get_socket(void) { return (the_socket); } TASK_HANDLE_TYPE get_owner_task(void) { return (owner_task); } int get_buffer(int wait); int close (void); int getchar (int wait, char * ch); int put_data (char * data, unsigned long length); int put_data_buffered (char * data, unsigned long length); int put_data_flush (void); }; connection::connection (void) { the_socket = 0; in_index = 0; out_index = 0; in_buffer_total = 0; out_buffer_total = 0; in_buffer = 0; in_buffer = new char[SOCKET_BUFFER_SIZE]; out_buffer = new char[SOCKET_BUFFER_SIZE]; last_winsock_error = 0; } connection::~connection (void) { delete [] in_buffer; } int gensock_is_a_number (char * string) { while (*string) { if (!isdigit (*string)) { return (0); } string++; } return (1); } // // --------------------------------------------------------------------------- // int connection::get_connected (char FAR * hostname, char FAR * service) { struct hostent FAR * hostentry; struct servent FAR * serventry; unsigned long ip_address; struct sockaddr_in sa_in; int our_port; int not = 0; int retval, err_code; unsigned long ioctl_blocking = 1; char message[512]; // if the ctor couldn't get a buffer if (!in_buffer || !out_buffer) return (ERR_CANT_MALLOC); // -------------------------------------------------- // resolve the service name // // If they've specified a number, just use it. if (gensock_is_a_number (service)) { char * tail; our_port = (int) strtol (service, &tail, 10); if (tail == service) { return (ERR_CANT_RESOLVE_SERVICE); } else { our_port = htons (our_port); } } else { // we have a name, we must resolve it. serventry = getservbyname (service, (LPSTR)"tcp"); if (serventry) our_port = serventry->s_port; else { retval = WSAGetLastError(); // Chicago beta is throwing a WSANO_RECOVERY here... if ((retval == WSANO_DATA) || (retval == WSANO_RECOVERY)) { return (ERR_CANT_RESOLVE_SERVICE); } else { return (retval - 5000); } } } // -------------------------------------------------- // resolve the hostname/ipaddress // if ((ip_address = inet_addr (hostname)) != INADDR_NONE) { sa_in.sin_addr.s_addr = ip_address; } else { if ((hostentry = gethostbyname(hostname)) == NULL) { return (ERR_CANT_RESOLVE_HOSTNAME); } sa_in.sin_addr.s_addr = *(long far *)hostentry->h_addr; } // -------------------------------------------------- // get a socket // if ((the_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { return (ERR_CANT_GET_SOCKET); } sa_in.sin_family = AF_INET; sa_in.sin_port = our_port; // set socket options. DONTLINGER will give us a more graceful disconnect setsockopt(the_socket, SOL_SOCKET, SO_DONTLINGER, (char *) ¬, sizeof(not)); // get a connection if ((retval = connect (the_socket, (struct sockaddr *)&sa_in, sizeof(struct sockaddr_in))==SOCKET_ERROR)) { switch ((err_code = WSAGetLastError())) { /* twiddle our thumbs until the connect succeeds */ case WSAEWOULDBLOCK: break; case WSAECONNREFUSED: return (ERR_CONNECTION_REFUSED); break; default: wsprintf(message, "unexpected error %d from winsock\n", err_code); complain(message); return (ERR_CANT_CONNECT); break; } } owner_task = GET_CURRENT_TASK; // Make this a non-blocking socket ioctlsocket (the_socket, FIONBIO, &ioctl_blocking); // make the FD_SET and timeout structures for later operations... FD_ZERO (&fds); FD_SET (the_socket, &fds); // normal timeout, can be changed by the wait option. timeout.tv_sec = 30; timeout.tv_usec = 0; return (0); } // //--------------------------------------------------------------------------- // // The 'wait' parameter, if set, says to return WAIT_A_BIT // if there's no data waiting to be read. int connection::get_buffer(int wait) { int retval; int bytes_read = 0; unsigned long ready_to_read = 0; // Use select to see if data is waiting... FD_ZERO (&fds); FD_SET (the_socket, &fds); // if wait is set, we are polling, return immediately if (wait) { timeout.tv_sec = 0; } else { timeout.tv_sec = 30; } if ((retval = select (0, &fds, NULL, NULL, &timeout)) == SOCKET_ERROR) { char what_error[256]; int error_code = WSAGetLastError(); if (error_code == WSAEINPROGRESS && wait) { return (WAIT_A_BIT); } wsprintf (what_error, "connection::get_buffer() unexpected error from select: %d", error_code); complain (what_error); } // if we don't want to wait if (!retval && wait) { return (WAIT_A_BIT); } // we have data waiting... bytes_read = recv (the_socket, in_buffer, SOCKET_BUFFER_SIZE, 0); // just in case. if (bytes_read == 0) { // connection terminated (semi-) gracefully by the other side return (ERR_NOT_CONNECTED); } if (bytes_read == SOCKET_ERROR) { char what_error[256]; int ws_error = WSAGetLastError(); switch (ws_error) { // all these indicate loss of connection (are there more?) case WSAENOTCONN: case WSAENETDOWN: case WSAENETUNREACH: case WSAENETRESET: case WSAECONNABORTED: case WSAECONNRESET: return (ERR_NOT_CONNECTED); break; case WSAEWOULDBLOCK: return (WAIT_A_BIT); break; default: wsprintf (what_error, "connection::get_buffer() unexpected error: %d", ws_error); complain (what_error); } } // reset buffer indices. in_buffer_total = bytes_read; in_index = 0; return (0); } // //--------------------------------------------------------------------------- // get a character from this connection. // int connection::getchar(int wait, char FAR * ch) { int retval; if (in_index >= in_buffer_total) { if ((retval = get_buffer(wait))) return (retval); } *ch = in_buffer[in_index++]; return (0); } // //--------------------------------------------------------------------------- // FIXME: should try to handle the fact that send can only take // an int, not an unsigned long. int connection::put_data (char * data, unsigned long length) { int num_sent; int retval; FD_ZERO (&fds); FD_SET (the_socket, &fds); timeout.tv_sec = 30; while (length > 0) { if ((retval = select (0, NULL, &fds, NULL, &timeout)) == SOCKET_ERROR) { char what_error[256]; int error_code = WSAGetLastError(); wsprintf (what_error, "connection::put_data() unexpected error from select: %d", error_code); complain (what_error); } num_sent = send (the_socket, data, length > 1024 ? 1024 : (int)length, 0); if (num_sent == SOCKET_ERROR) { char what_error[256]; int ws_error = WSAGetLastError(); switch (ws_error) { // this is the only error we really expect to see. case WSAENOTCONN: return (ERR_NOT_CONNECTED); break; // seems that we can still get a block case WSAEWOULDBLOCK: case WSAEINPROGRESS: break; default: wsprintf (what_error, "connection::put_data() unexpected error from send(): %d", ws_error); complain (what_error); return (ERR_SENDING_DATA); } } else { length -= num_sent; data += num_sent; } } return (0); } // // // buffered output // int connection::put_data_buffered (char * data, unsigned long length) { unsigned int sorta_sent = 0; int retval; while (length) { if ((out_index + length) < SOCKET_BUFFER_SIZE) { // we won't overflow, simply copy into the buffer memcpy (out_buffer + out_index, data, (size_t) length); out_index += (unsigned int) length; length = 0; } else { unsigned int orphaned_chunk = SOCKET_BUFFER_SIZE - out_index; // we will overflow, handle it memcpy (out_buffer + out_index, data, orphaned_chunk); // send this buffer... if ((retval = put_data (out_buffer, SOCKET_BUFFER_SIZE))) { return (retval); } length -= orphaned_chunk; out_index = 0; data += orphaned_chunk; } } return (0); } int connection::put_data_flush (void) { int retval; if ((retval = put_data (out_buffer, out_index))) return (retval); else out_index = 0; return(0); } // //--------------------------------------------------------------------------- // int connection::close (void) { if (closesocket(the_socket) == SOCKET_ERROR) return (ERR_CLOSING); else return (0); } // //--------------------------------------------------------------------------- // we keep lists of connections in this class class connection_list { private: connection * data; connection_list * next; public: connection_list (void); ~connection_list (void); void push (connection & conn); // should really use pointer-to-memberfun for these connection * find (SOCKET sock); int how_many_are_mine (void); void remove (socktag sock); }; connection_list::connection_list (void) { next = 0; } connection_list::~connection_list(void) { delete data; } // add a new connection to the list void connection_list::push (connection & conn) { connection_list * new_conn; new_conn = new connection_list(); new_conn->data = data; new_conn->next = next; data = &conn; next = new_conn; } int connection_list::how_many_are_mine(void) { TASK_HANDLE_TYPE current_task = GET_CURRENT_TASK; connection_list * iter = this; int num = 0; while (iter->data) { if (iter->data->get_owner_task() == current_task) num++; iter = iter->next; } return (num); } // find a particular socket's connection object. connection * connection_list::find (SOCKET sock) { connection_list * iter = this; while (iter->data) { if (iter->data->get_socket() == sock) return (iter->data); iter = iter->next; } return (0); } void connection_list::remove (socktag sock) { // at the end if (!data) return; // we can assume next is valid because // the last node is always {0,0} if (data == sock) { delete data; data = next->data; next = next->next; // 8^) return; } // recurse next->remove(sock); } // // --------------------------------------------------------------------------- // global variables (shared by all DLL users) connection_list global_socket_list; int network_initialized; // //--------------------------------------------------------------------------- // #ifndef WIN32 // the DLL entry routine int FAR PASCAL LibMain (HINSTANCE hinstance, WPARAM data_seg, LPARAM heap_size, LPSTR command_line) { network_initialized = 0; dll_module_handle = hinstance; return (1); } #else extern "C" { INT APIENTRY LibMain (HANDLE hInst, ULONG reason_called, LPVOID reserved) { switch (reason_called) { case DLL_PROCESS_ATTACH: /* init */ dll_module_handle = hInst; break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; default: break; } return (1); } /* * This wrapper is the actual entry point for the DLL. It ensures * that the C RTL is correctly [de]initialized. */ BOOL WINAPI _CRT_INIT (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); BOOL WINAPI dll_entry_point (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { /* Init the C run-time before calling any of your code */ switch (fdwReason) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: if (!_CRT_INIT (hinstDLL, fdwReason, lpReserved)) return (FALSE); else LibMain (hinstDLL, fdwReason, lpReserved); break; case DLL_PROCESS_DETACH: case DLL_THREAD_DETACH: if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved)) return(FALSE); break; } return (TRUE); } } #endif // --------------------------------------------------------------------------- // C/DLL interface // int FAR PASCAL GENSOCK_EXPORT gensock_connect (char FAR * hostname, char FAR * service, socktag FAR * pst) { int retval; connection * conn = new connection; if (!conn) return (ERR_INITIALIZING); // if this task hasn't opened any sockets yet, then // call WSAStartup() if (global_socket_list.how_many_are_mine() < 1) init_winsock(); global_socket_list.push(*conn); if ((retval = conn->get_connected (hostname, service))) { gensock_close(conn); *pst = 0; return (retval); } *pst = (void FAR *) conn; return (0); } // // // int FAR PASCAL GENSOCK_EXPORT gensock_getchar (socktag st, int wait, char FAR * ch) { connection * conn; int retval = 0; conn = (connection *) st; if (!conn) return (ERR_NOT_A_SOCKET); if ((retval = conn->getchar(wait, ch))) return (retval); else return (0); } //--------------------------------------------------------------------------- // // int FAR PASCAL GENSOCK_EXPORT gensock_put_data (socktag st, char FAR * data, unsigned long length) { connection * conn; int retval = 0; conn = (connection *) st; if (!conn) return (ERR_NOT_A_SOCKET); if ((retval = conn->put_data(data, length))) return (retval); return (0); } //--------------------------------------------------------------------------- // // int FAR PASCAL GENSOCK_EXPORT gensock_put_data_buffered (socktag st, char FAR * data, unsigned long length) { connection * conn; int retval = 0; conn = (connection *) st; if (!conn) return (ERR_NOT_A_SOCKET); if ((retval = conn->put_data_buffered (data, length))) return (retval); return (0); } //--------------------------------------------------------------------------- // // int FAR PASCAL GENSOCK_EXPORT gensock_put_data_flush (socktag st) { connection * conn; int retval = 0; conn = (connection *) st; if (!conn) return (ERR_NOT_A_SOCKET); if ((retval = conn->put_data_flush() )) return (retval); return (0); } //--------------------------------------------------------------------------- // // int FAR PASCAL GENSOCK_EXPORT gensock_gethostname (char FAR * name, int namelen) { int retval; if ((retval = gethostname(name, namelen))) { return (retval - 5000); } else return (0); } //--------------------------------------------------------------------------- // // int FAR PASCAL GENSOCK_EXPORT gensock_close (socktag st) { connection * conn; int retval; conn = (connection *) st; if (!conn) return (ERR_NOT_A_SOCKET); if ((retval = conn->close())) return (retval); global_socket_list.remove((connection *)st); if (global_socket_list.how_many_are_mine() < 1) { deinit_winsock(); } return (0); } //--------------------------------------------------------------------------- // // int init_winsock(void) { int retval; WSADATA winsock_data; WORD version_required = 0x0101; /* Version 1.1 */ retval = WSAStartup (version_required, &winsock_data); switch (retval) { case 0: /* successful */ break; case WSASYSNOTREADY: return (ERR_SYS_NOT_READY); break; case WSAEINVAL: return (ERR_EINVAL); break; case WSAVERNOTSUPPORTED: return (ERR_VER_NOT_SUPPORTED); break; } network_initialized = 1; return (0); } void deinit_winsock(void) { network_initialized = 0; WSACleanup(); }