#1
  1. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95

    keeping multiple tcp connections open after one closes


    i have this chat program. clients connect to the server, and the server broadcasts messages that it recieves. the server is multithreaded, and the clients are single threaded. my problem is that when one client logs off the server starts getting errors. i think it closes the socket when one client signs off, anyway, here is the server side code, (thanks for any help i know its a big chunk of stuff to look at):
    Code:
    /*****************************************
    **		chat_server_main.cpp			**
    **										**
    **		server for the chat program		**
    **		originally started on 5/25/03   **
    *****************************************/
    
    #include <stdio.h>
    #include <winsock.h>
    #include <process.h> 
    #include "my_time.h"
    
    #define		NO_FLAGS_SET    0
    #define		PORT            6969
    #define		MAX_CLIENTS		25
    #define		BUFFER			250
    #define		NAME_LEN		15
    #define		LIST_SIZE		400
    
    /* STRUCTURES */
    struct message
    {
    	char	data[BUFFER];
    	char	name[NAME_LEN];
    	bool	list_data_flag;
    	char	room_array[LIST_SIZE];
    };	
    
    struct client_data
    {
    	SOCKET			client_sock;
    	SOCKADDR_IN		client_addr;
    };
    
    
    /* GLOBAL VARIABLES */		
    int					num_clients = 0;			//keep track of how many clients connected
    CRITICAL_SECTION	critSec;					//used for printf and global variable incrementing
    char				room_list[LIST_SIZE];		//list of people in the room
    SOCKET				client_list[MAX_CLIENTS];	//handles to the sockets
    int					thread_id = 0;
    
    
    /*  THREAD FUNCTION PROTOTYPES */
    void talkToClient(void *cs);
    void send_list(void *ptr);
    
    
    /*  MAIN  */
    int main()
    {
    	WSADATA		wsaData;				
    	SOCKET		hListenServSocket;	
    	SOCKADDR_IN	serverSockAddr;	
    	
    	int			addrLen = sizeof(SOCKADDR_IN),	status = 0;	
    	DWORD			threadID,	list_threadID;						
    	time_freeze	da_time;
    	
    	InitializeCriticalSection(&critSec);	
    	
    	// 1. Initialize WSA, the Window Socket API
    	if( ( status = WSAStartup ( MAKEWORD(1, 1), &wsaData )) != 0) {return 1;}
    	
    	// 2. Create a socket
    	if ((hListenServSocket = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    	{
    		if ((status = WSACleanup()) == SOCKET_ERROR) printf("Error in creating socket\n");
    		return 1 ;
    	}
    	
    	// 3. Initialize server socket info 
    	memset(&serverSockAddr, 0, sizeof(serverSockAddr));		//set up server info
    	serverSockAddr.sin_family       = AF_INET;				
    	serverSockAddr.sin_addr.s_addr  = htonl(INADDR_ANY);	   
    	serverSockAddr.sin_port         = htons(PORT);		// host to network 
    	
    	// 4. Bind the socket with the address
    	status = bind (hListenServSocket, (LPSOCKADDR) &serverSockAddr,sizeof(serverSockAddr));
    	if (status == SOCKET_ERROR) { printf("Binding error\n"); return 1;}
    	
    	// 5. Listen for connections
    	if ((status = listen ( hListenServSocket, MAX_CLIENTS )) == SOCKET_ERROR) 
    	{ printf("Listen error\n"); return 1;}
    	
    	/* display startup info to the user */
    	gimme_time(da_time, true);
    	printf("[][][][]***(( Chat server now listening on port %u ))***[][][][]\n",PORT);
    	printf("[][][][]***(( Serving a maximum of %u   clients      ))***[][][][]\n",MAX_CLIENTS);
    	
    	SOCKET		hNewServSocket;			//client socket
    	SOCKADDR_IN	clientSockAddr;			// the clients address structure
    	client_data	client_handle[MAX_CLIENTS];
    	bool			start_list_thread = true;
    	
    	while(1)
    	{
    		// 6. Accept connection request when one received
    		hNewServSocket = accept (hListenServSocket, (LPSOCKADDR) &clientSockAddr, &addrLen);
    		if (hNewServSocket == INVALID_SOCKET) { printf("Accept error\n"); return 1; }
    		printf("\nClient accepted, now serving %u clients", num_clients + 1);
    		
    		// add client to list; setup struct to pass to thread
    		client_list[num_clients] = hNewServSocket;
    		client_handle[num_clients].client_addr = clientSockAddr;
    		client_handle[num_clients].client_sock = hNewServSocket;
    		
    		if ((threadID = _beginthread (talkToClient, 0, (void *) &client_handle[num_clients])) == -1)
    		{ 
    			printf("Thread creation error\n");
    			status = closesocket(hNewServSocket);
    			if (status == SOCKET_ERROR) printf("Socket closing error\n");
    		}
    		
    		if(start_list_thread)
    		{
    			if ((list_threadID = _beginthread (send_list, 0, NULL)) == -1)
    			{ 
    				printf("List thread creation error\n");
    				status = closesocket(hNewServSocket);
    				if (status == SOCKET_ERROR) printf("Socket closing error #%u\n", WSAGetLastError());
    			}
    			start_list_thread = false;
    		}	  
    	} /* while */	
    	
    	return 1;
    }
    
    // talkToClient()  - handles each client - recieves their message & broadcasts it to all clients
    void talkToClient(void *cs)
    {
    	bool	exit = false, first_connect = true;
    	int		nBytesSent = 0, nBytesRecvd = 0, myID = 0;										
    	message   incoming_data, outgoing_data;
    	client_data *ptr = (struct client_data *)cs;		
    	SOCKET  hNewServSocket = (SOCKET)ptr->client_sock;	
    	
    	// increment # of threads and clients
    	thread_id++;
    	num_clients++;
    	myID = thread_id;
    	
    	printf("\nThread %u has started\n", myID);
    	
    	//initialize data structures
    	incoming_data.list_data_flag = false; outgoing_data.list_data_flag = false;
    	strcpy(incoming_data.data,""); strcpy(incoming_data.name,""); strcpy(incoming_data.room_array,"");
    	strcpy(outgoing_data.data,""); strcpy(outgoing_data.name,""); strcpy(outgoing_data.room_array,"");
    	
    	// enter teh send recieve/loop	
    	while(!exit)	
    	{
    		// (a) Receive 
    		if (((nBytesRecvd = recv(hNewServSocket,(char *) &incoming_data, 
    			sizeof incoming_data,NO_FLAGS_SET)) == 0) || (nBytesRecvd == SOCKET_ERROR))
    		{
    			if(nBytesRecvd == 0){ printf("Gracefully shutting down...\n");}
    			else
    			{
    				EnterCriticalSection(&critSec);
    				printf("recv: Thread %u Failed with error #%u\n", myID, WSAGetLastError());
    				LeaveCriticalSection(&critSec);
    			}
    			exit = true;
    			break;
    		}
    		
    		// if this is first time connecting, then log the client's info
    		if(first_connect)
    		{
    			
    			printf("Client name is %s , client address is %x\n",incoming_data.name , 
    				ptr->client_addr.sin_addr);
    			
    			//add teh client to the room list now that we have their nick name
    			strcat(room_list, incoming_data.name); strcat(room_list, "-");
    			first_connect = false;
    		}
    		
    		// (b) Process
    		strncpy(outgoing_data.name,incoming_data.name,NAME_LEN);
    		strncpy(outgoing_data.data,incoming_data.data,BUFFER); 
    		outgoing_data.list_data_flag = false;
    		strcpy(outgoing_data.room_array, ""); 
    		
    		// (c) Broadcast message to all of the clients logged in
    		for(int y = 0; y < num_clients;y++)
    		{
    			nBytesSent = send(client_list[y],(char *) &outgoing_data, 
    				sizeof(outgoing_data),NO_FLAGS_SET);
    			
    			if ((nBytesSent == 0) || (nBytesSent == SOCKET_ERROR))
    			{
    				if(nBytesSent == 0){ printf("Gracefully shutting down...\n");}
    				else
    				{
    					EnterCriticalSection(&critSec);
    					printf("send: Thread %u Failed with error #%u\n", myID, WSAGetLastError());
    					LeaveCriticalSection(&critSec);	
    				}	
    				exit = true;
    				break;
    			}
    		}
    		
    		printf("\nThread %u is broadcasting\n", myID);
    	} // while end	 
    	
    	//remove the client from the client_array
    	for(int m = 0; m < num_clients; m++)
    	{
    		if(client_list[m] == hNewServSocket)
    		{
    			client_list[m] = client_list[num_clients - 1];
    			client_list[num_clients - 1] = NULL;
    		}
    	}
    	
    	//decrement the # of clients connected
    	num_clients--;
    	
    	// terminate the connection with the client
    	printf("%s has disconnected...Connection being closed\n",incoming_data.name);
    	shutdown    ( hNewServSocket, 1);
    	closesocket ( hNewServSocket );
    	
    	printf("Thread %u has ended\n", myID);
    }		
    
    /*  send_list this thread sends out the list once every 5 seconds only if the list has changed	**
    **  size since the last time.																	*/
    
    void send_list(void *ptr)
    {
    	char old_room_list[LIST_SIZE]; 
    	int bytes_sent = 0;
    	message list_out;
    	
    	strcpy(old_room_list, room_list);						// get a copy of the list
    	printf("List thread starting\n");
    	
    	while(num_clients > 0)		//while we have clients
    	{
    		Sleep(5000);			// once every 5 seconds
    		if((strcmp(old_room_list, room_list)) != 0)			// list is either bigger/smaller
    		{
    			strcpy(old_room_list, room_list);				// update our copy of the list
    			list_out.list_data_flag = true;					// set flag for client 
    			strcpy(list_out.room_array,room_list);			// setup the data packet 
    			for(int y = 0; y < num_clients; y++)			// send to all clients
    			{
    				bytes_sent = send(client_list[y], (char *) &list_out,
    					sizeof (list_out), NO_FLAGS_SET);
    				if ((bytes_sent == 0) || (bytes_sent == SOCKET_ERROR))
    				{
    					// bytes_sent = 0 indicates  graceful shutdown.
    					EnterCriticalSection(&critSec);//mistah Marshall Brain we do
    					printf("Failed with error #%u\n", WSAGetLastError());
    					LeaveCriticalSection(&critSec);		
    				} // end if
    			}	//end for
    		}	//end if
    	}	// end while
    	
    	printf("List thread ending\n");
    }
    Last edited by infamous41md; May 27th, 2003 at 09:12 PM.
  2. #2
  3. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,158
    Rep Power
    2222
    It looks to me that the server should have exited with an error right after the first client connects. After the first accept and thread creation, you are not going back to listen, but rather you immediately go back to accept. accept returns INVALID_SOCKET on error, which you test for and exit out of the server. Yet you say that's not how it's going down.

    If accept is not returning INVALID_SOCKET, then it would appear that your while(1) loop would just keep churning away creating more and more threads all trying to use that same socket created by the accept(). Then when you close the socket in one of the threads, the others do not have a valid socket. I don't know what error messages you're getting, but I assume it's something about an invalid socket.

    Also, you are not listening for further connects, so no other client will be able to connect.

    Suggestion:
    Move the listen call into the while(1) loop as the first thing it does. Then when you accept, you create a thread for that client and return to listen. Since each thread declares its own socket to which you copy the socket that accept returns, the client socket variable in main() should be reusable.

    Enhancement suggestion:
    Instead of just passing the socket to the thread, create a struct that contains the socket and the client's SOCKADDR_IN. Then one of the things the thread could do would be to display a report identifying the client who just connected and then also report when that client disconnects. This could be modified to a log-file or accounting file entry.
  4. #3
  5. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95
    i made some changes to teh above code. still though i am getting the same problem. so i decided instead of reusing the same client socket variable, to make an array of them and use a new one each time. still the same thing is happening, only slightly different now. i've been trying to debug it, and from what i can tell, what happens is that recv() returns a 0 and then it closes down. the only problem is that im not sure which thread i am looking at when this happens? how is one supposed to debug multi threaded apps?

    edit: now this is just odd. if i start it up with 5 or 6 clients logged on, then the following happens:
    -if one of the last clients to log on decides to disconnect, then everything works fine.
    -if the first or second client to log on decides to disconnect, then everything gets fuxx0red.

    also, what would be a good way to exit the program? after all the threads finish the program just sits there. it's somewhere in the while loop waiting for a new client.
    Last edited by infamous41md; May 25th, 2003 at 08:21 PM.
  6. #4
  7. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,158
    Rep Power
    2222
    Well, I keep hearing that debugging multi-threaded programs can be difficult. I don't have any direct experience with it, but I have debugged embedded programs.

    I would suggest using debug statements, AKA "trace statements". Print to the screen statements of where in the program it's being called from and what certain key variable values are. Then you can see from the debug statements exactly what happened when. You can start off with a few debug statements scattered where you think the problem might be. Then add statements around where things appear to be going wrong.

    I suggested that you pass a struct to the thread instead of just the socket. You should add an auto-increment serial number to main() and have it assign that number to the new thread and increment it. Also have main() output a value for the client socket -- it's simply an integer in Linux and I think it's also an integer in Winsock; you might have to play around with this one.

    Every thread should emit a debug statement when it starts, encounters an error condition, and exits. In each one, it should output its serial number. The start statement should also report the client address and the SOCKET value (after having been assigned to the local SOCKET variable). The exit statement should also report the local SOCKET value. Hopefully that would help to settle some questions about socket assignment.


    As for a good way to exit the program, you could try running yet another thread that looks for keyboard input. When the user enters a command to terminate the server, then it can shut everything down in an orderly fashhion -- for that, you may need to keep track of all the threadID's.

    Suggestion:
    Do the keyboard input in the main function. Put the listen/accept logic in a thread that is the only thread that main() starts. Then that thread will create all the client-handling threads.
  8. #5
  9. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95
    ok thanks, that sounds like good advice to me. but what are, "embedded programs?"
  10. #6
  11. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,158
    Rep Power
    2222
    Originally posted by infamous41md
    ok thanks, that sounds like good advice to me. but what are, "embedded programs?"
    The first applications for microprocessors involved embedding them into other devices as a replacement for discrete logic networks. We continue that practice, such that these microprocessors are found in almost every appliance we have, including TVs, microwave ovens, stove, washing machines, and (several) in your car. The programs for these embedded microprocessors are called "embedded programs".

    If you have ever or will ever study logic hardware design, you will see that it can take a rather complex network of many (read tens and hundreds) AND, OR, and NOT gates (and several hybrids thereof) to create a circuit that will output complex digital signals in response to a large set of input conditions. And the task of maintaining an existing logic circuit or modifying it to include additional inputs or different outputs can be daunting.

    A microprocessor and a small amount of "glue logic" circuitry replaced all that hard-wired circuitry, leading to size and cost reduction by the elimination of parts and reducing the cost of design and maintenance. And it opened the door to creating even more complex control logic.

    Now more recently, we've returned to complex logic circuits, only they are developed through software running on workstations and loaded into a single IC device called a "field-programmable gate array" (FPGA).

    The embedded software I develop handles the overall sequence of control, performs I/O into and out of the device, and interfaces with the FPGA. Since I test my code on production hardware, the only way I can debug the code is to observe how the device operates, have it output debug messages, and read through the code. The most productive way to get debug information out of the device is through the debug messages, so I've had an awful lot of experience with that method.
  12. #7
  13. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95
    wow that's pretty cool. i've always wanted to know how to hack my car LCD display and have it say funny stuff, you can do that? :D . i did a chapter or 2 on logic gates, it was pretty interesting stuff b/c u could actually see what's really going on in there.
  14. #8
  15. Contributing User
    Devshed Supreme Being (6500+ posts)

    Join Date
    Jan 2003
    Location
    USA
    Posts
    7,158
    Rep Power
    2222
    I feel fortunate that I got into computers via the hardware -- Air Force training as a computer repairman. Then I read the classic SAMS "bug books" on the 8080A, which was THE Intel microprocessor at the time (circa 1978). Then with that background to start with, I earned my computer science degree. I think that knowing about the hardware and how it works gave me a different perspective than the other students, for whom it must have all been very abstract.

    So my hardware knowledge makes me a good candidate for embedded programming; the vast majority of my professional work has been embedded. In one job, the EE had very little experience with digital ICs, so I ended up doing most of the interface designs.

    As for hacking your LCD display, that would take a bit of work. Though the fonts are stored in ROM as is the application code of the device talking to it. You can disassemble the code, but it's still a lot of work. I'll leave that project for you [grin].

    Let me know how you solve the TCP problem.
  16. #9
  17. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95
    the way this program behaves confuses me. if the first client to logon disconnects, then all future requests fail with WSAENOTSOCK. but if instead say the third or fourth client to log on disconnects, nothing out of the ordinary happens. very confusing. i'll have to keep trying i guess, i 'll let u know how it turns out.

    EDIT: of course, the problem was some faulty logic on my part. the problem was that after a client left i never removed them from the array that was used to broadcast messages to all the clients. then when the next client sent a message to the server, it would try to broadcast that message to all the clients, including the one who was not signed on, so it then gave me the "NOT A SOCKET ERROR."
    question: are u aware of any libraries that take a WSA Error message and output a nice text description of the error? if not i think im going to have to make one :)
    Last edited by infamous41md; May 27th, 2003 at 08:45 PM.
  18. #10
  19. Banned ;)
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2001
    Location
    Woodland Hills, Los Angeles County, California, USA
    Posts
    9,625
    Rep Power
    4247
    You can use WSAGetLastError() and pass the error code to FormatMessage(). However, there are some problems with this approach, on some versions of Windoze (actually, the version of Winsock, to be precise). For a better solution, you might want to write your own error translator. Better still, use someone else's work, like this:
    http://www.beginthread.com/Article/E...%20Translator/

    Hope this helps :)
    Last edited by Scorpions4ever; May 27th, 2003 at 09:04 PM.
  20. #11
  21. not a fan of fascism (n00b)
    Devshed Frequenter (2500 - 2999 posts)

    Join Date
    Feb 2003
    Location
    ct
    Posts
    2,756
    Rep Power
    95
    scorpions<< cool thanks, exactly what i was looking for. i figured that there had to be someone else who found those cryptic error #'s annoying and did something about it.

    edit: the code in my first post now has the completed and correct code if anyone was curious.
    Last edited by infamous41md; May 27th, 2003 at 09:28 PM.

IMN logo majestic logo threadwatch logo seochat tools logo