[Sockets] Server i konkurencja klientów

0

Czy następujące zachowanie jest standardowe?

Serwer nasłuchuje, odbiera komunikaty (socket TCP Stream) od klientow.

Klient A wysyla dlugi komunikat do serwera, w tym samym momencie klient B wysyla komunikat do serwera, serwer przerywa w polowie odbieranie komunikatu od A, odbiera od B i dopiero potem kontynuuje odbior od A. Komunikat od A podzielony zostaje na 2 częsci, zachowuja sie jak osobne komunikaty.

Problem jest taki ze trzeba implementowac mechaznim wykrywania niepelnych msgy sieciowych itp, czy taki tryb pracy jest poprawny? Czy sa sposoby aby zapewnic zeby socket serwerowy zawsze odbieral pelne msge?

Dodam ze message sa dlugosci od 10 do 2000 znaków (1 znak kodowany na 1KB).

0

Nie wiem czy o to chodzi ale seerwer wielowatkowy powinien byc odpowiedzia na dany problem :P

#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <string>

#define MAX_CLIENTS		20

using namespace std;

// kontekst klienta
struct ClientContext
{
	HANDLE thread;
	SOCKET sock;
	int	 id;
};

// socket serwera
SOCKET			sock;
// klienci
ClientContext	clients[MAX_CLIENTS];

void ShutdownServer()
{
	// zamykamy niezamknięte socket'y klientów
	for (int i = 0; i < MAX_CLIENTS; i++)
		if (clients[i].sock != INVALID_SOCKET)
		{
			closesocket (clients[i].sock);
			clients[i].sock = INVALID_SOCKET;
		}

	closesocket (sock);
	sock = INVALID_SOCKET;
}

DWORD WINAPI processClient (LPVOID ctx)
{
	ClientContext *context = (ClientContext*)ctx;
	string message = "Serwer wersja 1.0, witam\n\r";
	string inputString;
	char inputBuffer[513];
	int ret;
	char *ptr;

	cout << "Klient o ID " << context->id << " podlaczony!\n";

	// wysyłamy komunikat
	send(context->sock, message.c_str(), message.size()+1, 0);

	memset(inputBuffer, 0, 513);
	inputString = "";

	while (1)
	{
		ret = recv(context->sock, inputBuffer, 512, 0);

		if (ret == 0)
		{
			// połączenie zamknięte po stronie klienta
			cout << "Klient zamknął połączenie z serwerem.\n";
			break;
		}
		else if (ret == SOCKET_ERROR)
		{
			break;
		}

		// sprawdzamy czy odebralismy znak konca linii
		if((ptr = strchr(inputBuffer, '\n')) != NULL)
		{
			// jezeli tak to wstawiamy terminatora
			ptr --;
			*ptr = 0;

			// dodajemy do wejsciowego stringa
			inputString += inputBuffer;

			// sprawdzamy jaka komenda zostala wyslana
			if (inputString == "exit")
			{
				cout << "Klient prosi o rozlaczenie\n";
				closesocket (context->sock);
				context->sock = INVALID_SOCKET;
				break;
			}
			else if (inputString == "kill server")
			{
				// jezeli klient chce wylaczyc serwer to bezposrednio zamykamy
				// scketa z ktorego korzysta serwer
				cout << "Klient prosi o zamkniecie serwera\n";
				ShutdownServer();
				break;
			}
			else
			{
				// jezeli klient nie wyslal zadnej z powyzszych komend odpisujemy mu tym samym
				message = string("Napisales: ") + inputString + "\n\r";
				send(context->sock, message.c_str(), message.size()+1, 0);
			}

			// czyscimy bufory
			memset(inputBuffer, 0, 512);
			inputString = "";
		}
		else
		{
			// jezeli nie ma konca linii to dopisujemy nowe dane do bufora wejsciowego
			// a nastepnie "doklejamy" do bufora komendy
			inputBuffer[ret] = 0;
			inputString += inputBuffer;
		}
	}

	context->thread = NULL;
	return 0;
}

int main(int argc, char* argv[])
{
	WSADATA			wsaData;
	SOCKADDR_IN 	saddr;
	SOCKET			client;
	int				processConnections = 5;
	int				newId;

	// zerujemy konteksty klientów
	for (int i = 0; i < MAX_CLIENTS; i ++)
	{
		clients[i].thread = NULL;
		clients[i].sock   = INVALID_SOCKET;
		clients[i].id     = -1;
	}

	// uruchamiamy serwer, to juz umiemy
	WSAStartup( MAKEWORD(2,2), &wsaData );

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	memset( (void*)&saddr, 0, sizeof(saddr) );
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(10000);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if ( bind(sock, (sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR )
	{
		cout << "Wystąpił błąd podczas bindowania adresu!\n";
		return -1;
	}

	if (listen(sock, MAX_CLIENTS) == SOCKET_ERROR)
	{
		cout << "Ustawienie gniazda w tryb nasłuchiwania nie powiodło się\n";
		closesocket (sock);
		return -2;
	}

	while (1)
	{
		// akceptujemy nowego klienta
		client = accept(sock, NULL, NULL);

		if (client == INVALID_SOCKET)
		{
			// jeżeli dostajemy WSAENOTSOCKET to znaczy, ze ktorys z klientow zamknal
			// naszego nasluchujacego socketa
			if (WSAGetLastError() == WSAECONNRESET)
			{
				cout << "WSAECONNRESET\n";
			}
			else
				break;
		}
		else
		{
			// szukamy wolnego slota
			newId = -1;
			for (int i = 0; i < MAX_CLIENTS; i ++)
				if (clients[i].sock == INVALID_SOCKET)
				{
					newId = i;
					break;
				}

			if (newId == -1)
			{
				cout << "Serwer nie obsluguje wiekszej ilosci klientow jednoczesnie niz " << MAX_CLIENTS << endl;
			}
			else
			{
				// dodajemy nowego klienta i startujemy wątek
				clients[newId].sock   = client;
				clients[newId].id     = newId;
				clients[newId].thread = CreateThread (NULL, 0, processClient, (LPVOID)&clients[newId], 0, NULL);

				// utworzenie wątku nie powiodło się
				if (clients[newId].thread == NULL)
				{
					clients[newId].sock = INVALID_SOCKET;
					clients[newId].id   = -1;
					cout << "Utworzenie watku dla klienta nie powiodlo sie." << endl;
				}
			}
		}
	}

	// serwer zakonczyl dzialanie, tworzymy tablice uchwytów wątków
	HANDLE threads[MAX_CLIENTS];
	int threadsCount = 0;

	// uzupełniamy tablicę
	for (int i = 0; i < MAX_CLIENTS; i++)
		if (clients[i].thread != NULL)
		{
			threads[threadsCount] = clients[i].thread;
			threadsCount ++;
		}

	// czekamy 5 sekund i w ostatecznosci zabijamy watki
	if (WaitForMultipleObjects(threadsCount, threads, true, 5000) == WAIT_TIMEOUT)
		for (int i = 0; i < MAX_CLIENTS; i ++)
			TerminateThread (threads[i], 2);

	WSACleanup();
	system("pause");
	return 0;
}
0

No wlasnie, tylko uzycie takiej klasy ktora podales w aplikacji ktora przesyla siecią msge o dlugosci 4000 znakow bardzo czesto, zakladajac ze serwer obsluguje 200 - 300 klientow to masz pewne ze te stringi ktore zczytujesz nie bedą w całości...

Innymi slowy Client A wysyła "ALA MA KOTA", Client B wysyła "TOLA MA PSA" (w tym samym czasie), serwer odbiera: "ALA" "MA" "TOLA" "KOTA" "MA PSA"...

I teraz zabawa w wykrywanie kawałkow... poza tym zabawa w wielowątkową obsługę, osobne watki polaczen przychodzących, osobny dla wysylania i osobny dla odbierania (kolejki).

Nie no ogolnie ciekaw jestem czy bagno w które brnę ma sens :]... czy tak sie to wlasnie robi...

0

IMHO nie ma sensu. Jak chcesz, żeby coś szło równolegle otwierasz dodatkowe połączenie i piszesz równolegle do dwóch połączeń. Nie wiem po co ci pisanie tylko do jednego połączenia; na pewno to nie będzie wydajne ani skalowalne. Poza tym jak zamierzasz wykryć poszczególne kawałki? Jak będziesz wysyłał informacje o poszczególnych kawałkach to też wątek może się przełączyć, chyba że zapewnisz odpowiednią synchronizację. Dla mnie totalny bezsens zwłaszcza, że istnieje dobre rozwiązanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1