elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Únete al Grupo Steam elhacker.NET


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación General
| | |-+  .NET (C#, VB.NET, ASP)
| | | |-+  Programación Visual Basic (Moderadores: LeandroA, seba123neo)
| | | | |-+  [TUTORIAL] Aplicación Cliente-Servidor usando la API de Winsock
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] Ir Abajo Respuesta Imprimir
Autor Tema: [TUTORIAL] Aplicación Cliente-Servidor usando la API de Winsock  (Leído 4,113 veces)
DJ_MAQUINA

Desconectado Desconectado

Mensajes: 10


Ver Perfil
[TUTORIAL] Aplicación Cliente-Servidor usando la API de Winsock
« en: 9 Junio 2010, 05:05 am »

Hola: Soy nuevo en el foro pero siempre lo visito. He querido aportar con algo ahora, espero a más de alguien pueda serle útil  ;)

Utilizando la API de Winsock

Quizás una de las preguntas que mas se han hecho quienes intentan programar una aplicación Cliente-Servidor bajo Windows, es "¿Cómo uso la API de Winsock?" o "¿Cómo evito el uso de MSWINSCK.OCX?"

En este, y otros foros, se ha hecho muchas veces esta pregunta, y es poco lo que se ha conseguido.

Es sin duda uno de los temas donde se encuentra poca información, y la poca que hay está en inglés, y los manuales o tutoriales que hay son extensos y poco entendibles. Como último recurso sólo nos queda utilizar un módulo ya hecho por otro programador, con cientos de líneas que no entendemos.

La buena noticia es que todos podemos realizar una aplicación utilizando la API de Winsock en pocas lineas de código y un módulo ni tan extenso.

Es por eso que después de mucho averiguar, y leer, logré entender y poner en práctica el funcionamiento de la API de Winsock. Quizás no en un 100%, pero me permitió encontrar algunas funciones en un módulo y crear una aplicación funcional utilizando este método.

En este tutorial intentaré explicar paso a paso cómo realizar una sencilla aplicación Cliente y otra Servidor utilizando 100% la API y evitando el uso de cualquier OCX.

Manos a la obra:

El Cliente

Vamos a abrir VB6 y abrimos un proyecto nuevo. Añadimos los controles necesarios:



Como pueden ver, lleva Label, Textbox, Command, Listview. El texto rojo indica los nombre que les asigné a los controles para que luego no les de error el código.

Ahora vamos al MODULO:

Primero debemos declarar, entre otras cosas, las funciones para utilizar los sockets en Windows y además otras funciones para crear la subclase que nos permitirá interceptar los mensajes que se envién a la ventana (esto lo explicaremos mas adelante)

Código
  1. Option Explicit
  2.  
  3. Private Declare Function accept Lib "wsock32.dll" (ByVal s As Long, _
  4. addr As SOCKADDR, addrlen As Long) As Long
  5. Private Declare Function bind Lib "wsock32.dll" (ByVal s As Long, addr As SOCKADDR, _
  6. ByVal namelen As Long) As Long
  7. Private Declare Function closesocket Lib "wsock32.dll" (ByVal s As Long) As Long
  8. Private Declare Function Connect Lib "wsock32.dll" Alias "connect" (ByVal s As Long, _
  9. addr As SOCKADDR, ByVal namelen As Long) As Long
  10. Private Declare Function htonl Lib "wsock32.dll" (ByVal hostlong As Long) As Long
  11. Private Declare Function htons Lib "wsock32.dll" (ByVal hostshort As Long) As Integer
  12. Private Declare Function inet_ntoa Lib "wsock32.dll" (ByVal inn As Long) As Long
  13. Private Declare Function Listen Lib "wsock32.dll" Alias "listen" (ByVal s As Long, _
  14. ByVal backlog As Long) As Long
  15. Private Declare Function recv Lib "wsock32.dll" (ByVal s As Long, buf As Any, _
  16. ByVal buflen As Long, ByVal Flags As Long) As Long
  17. Private Declare Function Send Lib "wsock32.dll" Alias "send" (ByVal s As Long, _
  18. buf As Any, ByVal buflen As Long, ByVal Flags As Long) As Long
  19. Private Declare Function Socket Lib "wsock32.dll" Alias "socket" (ByVal af As Long, _
  20. ByVal s_type As Long, ByVal protocol As Long) As Long
  21. Private Declare Function gethostbyname Lib "wsock32.dll" (ByVal host_name As String) As Long
  22. Private Declare Function gethostname Lib "wsock32.dll" (ByVal host_name As String, _
  23. ByVal namelen As Long) As Long
  24. Private Declare Function WSAStartup Lib "wsock32.dll" (ByVal wVR As Long, _
  25. lpWSAD As WSADataType) As Long
  26. Private Declare Function WSACleanup Lib "wsock32.dll" () As Long
  27. Private Declare Function WSAIsBlocking Lib "wsock32.dll" () As Long
  28. Private Declare Function WSACancelBlockingCall Lib "wsock32.dll" () As Long
  29. Private Declare Function inet_addr Lib "wsock32.dll" (ByVal cp As String) As Long
  30. Private Declare Function WSAAsyncSelect Lib "wsock32.dll" (ByVal s As Long, _
  31. ByVal hWnd As Long, ByVal wMsg As Long, ByVal lEvent As Long) As Long
  32. Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
  33. (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  34. Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
  35. (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, _
  36. ByVal wParam As Long, ByVal lParam As Long) As Long
  37. Private Declare Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" _
  38. (ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, _
  39. ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _
  40. ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, _
  41. ByVal hInstance As Long, lpParam As Any) As Long
  42. Private Declare Function DestroyWindow Lib "user32.dll" (ByVal hWnd As Long) As Long
  43. Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Any) As Long
  44. Private Declare Function GetTickCount Lib "kernel32.dll" () As Long
  45. Private Declare Sub CopyMemoryIP Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, _
  46. ByVal hpvSource As Long, ByVal cbCopy As Long)
  47. Private Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (Dest As Any, Src As Any, ByVal cb As Long)
  48. Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
  49.  

Como pueden ver hemos llamado a "wsock32.dll" que contiene todas las funciones Winsock que necesitamos.

Ahora vamos a definir los tipos de datos necesarios para trabajar con Winsock:

Código
  1. Private Type WSADataType
  2. wVersion As Integer
  3. wHighVersion As Integer
  4. szDescription As String * 257
  5. szSystemStatus As String * 129
  6. iMaxSockets As Integer
  7. iMaxUdpDg As Integer
  8. lpVendorInfo As Long
  9. End Type
  10.  
  11. Private Type HostEnt
  12. hName As Long
  13. hAliases As Long
  14. hAddrType As Integer
  15. hLen As Integer
  16. hAddrList As Long
  17. End Type
  18.  
  19. Private Type SOCKADDR
  20. sin_family As Integer
  21. sin_port As Integer
  22. sin_addr As Long
  23. sin_zero As String * 8
  24. End Type

Las Constantes:

Código
  1. Private Const WINSOCK_MESSAGE As Long = 1025
  2. Private Const INADDR_NONE As Long = &HFFFF
  3. Private Const INADDR_ANY As Long = &H0
  4. Private Const IPPROTO_TCP As Long = 6
  5. Private Const INVALID_SOCKET As Long = -1
  6. Private Const SOCKET_ERROR As Long = -1
  7. Private Const SOCK_STREAM As Long = 1
  8. Private Const AF_INET As Long = 2
  9. Private Const PF_INET As Long = 2
  10. Private Const FD_READ As Long = &H1&
  11. Private Const FD_WRITE As Long = &H2&
  12. Private Const FD_OOB As Long = &H4&
  13. Private Const FD_ACCEPT As Long = &H8&
  14. Private Const FD_CONNECT As Long = &H10&
  15. Private Const FD_CLOSE As Long = &H20&
  16. Private Const GWL_WNDPROC As Long = (-4)

Las Variables:

Código
  1. Private PrevProc As Long
  2. Private bIsInit As Boolean
  3. Private hWin As Long
  4. Private m_ObjectHost As Object
  5. Private TimeOut As Long
  6.  
  7. Public PortOpen As Collection
  8. Public PortSesion As Collection
  9.  
  10. Public Sockets As Collection
  11. Public IPAddresses As Collection
  12. Public PortConection As Collection
  13.  
  14. Public CurrentSocketHandle As Long

Listo. Ahora seguimos (en el mismo módulo). Vamos a crear las funciones que nos permitirán crear y utilizar los socket.

Aquí debemos llamar a la API "CreateWindowEx" para crear el socket. A la función le pasaremos como argumento ObjectHost, que es la ventana o formulario principal. De esta forma, podemos posteriormente interceptar los mensajes que lleguen a este socket, por ejemplo cuando el Servidor nos envié datos o nos cierre la conexión:

Código
  1. Public Function InitWinSock(ObjectHost As Object) As Boolean
  2.  
  3. Dim StartupData As WSADataType
  4.  
  5. Set Sockets = New Collection
  6. Set IPAddresses = New Collection
  7. Set PortOpen = New Collection
  8. Set PortSesion = New Collection
  9. Set PortConection = New Collection
  10.  
  11. Set m_ObjectHost = ObjectHost
  12.  
  13. If Not bIsInit Then
  14. If Not WSAStartup(&H101, StartupData) Then
  15. bIsInit = True
  16. hWin = CreateWindowEx(0&, "STATIC", "SOCKET_WINDOW", _
  17. 0&, 0&, 0&, 0&, 0&, 0&, 0&, App.hInstance, ByVal 0&)
  18. PrevProc = SetWindowLong(hWin, GWL_WNDPROC, _
  19. AddressOf WindowProc)
  20. Else
  21. bIsInit = False
  22. End If
  23. End If
  24.  
  25. InitWinSock = bIsInit
  26.  
  27. End Function
  28.  

Ahora creamos una función que será llamada en el Form_Unload:

Código
  1. Public Sub TerminateWinSock()
  2.  
  3. Dim Ret As Long
  4. Dim Cnt As Long
  5.  
  6. For Cnt = 1 To Sockets.Count
  7. WsClose Sockets.Item(1)
  8. Next
  9.  
  10. For Cnt = 1 To PortSesion.Count
  11. closesocket PortSesion.Item(1)
  12. PortSesion.Remove (1)
  13. PortOpen.Remove (1)
  14. Next
  15.  
  16. If WSAIsBlocking Then WSACancelBlockingCall
  17.  
  18. Call WSACleanup
  19.  
  20. bIsInit = False
  21. SetWindowLong hWin, GWL_WNDPROC, PrevProc
  22. DestroyWindow hWin
  23.  
  24. Set Sockets = Nothing
  25. Set IPAddresses = Nothing
  26. Set PortConection = Nothing
  27. Set PortSesion = Nothing
  28. Set PortOpen = Nothing
  29.  
  30. End Sub
  31.  

Importante: Cuando ejecuten la aplicación desde el IDE de VB, no lo hagan con el botón "Stop", sino que deben cerrar la aplicación, ya que de lo contrario VB se cerrará y no preguntará por guardar cambios.

Una función muy importante: La que nos permite abrir la conexión:

Código
  1. Public Function WsConnect(ByVal Host As String, _
  2. ByVal Port As Long) As Long
  3.  
  4. Dim s As Long
  5. Dim Sockin As SOCKADDR
  6.  
  7. Sockin.sin_family = AF_INET
  8. Sockin.sin_port = htons(Port)
  9.  
  10. If Sockin.sin_port = INVALID_SOCKET Then Exit Function
  11.  
  12. Sockin.sin_addr = GetHostByNameAlias(Host$)
  13.  
  14. If Sockin.sin_addr = INADDR_NONE Then Exit Function
  15.  
  16. s = Socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
  17. If s < 0 Then Exit Function
  18.  
  19. If Connect(s, Sockin, 16) <> 0 Then
  20. If s Then closesocket s
  21. Exit Function
  22. End If
  23.  
  24. If WSAAsyncSelect(s, hWin, ByVal WINSOCK_MESSAGE, _
  25. ByVal FD_READ Or FD_WRITE Or FD_CONNECT Or FD_CLOSE) Then
  26. closesocket s
  27. Else
  28. IPAddresses.Add GetAscIp(Sockin.sin_addr), CStr(s)
  29. Sockets.Add s, CStr(s)
  30. PortConection.Add Port, CStr(s)
  31. CurrentSocketHandle = s
  32. WsConnect = s
  33. End If
  34.  
  35. End Function
  36.  

Otra función importante. Para enviar datos al servidor:

Código
  1. Public Function SendData(Socket As Long, Data As Variant) As Boolean
  2. Dim Ret As Long
  3. Dim TheMsg() As Byte, sTemp$
  4. TheMsg = ""
  5.  
  6. Select Case VarType(Data)
  7. Case 8209 'byte array
  8. sTemp = Data
  9. TheMsg = sTemp
  10. Case 8 'String
  11. sTemp = StrConv(Data, vbFromUnicode)
  12. Case Else
  13. sTemp = CStr(Data)
  14. sTemp = StrConv(Data, vbFromUnicode)
  15. End Select
  16. TheMsg = sTemp
  17.  
  18. If UBound(TheMsg) > -1 Then
  19. Ret = Send(Socket, TheMsg(0), (UBound(TheMsg) - LBound(TheMsg) + 1), 0)
  20.  
  21. If Ret = SOCKET_ERROR Then
  22. TimeOut = GetTickCount + 5000
  23. Do While Ret = SOCKET_ERROR
  24. Ret = Send(Socket, TheMsg(0), (UBound(TheMsg) - LBound(TheMsg) + 1), 0)
  25. DoEvents
  26. Sleep 10
  27. If TimeOut < GetTickCount Then Exit Do
  28. Loop
  29. End If
  30. SendData = Ret <> SOCKET_ERROR
  31. End If
  32.  
  33. End Function
  34.  

Para cerrar la conexión:

Código
  1. Public Function WsClose(ByVal s As Long) As Boolean
  2. On Local Error Resume Next
  3. WsClose = closesocket(s)
  4. IPAddresses.Remove CStr(s)
  5. Sockets.Remove CStr(s)
  6. PortConection.Remove CStr(s)
  7. End Function
  8.  

Estas funciones nos devuelven la IP local y el HOST local:

Código
  1. Public Function GetLocalIp() As String
  2.  
  3. Dim sHostName As String * 256
  4. Dim lpHost As Long
  5. Dim Host As HostEnt
  6. Dim dwIPAddr As Long
  7. Dim tmpIPAddr() As Byte
  8. Dim i As Integer
  9. Dim sIPAddr As String
  10.  
  11. lpHost = gethostbyname(sHostName)
  12.  
  13. CopyMemoryIP Host, lpHost, Len(Host)
  14. CopyMemoryIP dwIPAddr, Host.hAddrList, 4
  15. ReDim tmpIPAddr(1 To Host.hLen)
  16. CopyMemoryIP tmpIPAddr(1), dwIPAddr, Host.hLen
  17. For i = 1 To Host.hLen
  18. sIPAddr = sIPAddr & tmpIPAddr(i) & "."
  19. Next
  20. GetLocalIp = Mid$(sIPAddr, 1, Len(sIPAddr) - 1)
  21.  
  22. End Function
  23.  
  24.  
  25. Public Function LocalHostName() As String
  26. Dim sHostName As String * 256
  27. If gethostname(sHostName, 256) <> INVALID_SOCKET Then
  28. LocalHostName = Trim$(sHostName)
  29. End If
  30. End Function
  31.  
  32.  

Esta es la función de la que hablábamos anteriormente. Nos permite interceptar los mensajes que Windows nos envía a la ventana:

Código
  1. Private Function WindowProc(ByVal hWnd As Long, _
  2. ByVal uMsg As Long, ByVal wParam As Long, _
  3. ByVal lParam As Long) As Long
  4.  
  5. On Local Error Resume Next
  6.  
  7. If uMsg = WINSOCK_MESSAGE Then
  8.  
  9. Dim mIP As String
  10. Dim mPuerto As String
  11.  
  12. CurrentSocketHandle = wParam
  13.  
  14. Select Case lParam
  15.  
  16. Case FD_ACCEPT
  17. Dim s As Long, tempAddr As SOCKADDR
  18. s = accept(wParam, tempAddr, Len(tempAddr))
  19.  
  20. mIP = GetAscIp(tempAddr.sin_addr)
  21. mPuerto = PortOpen(CStr(wParam))
  22.  
  23. IPAddresses.Add mIP, CStr(s)
  24. Sockets.Add s, CStr(s)
  25. PortConection.Add mPuerto, CStr(s)
  26.  
  27. Call m_ObjectHost.Socket_Conect(s, mIP, mPuerto)
  28.  
  29. Case FD_CONNECT
  30. 'Debug.Print "FD_CONNECT"
  31.  
  32. Case FD_WRITE
  33. 'Debug.Print "FD_WRITE"
  34.  
  35. Case FD_READ
  36. Dim sTemp As String, lRet As Long, szBuf As String
  37.  
  38. Do
  39. szBuf = String(1024, 0)
  40. lRet = recv(wParam, ByVal szBuf, Len(szBuf), 0)
  41. If lRet > 0 Then sTemp = sTemp + Left$(szBuf, lRet)
  42. Loop Until lRet <= 0
  43.  
  44. If LenB(sTemp) > 0 Then
  45. mIP = IPAddresses(CStr(wParam))
  46. mPuerto = PortConection(CStr(wParam))
  47.  
  48. Call m_ObjectHost.Socket_DataArrival(wParam, _
  49. mIP, mPuerto, sTemp)
  50. End If
  51.  
  52. Case Else 'FD_CLOSE
  53. mPuerto = PortConection(CStr(wParam))
  54. mIP = IPAddresses(CStr(wParam))
  55. WsClose wParam
  56.  
  57. Call m_ObjectHost.Socket_Close(wParam, mIP, mPuerto)
  58.  
  59. End Select
  60.  
  61. Else
  62. WindowProc = CallWindowProc(PrevProc, hWnd, uMsg, wParam, lParam)
  63. End If
  64.  
  65. End Function
  66.  

Por último, otras funciones para tratar IP y Hosts:

Código
  1. Private Function GetHostByNameAlias(ByVal HostName As String) As Long
  2.  
  3. On Error Resume Next
  4. Err.Clear
  5.  
  6. Dim heDestHost As HostEnt
  7. Dim addrList As Long
  8. Dim retIP As Long
  9. Dim phe As Long
  10.  
  11. retIP = inet_addr(HostName)
  12.  
  13. If retIP = INADDR_NONE Then
  14. phe = gethostbyname(HostName)
  15. If phe <> 0 Then
  16. MemCopy heDestHost, ByVal phe, 16
  17. MemCopy addrList, ByVal heDestHost.hAddrList, 4
  18. MemCopy retIP, ByVal addrList, heDestHost.hLen
  19. Else
  20. retIP = INADDR_NONE
  21. End If
  22. End If
  23.  
  24. GetHostByNameAlias = retIP
  25.  
  26. If Err Then GetHostByNameAlias = INADDR_NONE
  27. End Function
  28.  
  29. Private Function GetAscIp(ByVal inn As Long) As String
  30. On Error Resume Next
  31. Dim lpStr&
  32. Dim nStr&
  33. Dim retString$
  34.  
  35. retString = String(32, 0)
  36.  
  37. lpStr = inet_ntoa(inn)
  38. If lpStr = 0 Then
  39. GetAscIp = "255.255.255.255"
  40. Exit Function
  41. End If
  42. nStr = lstrlen(lpStr)
  43. If nStr > 32 Then nStr = 32
  44. MemCopy ByVal retString, ByVal lpStr, nStr
  45. retString = Left(retString, nStr)
  46. GetAscIp = retString
  47. If Err Then GetAscIp = "255.255.255.255"
  48. End Function
  49.  
  50.  

Muy bién. Con esto hemos terminado el módulo.

Lo llamaremos: WinSock32


Ahora, el código del formulario. Creo que no hace falta explicarlo ya que todo corresponde a funciones que ya todos saben manejar (eso creo).

Lo principal aquí es que se establece una conexión, se envián y se reciben datos.

Código
  1. Option Explicit
  2.  
  3. Dim Conectado As Boolean
  4.  
  5. Private Sub Form_Load()
  6. Conectado = False
  7. WinSock32.InitWinSock Me ' Aqui inicializamos el socket
  8. txtIP = WinSock32.GetLocalIp ' Obtenemos nuestra IP local
  9. End Sub
  10.  
  11. Private Sub cmdConectar_Click()
  12. cmdConectar.Enabled = False
  13. lblEstado.Caption = "Intentando conectar": DoEvents
  14. If WinSock32.WsConnect(txtIP, txtPuerto) Then 'Si la conexion es exitosa
  15. lblEstado.Caption = "Conectado con " & txtIP & " en el puerto " & _
  16. txtPuerto
  17. Conectado = True
  18. Else
  19. lblEstado.Caption = "Error al conectar" 'No se pudo conectar
  20. cmdConectar.Enabled = True
  21. Conectado = False
  22. End If
  23. End Sub
  24.  
  25. Private Sub cmdEnviar_Click()
  26. If WinSock32.SendData(CurrentSocketHandle, txtMsg.Text) Then 'Intentamos enviar datos al servidor
  27. lstShow.AddItem "[Cliente]: " & txtMsg.Text
  28. txtMsg.Text = ""
  29. Else
  30. lblEstado.Caption = "Error al enviar mensaje" 'No se pudo enviar
  31. End If
  32. End Sub
  33.  
  34. Private Sub txtMsg_Change()
  35. 'Funcion para habilitar/deshabilitar el boton Enviar
  36. If Conectado = False Then Exit Sub
  37. If txtMsg.Text = vbNullString Then
  38. cmdEnviar.Enabled = False
  39. Else
  40. cmdEnviar.Enabled = True
  41. End If
  42. End Sub
  43.  
  44. Public Sub Socket_DataArrival(ID As Long, IP As String, Puerto As String, _
  45. Data As String) 'Funcion que intercepta datos que llegan al socket
  46. lstShow.AddItem "[Servidor]: " & Data
  47. End Sub
  48.  
  49. Public Sub Socket_Close(ID As Long, IP As String, Puerto As String) 'Si se cierra la conexion
  50. cmdConectar.Enabled = True
  51. lblEstado.Caption = "Desconectado"
  52. Conectado = False
  53. End Sub
  54.  
  55. Private Sub Form_Unload(Cancel As Integer)
  56. WinSock32.TerminateWinSock 'Importante llamada al descargar formulario
  57. End Sub
  58.  

Ahora compilamos y ejecutamos. Tenemos listo nuestro Cliente Winsock sin usar un OCX.

Lo más bonito de todo esto, es que el programa está testeado en Windows XP, Vista y Seven y funciona perfectamente

En la 2º parte, explicaremos cómo programar la parte servidor (la que pone a la escucha un puerto y recibe la conexión) también utilizando llamadas a la API.

De esta manera, nuestra aplicación será 100% portable, ya que no tiene absolutamente ninguna dependencia con Controles ActiveX

Nota Final: Lo importante de todo esto es quienes quieran aprender, traten de ENTENDER el código. No basta con COPIAR y PEGAR, pues de esa manera nunca podrán aprender.

Espero que a más de alguien le sirva.

Pronto la 2º parte...

Saludos!


« Última modificación: 9 Junio 2010, 05:08 am por DJ_MAQUINA » En línea

Páginas: [1] Ir Arriba Respuesta Imprimir 

Ir a:  

WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines