Sockets e Threads

Olá pessoal!

Estou tentando criar um chat usando swing, a comunicação está funcionando entre todos os clientes, não usei DataInput como o Viny disse e sim PrintStreams…(Acho que por isso me lascei). Eis que resolvo criar uma simples JList para saber quem está Online no chat, e é quando o problema começa.

Ultilzando somente um socket não consegui fazer isto, então estou a utilizar dois socket por Cliente, o que deve estar completamente errado.

O que eu fiz: Sempre que um novo Cliente se conecta ao servidor, o servidor manda o nome deste cliente para todos os outros, mas o ultimo cliente não recebe os clientes já conectados, assim como o penúltimo que só recebe os clientes conectados anterior a ele.
Uma solução que eu pensei seria enviar uma Lista para o cliente e não somente o novo Cliente conectado… mas como fazer?

Posso utilizar dois sockets? Ou devo tentar aprender a usar os protocolos como o Viny ensinou? http://www.guj.com.br/java/136538-duvida-simples#735860

tenta fazer com j2ee

Use um protocolo (de preferência um protocolo binário, não texto), e não fique usando N sockets.
Quanto mais sockets, mais problemas você vai ter.

Então esse protocolo eu mesmo devo criar? Ou existem prontos? sem ser o FTP

Como alguém que já teve de implementar protocolos segundo a especificação, posso lhe dizer: protocolos já existentes costumam ser muito complexos. Em vez disso, crie um mais simples, mas que seja flexível. Exemplo de um protocolo simples e flexível:

Primeiros 2 bytes - um identificador único e fixo, que você pode usar para verificar se a mensagem é válida (normalmente chamado “número mágico”). É interessante que sejam coisas que não apareçam normalmente nas mensagens que você manda.
4 bytes seguintes - o tamanho total da mensagem
2 bytes seguintes - um identificador para a mensagem (por exemplo, 0x0001 = PING, 0x0002 = LOGIN, 0x0003 = LOGOUT, 0x0004 = USER LIST etc. )
resto - o resto da mensagem (a estrutura dela depende, obviamente, do tipo de mensagem que você vai criar.

Você deve parar para pensar no seu protocolo, obrigatoriamente.

[size=18][color=red]
Não é uma opção.
Não é uma opção.
Não é uma opção.
[/color]
[/size]

O protocolo nada mais é do que as regras que regem sua comunicação.

Não interessa se é por texto ou binário, mas você deve definir exatamente isso:

  • Como os clientes avisam uns aos outros quem está conectado?
  • Como o cliente avisa o servidor que quer parar de se comunicar?
  • Como o servidor avisa os outros clientes que alguém saiu?
  • Como o servidor testa se um cliente ainda está conectado?
  • Como um cliente manda uma mensagem particular para outro?

E assim por diante. Para cada uma dessas perguntas, provavelmente haverá uma ou mais mensagens que deverão ser trocadas. E seria bom você estruturar essas mensagens de forma que seja fácil de quebrar o texto e analisa-las depois. No caso de usar o DataInputStream (como sugeri no tópico sobre protocolos) você estará usando um protocolo binário (como o Entanglement sugeriu) o que na, minha opinião, não só é mais leve como muito mais fácil de implementar. Basta estruturar os dados (como no exemplo que eu e ele demos) e le-los na ordem correta.

Se você não pensar no seu protocolo, você ainda terá um protocolo (na verdade, você já tem um hoje). Mas será tosco, desorganizado e provavelmente vai te dar muito trabalho.

Entendi, então o protocolo seria a camada entra o servidor e o cliente?

Estas perguntas que você citou eu realmente já fiz, e sim, o meu “protocolo” está bem tosco.

E tenho outras dúvidas quanto a lógica cliente-servidor. Usando sua pergunta: - Como o servidor testa se um cliente ainda está conectado?

Eu teria que usar um while(true) para isso? não causaria problemas?

Obrigado a todos pela paciência…

[quote=diego123321]Entendi, então o protocolo seria a camada entra o servidor e o cliente?

Estas perguntas que você citou eu realmente já fiz, e sim, o meu “protocolo” está bem tosco.

E tenho outras dúvidas quanto a lógica cliente-servidor. Usando sua pergunta: - Como o servidor testa se um cliente ainda está conectado?

Eu teria que usar um while(true) para isso? não causaria problemas?

Obrigado a todos pela paciência…[/quote]

Exatamente. O código de sockets sempre roda num while:

while (!desconectou) { Mensagem m = pegaAProximaMensagem(); processa(mensagem); }

Como o servidor testa se um cliente desconectou? Há algumas opções:
a) O cliente envia uma mensagem dizendo que ele desconectou (isso é chamado de Graceful Disconnection);
b) Se o cliente está mudo há muito tempo, o servidor envia uma mensagem de KEEP_ALIVE. O cliente, se estiver online, deve responder com uma mensagem de ALIVE.

As mensagens de KEEP_ALIVE e ALIVE são partes desse protocolo. Nele vc especificou como é a mensagem, e que obrigatoriamente, uma mensagem de ALIVE deve ser resposta de um KEEP_ALIVE. Se a mensagem não chegar, o servidor encerra a conexão com aquele cliente.

O protocolo é justamente a lista de mensagens que podem circular pela rede, e como essas mensagens são codificadas (se são binárias, texto, em suma, seu formato). Sem definir que mensagens vão e vem, sua aplicação estará fadada ao fracasso.

Entendi.

Eu devo utilizar as duas opções ou somente uma? No caso se eu usar KEEP_ALIVE e ALIVE e estiver em uma rede ou pela web, poderia haver algum tipo de delay, então eu teria que esperar a resposta por um determinado tempo?

Analisando o protocolo, este seria, por exemplo, uma classe no servidor e uma para o cliente?

No exemplo em que o entanglement passou:

[quote]Primeiros 2 bytes - um identificador único e fixo, que você pode usar para verificar se a mensagem é válida (normalmente chamado “número mágico”). É interessante que sejam coisas que não apareçam normalmente nas mensagens que você manda.
4 bytes seguintes - o tamanho total da mensagem
2 bytes seguintes - um identificador para a mensagem (por exemplo, 0x0001 = PING, 0x0002 = LOGIN, 0x0003 = LOGOUT, 0x0004 = USER LIST etc. )
resto - o resto da mensagem (a estrutura dela depende, obviamente, do tipo de mensagem que você vai criar. [/quote]

Como eu montaria uma mensagem utilizando bytes?

Edit:

Como você citou, um socket sempre roda dentro de um while , isso não iria gerar um loop inifinito? Ou existe algum método blocante?

Desculpe por tantas perguntas…

Isso mesmo.

Geralmente as duas. O KeepAlive é para aquele cliente em que a luz da casa dele simplesmente acabou.
Ele jamais enviará a mensagem de que saiu e demorará muito até que o socket detecte a queda do cliente sozinho (o socket tem um keep alive, mas os tempos giram na casa de 10 minutos).

Leia o meu tópico sobre protocolos que vc linkou, lá dou um exemplo de como montar uma mensagem usando um DataInputStream.

Obrigado pelo ajuda Vini!

Bom tarde pessoal.

Coloquei a mão na massa… no teclado e comecei meu Chat novamente do 0, usando como referência o tópico do Vini sobre protocolos binários.

Estou com problemas na leitura… e dúvidas

public ByteArrayOutputStream codificarMensagem(String mensagem)
			throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		DataOutputStream das = new DataOutputStream(bos);
		das.writeByte(this.codCliente);
		das.writeChars(mensagem);
		return bos;

	}

	public ByteArrayOutputStream codificarNomeCliente(String nomeCliente)
			throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		DataOutputStream das = new DataOutputStream(bos);
		das.writeByte(this.codCliente); 
		das.writeChars(nomeCliente);
		return bos;

	}

	public void envia(ByteArrayOutputStream mensagem) throws IOException {
		byte[] caracteres = mensagem.toByteArray();
		DataOutputStream out = new DataOutputStream(socket.getOutputStream());
		out.writeInt(caracteres.length);
		out.write(caracteres);
		out.flush();

	}

E aqui o código de leitura:

public void run() {
		// TODO Auto-generated method stub
		// Recebe as mensagens vindas do cliente
		try {
			DataInputStream ds = new DataInputStream(cliente.getInputStream());
			while (true) {
				int size = ds.readInt();
				byte codigo = ds.readByte();
				System.out.println(codigo);
				switch (codigo) {
				case 1:
					int cont = 0;
					char[] chars = new char[size];
					while (cont < size) {
						chars[cont] = ds.readChar();
						cont++;
					}
					String mensagem = new String(chars);
					servidor.mandarMensagens(mensagem, new DataOutputStream(
							cliente.getOutputStream()));
					size = 0;
					break;
				case 2:
					int cont2 = 0;
					char[] chars2 = new char[size];
					while (cont2 < size) {
						chars2[cont2] = ds.readChar();
						cont2++;
					}
					String mensagem2 = new String(chars2);
					System.out.println("nome:"+mensagem2);
					servidor.guardaNomeCliente(mensagem2, new DataOutputStream(
							cliente.getOutputStream()));
					size = 0;
					break;
				default:
					break;
				}

			}
		} catch (IOException e) {
		}
	}

Não recebo nada na tela, ele não está lendo corretamente…

Se você vai usar um protocolo “chatty” (ou seja, muitas mensagens pequenas com perguntas e respostas), então você tem de setar uma opção do socket chamada TCP_NODELAY.

Veja o exemplo de código no Javadoc da classe SocketOptions:

http://docs.oracle.com/javase/1.4.2/docs/api/java/net/SocketOptions.html

Um protocolo não “chatty” é algo parecido com uma estrada com pista dupla - um sentido não interfere com o outro, e ambos os lados podem rodar na velocidade máxima. Nesse caso, você pode usar a configuração padrão.

Um protocolo “chatty”, como o que você tem de usar em um “chat”, é parecido com uma estrada que tem apenas uma pista, e ela tem de ser usada tanto para a mão quanto para a contra-mão. Se um carro tiver de passar em um sentido, tem de esperar o outro no outro sentido passar, e vice-versa. Nesse caso é interessante usar o TCP_NODELAY porque isso desabilita o algoritmo de Nagle:

Entendi, porém o meu problema ainda é a leitura do DataInputStream

Eu sei, mas é por isso que estou falando das opções do socket. Muitas vezes o seu problema não é no DataInputStream (aquilo que você enxerga de perto) e sim no Socket (aquilo que está mais longe).