[winsock] prosty czat

0

Witam

Jak można zrobić, aby do serwera połączyło się dwóch lub więcej klientów, i każdy z nich widział co piszą inni, tak jak czat. http://winapi.org/index.php?option=content...d=128&Itemid=36 Na tej stronie jest przykad, i chodzi mi o to jak go ulepszyć aby każdy widział co inni piszą (taki prosty czat).
Wiem że chyba trzeba użyć wątków, ale nie wiem jak. Pomóżcie.

0

Musisz do zrobic na 'watkach' tzn kazdemu nowemu klientowi serwer bedzie przydzielal nowe gniazdo, dzieki temu naraz bedzie moglo sie polaczyc z serwerem kilkanascie osob. W watku pisze sie instrukcje, operacje ogolne dla klienta.

Tu masz kodzik serwerka opartego na watku (przeanalizuj go sobie)

#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>

#define DEFAULT_PORT 447
#define DEFAULT_BUFFER 4096

//wątek do komunikacji z klientem
DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET sock = (SOCKET)lpParam;
char szBuf[DEFAULT_BUFFER];

while(1)
{

/tutaj sobie piszesz co ma robic po polaczeniu klienta... czyli odbieranie, przetwarzanie pakietow itd.../
}
return 0;
}

int main()
{
WSADATA wsd;
SOCKET sListen,
sClient;
int iAddrSize;
HANDLE hThread;
DWORD dwThreadID;
struct sockaddr_in local, client;
struct hostent *host = NULL;

// inicjuj Winsock 2.2
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Bladd ladowania Winsock 2.2!\n");
return 1;
}

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sListen == SOCKET_ERROR)
{
printf("Blad funkcji socket(): %d\n", WSAGetLastError());
return 1;
}

local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(DEFAULT_PORT);
if (bind(sListen, (struct sockaddr *)&local, sizeof(local)) == SOCKET_ERROR)
{
printf("Blad funkcji bind(): %d\n", WSAGetLastError());
return 1;
}

host = gethostbyname("localhost");
if (host == NULL)
{
printf("Nie udalo sie wydobyc nazwy serwera\n");
return 1;
}

listen(sListen, 8);
printf("SERWER NASLUCHUJE\n");
printf("Adres: %s, port: %d\n", host->h_name, DEFAULT_PORT);

/* tutaj akceptuje nadchodzace polaczenia i kazdemu polaczeniowi przydziela nowe gniazdo i wykonywane sa operacje w watku...*/
while (1)
{
iAddrSize = sizeof(client);
sClient = accept(sListen, (struct sockaddr *)&client, &iAddrSize);
if (sClient == INVALID_SOCKET)
{
printf("Blad funkcji accept(): %d\n", WSAGetLastError());
return 1;
}
printf("Zaakceptowano polaczenie: serwer %s, port %d\n",
inet_ntoa(client.sin_addr), ntohs(client.sin_port));

//tutaj jest tworzony watek dla nowego polaczenia
hThread = CreateThread(NULL, 0, ClientThread,
(LPVOID)sClient, 0, &dwThreadID);
if (hThread == NULL)
{
printf("Blad funkcji CreateThread(): %d\n", WSAGetLastError());
return 1;
}
CloseHandle(hThread);
}
closesocket(sListen);

WSACleanup();
return 0;
}

Kodzik powinien byc poprawny - skopiowalem go z mojego starego kodu zrodlowego, niechcialo mi sie go sprawdzac ale powinnien dzialac :):) pozdrawiam

0

A jak wątków można użyć w c++ builderze? Oto cały kod serwera:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <winsock2.h>
#include <windows.h>

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;


SOCKET nasluchujacy, polaczony;

struct sockaddr_in sin;

int rozmiar;


struct OdbPak {
char odbiorcza[250];
char nick[20];
} odbierz;


String temp;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::procka(TMessage &Msg)
{


  switch (Msg.Msg)
  {
        case WM_USER:
                switch (WSAGETSELECTEVENT(Msg.LParam))
                {
                        case FD_ACCEPT:
                        polaczony = accept(nasluchujacy,(struct sockaddr *)&sin, &rozmiar);
                        W_ID = BeginThread(NULL, 0, ClientThred, this, 0, W_PD);
                        break;

                        case FD_READ:
                        recv(polaczony,(char*)&odbierz,sizeof(odbierz),0);
                        temp = odbierz.nick;
                        temp += ": ";
                        temp += odbierz.odbiorcza;
                        RichEdit1->Lines->Add(temp);
                        break;

                        case FD_CONNECT:

                        break;

                        case FD_WRITE:
                        break;
                }

        break;
  }

}



void __fastcall TForm1::Button5Click(TObject *Sender)
{
int blad;
WORD wersja;
WSADATA wsaData;
wersja = MAKEWORD( 2, 0 );
blad = WSAStartup(wersja, &wsaData );


if ( blad != 0 )
{
MessageBox(Handle,"error WSA","ERROR",MB_OK);
WSACleanup();
}


if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 )
{
MessageBox(Handle,"error zła wersja winsocket","ERROR",MB_OK);
WSACleanup();
}


nasluchujacy =  socket( AF_INET, SOCK_STREAM, 0 );

memset( &sin, 0, sizeof(sin) );
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(6666);
rozmiar = sizeof(sin);

if ( bind(nasluchujacy,(struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR )
{
MessageBox(Handle,"bind - błąd","błąd",MB_OK);
WSACleanup();
}



if (listen(nasluchujacy,SOMAXCONN)==SOCKET_ERROR)
{
MessageBox(Handle,"error listen","ERROR",MB_OK);
WSACleanup();
}

WSAAsyncSelect(nasluchujacy,Handle,WM_USER,FD_ACCEPT|FD_CONNECT|FD_READ|FD_WRITE);
        
}
//---------------------------------------------------------------------------
0

wklejasz na poczatku sobie to:

DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET sock = (SOCKET)lpParam;
char szBuf[DEFAULT_BUFFER];

while(1)
{

/tutaj sobie piszesz co ma robic po polaczeniu klienta... czyli odbieranie, przetwarzanie pakietow itd.../
}
return 0;
}

a potem dajesz 2 zmienne globalne:

SOCKET klient;
HANDLE hThread;
DWORD dwThreadID;

no i tam pozniej do nasluchiwania dajesz:

kient = WSAAsyncSelect(nasluchujacy,Handle,WM_USER,FD_ACCEPT|FD_CONNECT|FD_READ|FD_WRITE);

hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)klient, 0, &dwThreadID);
if (hThread == NULL)
{
MessageBox(Handle,"error - watek","ERROR",MB_OK);
}
CloseHandle(hThread);

i powinno dzialac tylko nie jestem pewny co do tego WSAAsyncSelect() zrob tak jak ci wczesniej podalem na accept();

0

A czy przez tą nieskończoną pętle

while(1)
{
     
/*tutaj sobie piszesz co ma robic po polaczeniu klienta... czyli odbieranie, przetwarzanie pakietow itd...*/
}

nie będzie program zajmował 99% CPU?

0

Nie [!!!] a jakbys tej petli nie dal to jak by ci doszlo do konca watku to by nastapilo jego zamkniecie, a tak jak jest petla to bedzie ci ciagle watek wykonywalo. np jak w watku dasz tam zeby nasluchiwal (odbieral dane) to petla nie bedzie sie w tym momencie wykonywac tylko pozniej jak juz odbierze te dane itd... :>

0

Działa

0

Jeśli się połączy dwóch lub więcej klientów to jak mam do nich wysłać wiadomość z swerwera?? Jak mam to zrobić jeśli każdy sock(klient) jest w osobnym wątku?
Z góry dzięki.</url>

0

tworzysz sobie w watku nowego socketa:

SOCKET sock1 = (SOCKET)lpParam;

i teraz zeby wyslac informacje jakies do klienta piszesz:
send(sock1, zmienna_typu_char, ilosc_wyslanych_bajtow, 0);
a zeby odebrac to tak:
recv(sock1, zmienna_typu_char, wielkosc_buffora_odczytu, 0);

0

Mam w wątku nowego socketa tak jak pokazaeś. Tylko że jak mam wysyłać skoro recv czeka na odebranie i blokuje połączenie w tym sockecie?? Czy jest jakaś możliwość aby w wątku można było korzystać z komunikatów??

void __fastcall TForm1::procka(TMessage &Msg)
{



  switch (Msg.Msg)
  {
        case WM_USER:
                switch (WSAGETSELECTEVENT(Msg.LParam))
                {
                        case FD_ACCEPT:
                        break;

                        case FD_READ:
                        break;

                        case FD_CONNECT:
                        break;

                        case FD_WRITE:
                        break;
                }

        break;
  }

}

Korzystam z Buildera 6, to jest aplikacja dla windows a nie wiersza poleceń.

0

chyba nierozumiesz... zobacz, recv() blokuje ci dalsze wykonywanie watku fakt ale przecieŻ tak ma byc, zalozmy ze klient wysyla do ciebie komunikat co nie i ty sobie go odbierasz w serwerze przetwarzasz i wysylasz do klienta pakiet jakis - i tak ma byc !!! na tej zasadzie sa pisane programy typu serwer - client... nie jest istotne ze recv() blokuje ci wykonywanie watku !!! inaczej mozesz oczywiscie zrobic ale to nie bedzie mialo sensu, pomysl dobrze... :) nooo na bank sie da komunikaty wysylac, ale ja sie w to nie zaglebialem.

A co do wiersza polecen to przecieŻ tak samo praktycznie pisze sie pod konsole jak i aplikacje okienkowa - oczywiscie jezeli nie piszesz na komponentach :)

0

Oki więc jak mam wysłać do wszystkich klientów wiadomość, po kliknięciu na buttona? (button to ostatnia funkcja w tym kodzie).

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <winsock2.h>
#include <windows.h>
                                                                                           
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

bool stop = false;


SOCKET nasluchujacy, polaczony;

struct sockaddr_in sin;

int rozmiar;


struct OdbPak {
char odbiorcza[250];
char nick[20];
} odbierz;


String temp;

HANDLE hThread;
DWORD dwThreadID;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------


DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET sock = (SOCKET)lpParam;




while (1) {

 recv(polaczony,(char*)&odbierz,sizeof(odbierz),0);
                        temp = odbierz.nick;
                        temp += ": ";
                        temp += odbierz.odbiorcza;
                        Form1->RichEdit1->Lines->Add(temp);

}

}


int __fastcall nasluch(Pointer Parameter)
{
 while (1)
{
if(stop == true) break;

rozmiar = sizeof(sin);
polaczony = accept(nasluchujacy,(struct sockaddr *)&sin, &rozmiar);

SOCKADDR_IN adr;
int len = 16;
if (getpeername(polaczony,(SOCKADDR*) &adr,&len)==0)
{
HOSTENT *he = gethostbyaddr((CHAR*) &adr.sin_addr,len,AF_INET);
if (he)
{
char* buf = new char[256];
wsprintf(buf,"Adres ip: %s, nazwa hosta: %s :)",inet_ntoa(adr.sin_addr),he->h_name);
Form1->RichEdit1->Lines->Add(buf);
}
}

hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)polaczony, 0, &dwThreadID);

}

}


void __fastcall TForm1::Button5Click(TObject *Sender)
{
int blad;
WORD wersja;
WSADATA wsaData;
wersja = MAKEWORD( 2, 0 );
blad = WSAStartup(wersja, &wsaData );


if ( blad != 0 )
{
MessageBox(Handle,"error WSA","ERROR",MB_OK);
WSACleanup();
}


if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 )
{
MessageBox(Handle,"error zła wersja winsocket","ERROR",MB_OK);
WSACleanup();
}


nasluchujacy =  socket( AF_INET, SOCK_STREAM, 0 );

memset( &sin, 0, sizeof(sin) );
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(6666);



rozmiar = sizeof(sin);

if ( bind(nasluchujacy,(struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR )
{
MessageBox(Handle,"bind - błąd","błąd",MB_OK);
WSACleanup();
}



if (listen(nasluchujacy,SOMAXCONN)==SOCKET_ERROR)
{
MessageBox(Handle,"error listen","ERROR",MB_OK);
WSACleanup();
}



 W_ID = BeginThread(NULL, 0, nasluch, this, 0, W_PD);

        
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
stop = true;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
strcpy(odbierz.odbiorcza , "Testowa wiadomosc od serwera!");
strcpy(odbierz.nick , "Serv");
send(polaczony,(char*)&odbierz,sizeof(odbierz),0);
}
//---------------------------------------------------------------------------
0

hmm jezeli chcesz wyslac do jednej osoby to w funkcji send() podajesz gniazdo te ktore masz w watku to u ciebie to bedzie 'sock', a jezeli do wszystkich to gniazdo, ktore masz przy nasluchiwaniu... :)

0

Do socka który jest w wątku nie mogę się odwołać przez buttona. A gdy wysyłam przez socket nasłuchujący to wysyła tylko do tego klienta który sie ostatnio połączył a nie do wszystkich.

0

pisalem kiedys podobny program i obeszlo sie bez watkow,
po prostu zrobilem list<socket> i za kazdym nowym polaczeniem na socketa ktory nasluchiwal dodawalem do listy nowy element

list<socket> mojalista;
mojalista.push_front(accept....);

teraz wysylanie do kazdego podlaczonego klienta jest bardzo proste, trzeba pzreleciec przez cala liste i do kazdego to wyslac. i wystarczy jeden socket ktory nasluchuje. ponadto socket nasluchujacy dajesz do funkcji
WSAAsyncSelect(nasluchujacy,okno,WM_USER,FD_ACCEPT | FD_READ | FD_WRITE); teraz mozesz wszystko boslugiwac w procedurze obslugi komunikatow. przy polaczeniu w FD_ACCEPT: robisz tak:

mojalista.push_back(accept(nasluchujacy,(SOCKADDR *)&strukturaSOCKADDR_IN,&rozmiar));

a wysylac do wszystkich mozesz za pomocą petli for i w sumie tyle wszystko dzialalo :) pozdro

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