:TITLE: Canal de entrada/saída - socket, fileevent, vwait
;#
;# RCSID: $Header: /home/clif/CVS_ROOT/TclTutor/Tcl40.lsn,v 1.5 2000/04/24 16:17:53 clif Exp $
;# Copyright (c) 1995 Clif Flynt
;# 9300 Fleming Rd.
;# Dexter, MI  48130
;# clif@cflynt.com
;# Veja o arquivo "AVISO" com os termos da licença.
;#
;#*********************************************************
;# Portuguese translation: Lucio Agostinho Rocha
;# E-mail: outrosdiasvirao@yahoo.com.br
;# Last update: 17/01/2006
;#*********************************************************
:LESSON_TEXT_START_LEVEL 0:
A entrada/saída em Tcl é baseada em um <I>canal</I>. Um canal
é conceitualmente similar a <I>FILE *</I> em C, ou um fluxo
na programação <I>shell</I>. A diferença é que um canal pode
ser ou um dispositivo de fluxo como um arquivo, <I>drive</I> de fita, 
tty, ou uma construção de conexão orientada como um socket.
<P>
Um canal baseado em fluxo é criado com o comando <B>open</B>, como
discutido na lição 26. Um canal baseado em <I>socket</I> é criado
com um comando <B>socket</B>. Um <I>socket</I> pode ser aberto
ou como um cliente TCP, ou como um servidor.
<P>
Quando um canal existe, um manipulador pode ser definido, e ele será
chamado quando o canal estiver disponível para leitura ou escrita. Este
manipulador é definido com o comando <B>fileevent</B>.
<P>
Finalmente, há um comando para esperar até que um evento ocorra. O comando
<B>vwait</B> irá esperar até que uma variável seja atribuída. Isso pode
ser usado para criar uma funcionalidade do tipo semáforo para a interação
entre cliente e servidor.
<DL>
<B><DT>socket -server</B> <I>comando ?opcoes? porta</I>
<DD>O comando <B>socket</B> com a <I>flag</I> -server abre uma conexão
servidor na <I>porta</I>. Quando uma conexão ocorre na <I>porta</I>, o
proc <I>comando</I> é chamado com os argumentos:
<UL>
<LI>canal . . . . O canal para o novo cliente
<LI>endereco . . . . O endereço IP deste cliente
<LI>porta. . . . . . . . A porta que é atribuída para este cliente
</UL>
<B><DT>socket</B> <I>?opcoes? host porta</I>
<DD> O comando <B>socket</B> sem a opção <B>-server</B> abre uma
conexão cliente para o sistema com o endereço IP do <I>host</I> e o
endereço de <I>porta</I>. O endereço de IP pode ser dado como uma
string numérica, ou um endereço de domínio totalmente qualificado.
<P>
Para conectar ao host local, use o endereço 127.0.0.1 (o endereço
de <I>loopback</I>).
<P>
<B><DT>fileevent</B> <I>IDCanal</I> <B>readable</B> <I>?script?</I>
<B><DT>fileevent</B> <I>IDCanal</I> <B>writeable</B> <I>?script?</I>
<DD>O comando <B>fileevent</B> define um manipulador para ser chamado
quando uma condição ocorre. As condições são <B>readable</B>, a qual
chama o <I>script</I> quando um dado está pronto para ser lido no
<I>IDCanal</I>, e <B>writeable</B>, quando <I>IDCanal</I> está
pronto para receber dados.
<P>
<B><DT>vwait</B> <I>nomeVar</I>
<DD>O comando <B>vwait</B> pausa a execução de um script até que alguma
ação em plano de fundo atribua <I>nomeVar</I>. Uma ação de plano de
fundo pode ser um proc chamado por um <I>fileevent</I>, ou uma
conexão de <I>socket</I>, ou um evento de um widget Tk.
</DL>
:TEXT_END:

:LESSON_TEXT_START_LEVEL 1:
A entrada/saída em Tcl é baseada em um <I>canal</I>. Um canal
é conceitualmente similar a <I>FILE *</I> em C, ou um fluxo
na programação <I>shell</I>. A diferença é que um canal pode
ser ou um dispositivo de fluxo como um arquivo, <I>drive</I> de fita, 
tty, ou uma construção de conexão orientada como um socket.
<P>
Um canal baseado em fluxo é criado com o comando <B>open</B>, como
discutido na lição 26. Um canal baseado em <I>socket</I> é criado
com um comando <B>socket</B>. Um <I>socket</I> pode ser aberto
ou como um cliente TCP, ou como um servidor.
<P> 
Se um canal é aberto como um servidor, então o programa Tcl
irá 'ouvir' naquele canal por outra tarefa que tentar se conectar
ao canal servidor. Quando isso acontece, um novo canal é criado
para aquele <I>link</I> (servidor -> novo cliente), e o programa Tcl
continua a ouvir por conexões no número de porta original. Desta
maneira, um único servidor Tcl poderia estar conversando
com muitos clientes simultaneamente.
<P> 
Quando um canal existe, um manipulador pode ser definido, e ele será
chamado quando o canal estiver disponível para leitura ou escrita. Este
manipulador é definido com o comando <B>fileevent</B>.
Quando um procedimento Tcl faz um <B>gets</B> ou <B>puts</B> 
para um dispositivo de bloco, e o dispositivo não está pronto para
entrada/saída, o programa irá ficar bloqueado até o dispositivo
ficar pronto. Isso pode tomar muito tempo se o outro final do
canal de entrada/saída foi desligado. Utilizando o comando
<B>fileevent</B>, o programa só acessa um canal de entrada/saída
quando ele estiver pronto para mover dados.
<P>
Finalmente, há um comando para esperar até que um evento ocorra. O comando
<B>vwait</B> irá esperar até que uma variável seja atribuída. Isso pode
ser usado para criar uma funcionalidade do tipo semáforo para a interação
entre cliente e servidor, e deixar um procedimento de controle saber
que um evento ocorreu.
<P> 
Observe o exemplo e você verá o comando <B>socket</B> sendo usado
como cliente e servidor, e os comandos <B>fileevent</B> e
<B>vwait</B> sendo usados para controlar a entrada/saida entre
o cliente e o servidor.
<P> 
Note em particular os comandos <B>flush</B> sendo usados. Da mesma
forma que um canal é aberto como um <I>pipe</I> para um comando,
ele não envia dados até que, ou um <B>flush</B> seja chamado, ou
o <I>buffer</I> esteja cheio. Os canais baseados em <I>sockets</I>
não enviam dados automaticamente.
<DL>
<B><DT>socket</B> <I>?opcoes? host porta</I>
<DD> O comando <B>socket</B> sem a opção <B>-server</B> abre uma
conexão cliente para o sistema com o endereço IP do <I>host</I> e o
endereço de <I>porta</I>. O endereço de IP pode ser dado como uma
string numérica, ou um endereço de domínio totalmente qualificado.
<P>
Para conectar ao host local, use o endereço 127.0.0.1 (o endereço
de <I>loopback</I>).
<P>
<B><DT>fileevent</B> <I>IDCanal</I> <B>readable</B> <I>?script?</I>
<B><DT>fileevent</B> <I>IDCanal</I> <B>writeable</B> <I>?script?</I>
<DD>O comando <B>fileevent</B> define um manipulador para ser chamado
quando uma condição ocorre. As condições são <B>readable</B>, a qual
chama o <I>script</I> quando um dado está pronto para ser lido no
<I>IDCanal</I>, e <B>writeable</B>, quando <I>IDCanal</I> está
pronto para receber dados.
<P>
<B><DT>vwait</B> <I>nomeVar</I>
<DD>O comando <B>vwait</B> pausa a execução de um script até que alguma
ação em plano de fundo atribua <I>nomeVar</I>. Uma ação de plano de
fundo pode ser um proc chamado por um <I>fileevent</I>, ou uma
conexão de <I>socket</I>, ou um evento de um widget Tk.
</DL>
:TEXT_END:

:LESSON_TEXT_START_LEVEL 2:
De forma a um programa se comunicar com outro programa, máquina, ou
mesmo uma pessoa, ele tem que abrir um canal de entrada/saída para
o dispositivo que conversa com a outra entidade. Por padrão, a
maioria dos programas tem uma conexão para o terminal de exibição
e o teclado. No simples caso do programa "Hello, world!", o canal de
entrada/saída tem o canal de saída para a tela de exibição.
<P>
Tcl pode abrir muitos tipos de canais. Ele pode abrir o tipo simples
de canal de fluxo de entrada/saída, como foi discutido na lição 26, e
pode abrir canais para outras máquinas na rede usando o comando
<B>socket</B>.
<P>
Sockets vêm em dois "sabores": há sockets servidores que
esperam por alguém se conectar a eles, e então processam a entrada
(como um servidor de notícias NNTP, ou um servidor de correio
SMTP), e há socketes clientes que se conectam ao socket servidor e
o requisitam para realizar uma ação. Tcl pode abrir canais de socket
de entrada/saída ou como servidores ou como clientes.
<P>
Se um canal é aberto como um servidor, então o programa Tcl
irá 'ouvir' naquele canal por outra tarefa que tentar se conectar
ao canal servidor. Quando isso acontece, um novo canal é criado
para aquele <I>link</I> (servidor -> novo cliente), e o programa Tcl
continua a ouvir por conexões no número de porta original. Desta
maneira, um único servidor Tcl poderia estar conversando
com muitos clientes simultaneamente.
<P>
Uma vez que o canal existe, um manipulador pode ser definido, e ele
será chamado quando o canal estiver disponível para leitura ou escrita.
Este manipulador é definido com o comando <B>fileevent</B>.
<P>
Quando um procedimento Tcl faz um <B>gets</B> ou <B>puts</B> para
um dispositivo de bloco (por exemplo, fazendo um <I>gets</I> para
o teclado do usuário), e o dispositivo não está pronto para entrada/saída
(ou seja, o usuário ainda não digitou nada), o programa irá ficar bloqueado
até que o dispositivo esteja pronto. Isso pode tomar muito tempo se o outro final do
canal de entrada/saída foi desligado. Utilizando o comando
<B>fileevent</B>, o programa só acessa um canal de entrada/saída
quando ele estiver pronto para mover dados.
<P>
Finalmente, há um comando para esperar até que um evento ocorra. O comando
<B>vwait</B> irá esperar até que uma variável seja atribuída. Isso pode
ser usado para criar uma funcionalidade do tipo semáforo para a interação
entre cliente e servidor, e deixar um procedimento de controle saber
que um evento ocorreu.
<P> 
Observe o exemplo e você verá o comando <B>socket</B> sendo usado
como cliente e servidor, e os comandos <B>fileevent</B> e
<B>vwait</B> sendo usados para controlar a entrada/saida entre
o cliente e o servidor.
<P>
Note em particular os comandos <B>flush</B> sendo usados. Da mesma
forma que um canal é aberto como um <I>pipe</I> para um comando,
ele não envia dados até que, ou um <B>flush</B> seja chamado, ou
o <I>buffer</I> esteja cheio. Os canais baseados em <I>sockets</I>
não enviam dados automaticamente.
<P>
O exemplo define dois procs para manipular eventos. Um desses é o
proc <B>abrirServidor</B>, que será chamado quando o servidor receber 
uma conexão. O outro é um proc <B>linhaLida</B>, que é declarado
para ser o manipulador de evento de entrada disponível pelo comando
<B>fileevent</B> dentro de <B>abrirServidor</B>.
<P>
Quando o código executa a linha <B>puts $sockCliente "Uma linha de teste"</B>, 
ele envia a linha para o socket servidor. Receber essa entrada faz com que
a execução automaticamente vá para o proc <B>linhaLida</B>, que lê
a linha do socket servidor.
<P>
O código depois da chamada <B>puts</B> chama um comando <B>vwait</B> 
para esperar até que ele saiba que a linha de dados foi processada.
Quando <B>linhaLida</B> terminar de ler e exibir a linha de dados,
ele envia uma resposta para o socket cliente, e atribui a variável
<I>lido</I>. Isso permite que a linha de código principal saiba
que ela pode continuar o processamento, e adquirir a resposta.
<DL>
<B><DT>socket -server</B> <I>comando ?opcoes? porta</I>
<DD>O comando <B>socket</B> com a <I>flag</I> -server abre uma conexão
servidor na <I>porta</I>. Quando uma conexão ocorre na <I>porta</I>, o
proc <I>comando</I> é chamado com os argumentos:
<UL>
<LI>canal . . . . O canal para o novo cliente
<LI>endereco . . . . O endereço IP deste cliente
<LI>porta. . . . . . . . A porta que é atribuída para este cliente
</UL>
<B><DT>socket</B> <I>?opcoes? host porta</I>
<DD> O comando <B>socket</B> sem a opção <B>-server</B> abre uma
conexão cliente para o sistema com o endereço IP do <I>host</I> e o
endereço de <I>porta</I>. O endereço de IP pode ser dado como uma
string numérica, ou um endereço de domínio totalmente qualificado.
<P>
Para conectar ao host local, use o endereço 127.0.0.1 (o endereço
de <I>loopback</I>).
<P>
<B><DT>fileevent</B> <I>IDCanal</I> <B>readable</B> <I>?script?</I>
<B><DT>fileevent</B> <I>IDCanal</I> <B>writeable</B> <I>?script?</I>
<DD>O comando <B>fileevent</B> define um manipulador para ser chamado
quando uma condição ocorre. As condições são <B>readable</B>, a qual
chama o <I>script</I> quando um dado está pronto para ser lido no
<I>IDCanal</I>, e <B>writeable</B>, quando <I>IDCanal</I> está
pronto para receber dados.
<P>
<B><DT>vwait</B> <I>nomeVar</I>
<DD>O comando <B>vwait</B> pausa a execução de um script até que alguma
ação em plano de fundo atribua <I>nomeVar</I>. Uma ação de plano de
fundo pode ser um proc chamado por um <I>fileevent</I>, ou uma
conexão de <I>socket</I>, ou um evento de um widget Tk.
</DL>
:TEXT_END:

:CODE_START:
#Esse procedimento eh chamado quando o socket servidor recebe uma conexão

proc abrirServidor {canal endereco porta} {
    global conectado
    set conectado 1
    
    #Manipulador de eventos do socket servidor    
    #varServidor eh uma variavel que guarda 
    #a origem da chamada de fileevent (pode ser retirada)
    #
    fileevent $canal readable "linhaLida varServidor $canal"

    puts "\n Canal - O canal do novo cliente: $canal\n\
Endereco - O endereço IP desse cliente: $endereco\n\
Porta - A porta que é atribuída para esse cliente: $porta\n"

    puts "ABERTO"

}

proc linhaLida {quemChamou canal} {
    global lido
    global sair
    if { [gets $canal linha] < 0 } { 
            puts "Fechando sockCliente"
            #Ao fechar o canal, 
            #o proc "linhaLida" não poderá mais ser chamado
            #
            fileevent $canal readable {}
            #
            #after idle - os comandos serão executados mais tarde
            #             como uma chamada ociosa. É executado apenas uma vez,
            #             ou seja, quando não existirem mais eventos para processar.
            #             Só posso fechar o canal com a certeza de que todos
            #             os eventos foram processados. Senão, o fechamento do canal,
            #             sem "after idle", não ocorre.
            #             
            after idle "close $canal; set sair 1"
    } else {        
            puts "LINHA LIDA: $linha"
            puts $canal "$linha 1" ;#Coloca no canal o retorno
            flush $canal
            set lido 1
    }
}

set conectado 0

#Tenta abrir o socket servidor na porta 33000
catch { set sockServidor [ socket -server abrirServidor 33000 ] } erroSockServidor

#Utilizado dessa forma, o comando "after" retorna imediatamente, mas
#após 100 microsegundos ele faz com que todos os eventos pendentes
#sejam processados.
#
#Foi utilizado aqui para se ter a certeza de que, após 100 microsegundos,
#o socket cliente será criado.

after 100 update

#O socket cliente utiliza o socket servidor porque informa
#o mesmo endereço de IP do servidor e
#o mesmo endereço da porta do servidor
catch { set sockCliente [socket localhost 33000] } erroSockCliente
puts "sockServidor: $sockServidor\nsockCliente: $sockCliente"

#Pausa o script até que a variável "conectado" seja alterada
vwait conectado

#Um socket é um canal para outras máquinas na rede. 
#O canal servidor (socket) "ouve" o 
#seu canal, à espera de conexões. Quando um cliente se conecta ao servidor, um novo canal 
#é criado para o link cliente à servidor.

#Envia "Uma linha de teste" no sockCliente que, por sua vez, 
#envia "Uma linha de teste" para o sockServidor que, por sua vez,
#chama o proc linhaLida

puts $sockCliente "Uma linha de teste" 
flush $sockCliente
vwait lido

;#Recolhe o retorno de linhaLida
set comprimento [gets $sockCliente linha] 
puts "LINHA RETORNADA: $comprimento -- $linha"

catch {close $sockCliente}
vwait sair
puts "Fechando sockServidor"
close $sockServidor
	
:TEXT_END:

