FORUM DARKERS

Administração => Darkers Project => Topic started by: Dark_Side on 10 de September , 2006, 05:50:07 AM

Title: [Source] Chat Non-Blocking - C++
Post by: Dark_Side on 10 de September , 2006, 05:50:07 AM
Hi,
Um bom começo para quem está começando a programar sockets é desenvolver um programa de chat cliente-servidor.Algo que reparamos é que, enquanto um computador envia uma mensagem, o outro está aguardando por esta, só depois de recebê-la terá sua vez de enviar uma mensagem também. Isso ocorre devido ao fato de que uma instrução é executada  por vez. É possível, entretanto, executar duas ou mais funções simultaneamente, conseqüentemente podemos executar várias instruções ao mesmo tempo.  O programa primeiramente define o socket como non-blocking - permite que o socket possa executar várias tarefas ao mesmo tempo - porém não é suficiente para que possamos ler e enviar dados ao mesmo tempo no programa. Para esta última função, utilizaremos Threads - que são subprocessos de um processo maior e que dividem tarefas, permitindo que estas sejam executadas simultaneamente.
O código abaixo ilustra o funcionamento:
chat.cpp

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <windows.h> // Dados e funções da API do Windows
#include <winsock.h> // Biblioteca do winsock

#define ERRO -1 // Define de erro
#define SERVER_PORTA 2222 // Porta utilizada no chat

using namespace std;
    WSADATA data;
    SOCKET sock,newsock; // Sockets
    struct sockaddr_in sin,newsin; // SIN ADDR's
    static int sin_size = sizeof(sockaddr_in); // Tamanho da estrutura sockaddr_in
    struct hostent * host; // Estrutura hostent
    u_long nonblocking = 1; // Non-Blocking mode
    HANDLE thread1,thread2; // Handles para os threads
    DWORD id1,id2; //ID's
    char buffer[0x200],entrada[0x200]; // Buffers para enviar e receber dados
   

    void erro(const char*),servidor(void),cliente(void),threads(void); // Funções em geral
    DWORD _stdcall enviar(LPVOID); // Função do thread1
    DWORD _stdcall receber(LPVOID); // Função do thread2
 
int main()
{
   
    init:
         system("cls");
cout << "################################################" << endl;
cout << "###    Exemplo - Chat estilo Non-Blocking    ###" << endl;
cout << "###                     by Dark Side         ###" << endl;
cout << "################################################" << endl;

char modo;
cout << "Modos:\n(1) Servidor\n(2) Cliente\nEscolha um modo:";
modo = cin.get(); // Obtém a opção
switch(modo) //Verifica-a
{
            case '1':
                 servidor(); // Modo servidor -> opção 1
                 break;
            case '2':
                 cliente(); // Modo cliente -> opção 2
                 break;
            default:
                 goto init; // Nenhuma das opções acima -> retorna ao início
                 break;         
            }

return 0;
}


 
void erro(const char * erro) // Função que exibe erros
{
cout << erro;
exit(1);
}
       
void servidor()
{
 
if(WSAStartup(MAKEWORD(1,1),&data)==ERRO) //Tenta inicializar o winsock
erro("\n>> Erro ao inicializar winsock.");

if((sock = socket(AF_INET,SOCK_STREAM,0))==ERRO) //Tenta criar o socket
erro("\n>> Erro ao criar socket.");

sin.sin_family=AF_INET; // Família do socket
sin.sin_port = htons(SERVER_PORTA); // Porta

// Tenta configurar o socket na porta local
if((bind(sock,(struct sockaddr*)&sin,sizeof(sin)))==ERRO)
erro("\n>> Erro ao configurar socket.");

if(listen(sock,1) == ERRO) // Tenta colocar o socket em modo listen
erro("\n>> Erro ao colocar socket no modo listen.");


// Define o socket como Non-Blocking => O socket pode realizar diversas tarefas ao mesmo tempo
ioctlsocket(sock,FIONBIO,&nonblocking);

cout << "\nAguardando conexao...\n";

// Enquanto não houver conexão com cliente
while((newsock = accept(sock,(struct sockaddr*)&newsin,&sin_size))==ERRO)
Sleep(10); // Aguarda

// Mostra o IP do cliente conectado, quando há uma conexão -> fim do loop
cout << "Cliente conectado: " << inet_ntoa(newsin.sin_addr) << endl;
threads(); // Chama função responsável por criar os threads
}

 void cliente()
 {
       
if(WSAStartup(MAKEWORD(1,1),&data)==ERRO) //Tenta inicializar o winsock
erro("\n>> Erro ao inicializar winsock.");

if((sock = socket(AF_INET,SOCK_STREAM,0))==ERRO) //Tenta criar o socket
erro("\n>> Erro ao criar socket.");

sin.sin_family=AF_INET; // Família do socket
sin.sin_port = htons(SERVER_PORTA); // Porta

// Solicita o host do servidor
char nome[0x32];
cin.get();
cout << "Servidor: ";
cin.getline(nome,0x32); // Obtém host do servidor digitdo

host = gethostbyname(nome); // Tenta obter o IP do servidor
if(host == NULL)
erro("\n>> Erro ao obter o host.");

memcpy(&sin.sin_addr,host->h_addr_list[0],host->h_length); // Copia o IP para a estrutura sockaddr_in
newsock = sock; // Ativa um novo socket;

// Tenta conectar-se
if(connect(newsock,(struct sockaddr*)&sin,sizeof(sin))==ERRO)
erro("\n>> Erro ao se conectar no host.");
cout << "Conectado com o servidor" << endl;

ioctlsocket(sock,FIONBIO,&nonblocking); // Define modo Non-Blocking
threads(); // Cria Threads
  }
 
  void threads(){
       

thread1 = CreateThread(0,0,enviar,NULL,0,&id1); // Cria thread para enviar dados
thread2 = CreateThread(0,0,receber,NULL,0,&id2); // Cria thread para receber dados

WaitForSingleObject(thread1,INFINITE); // Aguarda o encerramento da função do thread1
WaitForSingleObject(thread2,INFINITE); // Aguarda o encerramento da função do thread2

// Fecha os handles
CloseHandle(thread1);
CloseHandle(thread2);

// Encerra
cout << "\n>> A conexao foi finalizada.";
     closesocket(newsock); // Fecha o socket
     WSACleanup(); // Finaliza Winsock
}
 
  DWORD _stdcall enviar(LPVOID p){
       while(WSAGetLastError() != WSAECONNRESET) // Enquanto estiver conectado.
       {   Sleep(10);
           
               ZeroMemory(entrada,0x200); // Zera buffer
               cin.getline(entrada,0x200); // Obtém dados digitados
               send(newsock,entrada,strlen(entrada),0); // Envia-os
               send(newsock,"\n",1,0);
               
                              }
               return 0;
        }
    DWORD _stdcall receber(LPVOID p){
         while(WSAGetLastError() != WSAECONNRESET) // Enquanto estiver conectado.
         {
                 Sleep(10);
           
                 ZeroMemory(buffer,0x200); // Zera buffer
                 recv(newsock,buffer,0x200,0); // Obtém dados recebidos
                 if(strlen(buffer) > 0) // Se tiverem pelo menos 1 byte
                 cout << endl << buffer; // Mostra-os na tela.

                 }
    return 0;
}

Obs:: O objetivo maior do programa é ilustrar o funcionamento da técnica, não dando muita prioridade à parte do chat em si.
Bye... ;D