Programação de sockets é fundamental para desenvolver sistemas que conversam pela rede via pacotes TCP ou UDP - tais como servidores, daemons e aplicativos cliente/servidor.
Podemos trabalhar com sockets em Linguagem C, Java, PHP, Python, Ruby, etc. Mas em Assembly, será que é possível? Com certeza sim! Claro que não é tão fácil como em Python, ou elegantemente complexo como em C, mas é possível - e é isso que veremos aqui.
Este exemplo foi escrito para Windows, utilizando a ferramenta WinASM 32. Veremos um exemplo utilizando macros do WinASM - que facilitam e muito o desenvolvimento - e depois um exemplo com Assembly puro, sem utilizar as facilidades da Microsoft. Vamos lá? Siga o coelho branco... ops, digo, o código:
Exemplo Utilizando macros WinASM:
;SOCKET SERVER EM ASSEMBLY ;=========================== ;Exemplo de servidor escrito em Assembly. ;Neste exemplo o server escuta na porta 2016 e replica a mensagem enviada pelo client. ;Seria uma especie de servidor de echo :) ; ;Esta versao se utiliza de macros do WinASM. Para ver como ficaria tudo isso ;o mais proximo possivel do Assembly veja o exemplo posterior :) ; ;Atencao: nao conecte-se a essa porta por telnet, ele simula enter a cada tecla digitada. ;O melhor eh utilizar o netcat: ;c:\>nc -vv localhost 2016 ; ;Ivan S. Vargas | www.is5.com.br | contato@is5.com.br ;01/2016 .386 .MODEL FLAT,STDCALL include windows.inc include kernel32.inc include user32.inc include wininet.inc include wsock32.inc includelib kernel32.lib includelib user32.lib includelib wininet.lib includelib wsock32.lib .DATA Caption db "Atencao!",0 SocketOk db "Socket criado com sucesso!",0 SocketErro db "Erro ao criar socket.",0 BindOk db "Porta aberta com sucesso!",0 BindErro db "Erro ao abrir porta",0 ListenOk db "Escutando na porta",0 ListenErro db "Erro em listen",0 AcceptOk db "Aguardando conexao",0 AcceptErro db "Erro em accept",0 HELO db "Ola mundo!",0 Aguardando db "Aguardando conexao",0 Version dw 2 BUFFER byte 1024 dup(?) .DATA? hSocket SOCKET ? hClient SOCKET ? wStart dw ? socketaddr sockaddr_in <> .CODE START: ;carrega WSAStartup invoke WSAStartup, addr Version,addr wStart ;inicializa socket invoke socket,AF_INET,SOCK_STREAM,IPPROTO_IP mov hSocket, eax cmp hSocket, INVALID_SOCKET jz @erroSocket invoke MessageBox,NULL, addr SocketOk, addr Caption,MB_OK ;preenche estrutura sockaddr mov socketaddr.sin_family,AF_INET invoke htons,2016 mov socketaddr.sin_port,ax mov socketaddr.sin_addr,INADDR_ANY @bind: ;binda a porta invoke bind,hSocket,addr socketaddr,SIZE socketaddr cmp eax,0 jz @sucessoBind jmp @erroBind @listen: ;escuta na porta invoke listen,hSocket,5 cmp eax,SOCKET_ERROR jz @erroListen invoke MessageBox,NULL,addr ListenOk,addr Caption,MB_OK @accept: ;aguarda conexoes ;invoke accept,hSocket,addr socketaddr, SIZE socketaddr invoke accept,hSocket,0,0 cmp eax,INVALID_SOCKET ;verifica se deu erro (nao conectou) jz @accept ;se nao conectou,retorna e aguarda novamente mov hClient,eax ;se conectou,copia o endereco do socket cliente invoke send,hClient,addr HELO,10,0 ;envia mensagem de saudacao .REPEAT ;enquanto nao der erro (desconexao) recebe mensagem e a retorno (echo) invoke recv,hClient,addr BUFFER,1024,0 invoke send,hClient,addr BUFFER, SIZE BUFFER,0 .UNTIL eax == SOCKET_ERROR jmp @accept ;se der erro, aguarda nova conexao @erroSocket: invoke MessageBox,NULL,addr SocketErro,addr Caption,MB_OK jmp @sair @erroBind: invoke MessageBox,NULL,addr BindErro,addr Caption,MB_OK jmp @sair @erroListen: invoke MessageBox,NULL,addr ListenErro,addr Caption,MB_OK jmp @sair @erroAccept: invoke MessageBox,NULL,addr AcceptErro,addr Caption,MB_OK jmp @sair @sucessoBind: invoke MessageBox,NULL,addr BindOk,addr Caption,MB_OK jmp @listen @sair: invoke ExitProcess,NULL @sucessoAccept: invoke MessageBox,NULL,addr AcceptOk,addr Caption,MB_OK END START
Muito bem, este foi o exemplo utilizando as funções do WinASM. Veremos agora um outro exemplo que tem a mesma lógica e propósito do código anterior, mas dessa vez iremos chamar as interrupções diretamente, passando antes os parâmetros esperados em ordem reversa para os registradores do processador - como manda o Assembly :)
;=========================== ;SOCKET SERVER EM ASSEMBLY ;=========================== ;Exemplo de servidor escrito em Assembly. ;Neste exemplo o server escuta na porta 2016 e replica a mensagem enviada pelo client. ;Seria uma especie de servidor de echo :) ; ;Para fins de comparacao, esta versao utiliza a linguagem basica do Assembly, sem ; valers-se dos pseudo codigos da Microsoft. ; ;Atencao: nao conecte-se a essa porta por telnet, ele simula enter a cada tecla digitada. ;O melhor eh utilizar o netcat: ;c:\>nc -vv localhost 2016 ; ;Ivan S. Vargas | www.is5.com.br | contato@is5.com.br ;01/2016 .386 .MODEL FLAT,STDCALL include windows.inc include kernel32.inc include user32.inc include wininet.inc include wsock32.inc includelib kernel32.lib includelib user32.lib includelib wininet.lib includelib wsock32.lib .DATA Caption db "Atencao!",0 SocketOk db "Socket criado com sucesso!",0 SocketErro db "Erro ao criar socket.",0 BindOk db "Porta aberta com sucesso!",0 BindErro db "Erro ao abrir porta",0 ListenOk db "Escutando na porta",0 ListenErro db "Erro em listen",0 AcceptOk db "Aguardando conexao",0 AcceptErro db "Erro em accept",0 HELO db "Ola mundo!",0 Aguardando db "Aguardando conexao",0 Version dw 2 BUFFER byte 1024 dup(?) .DATA? hSocket SOCKET ? hClient SOCKET ? wStart dw ? socketaddr sockaddr_in <> .CODE START: ;carrega WSAStartup ;invoke WSAStartup, addr Version,addr wStart push offset wStart push offset Version call WSAStartup ;inicializa socket ;invoke socket,AF_INET,SOCK_STREAM,IPPROTO_IP push 0 push 1 push 2 call socket mov hSocket, EAX cmp hSocket, -1 je @erroSocket ;invoke MessageBox,NULL, addr SocketOk, addr Caption,MB_OK push 0 push offset Caption push offset SocketOk push 0 call MessageBox ;preenche estrutura sockaddr ;mov socketaddr.sin_family,AF_INET ;invoke htons,2016 ;mov socketaddr.sin_port,ax ;mov socketaddr.sin_addr,INADDR_ANY mov socketaddr.sin_family,2 push 7E0h call htons mov socketaddr.sin_port,ax mov socketaddr.sin_addr,0 @bind: ;binda a porta ;invoke bind,hSocket,addr socketaddr,SIZE socketaddr push 10h push offset socketaddr push hSocket call bind cmp eax,0 je @sucessoBind jmp @erroBind @listen: ;escuta na porta ;invoke listen,hSocket,5 push 5 push hSocket call listen cmp eax,-1 jz @erroListen ;invoke MessageBox,NULL,addr ListenOk,addr Caption,MB_OK push 0 push offset Caption push offset ListenOk push NULL call MessageBox @accept: ;aguarda conexoes ;invoke accept,hSocket,addr socketaddr, SIZE socketaddr ;invoke accept,hSocket,0,0 push 0 push 0 push hSocket call accept cmp eax,-1 ;verifica se deu erro (nao conectou) jz @accept ;se nao conectou,retorna e aguarda novamente mov hClient,eax ;se conectou,copia o endereco do socket cliente ;invoke send,hClient,addr HELO,10,0 ;envia mensagem de saudacao push 0 push 0Ah push offset HELO push hClient call send @repeat: ;.REPEAT ;enquanto nao der erro (desconexao) recebe mensagem e a retorna (echo) ;invoke recv,hClient,addr BUFFER,1024,0 push 0 push 400 push offset BUFFER push hClient call recv ;invoke send,hClient,addr BUFFER, SIZE BUFFER,0 push 0 push 400 push offset BUFFER push hClient call send ;.UNTIL eax == SOCKET_ERROR cmp eax,-1 jnz @repeat jmp @accept ;se der erro, aguarda nova conexao @erroSocket: ;invoke MessageBox,NULL,addr SocketErro,addr Caption,MB_OK push 0 push offset Caption push offset SocketErro push NULL call MessageBox jmp @sair @erroBind: ;invoke MessageBox,NULL,addr BindErro,addr Caption,MB_OK push 0 push offset Caption push offset BindErro push NULL call MessageBox jmp @sair @erroListen: ;invoke MessageBox,NULL,addr ListenErro,addr Caption,MB_OK push 0 push offset Caption push offset ListenErro push NULL call MessageBox jmp @sair @erroAccept: ;invoke MessageBox,NULL,addr AcceptErro,addr Caption,MB_OK push 0 push offset Caption push offset AcceptErro push NULL call MessageBox jmp @sair @sucessoBind: ;invoke MessageBox,NULL,addr BindOk,addr Caption,MB_OK push 0 push offset Caption push offset BindOk push NULL call MessageBox jmp @listen @sair: ;invoke ExitProcess,NULL PUSH NULL call ExitProcess @sucessoAccept: ;invoke MessageBox,NULL,addr AcceptOk,addr Caption,MB_OK push 0 push offset Caption push offset AcceptOk push NULL call MessageBox END START msg MACRO texto push 0 push offset Caption push texto push NULL call MessageBox EndM
E isso é tudo. Este segundo código é mais complexo mas é o mais próximo do puro Assembly que podemos chegar com o WinASM, sem valer-se muito de suas macros e funções auxiliares.
Espero que este exemplo possa ser útil de alguma forma. É um exemplo didático, apenas para fins de estudo. Se você precisa desenvolver aplicações de rede aconselho a utilizar linguagens de nível médio (Linguagem C) ou de alto nível (Java, Python).
Abraço.
Ivan S. Vargas