home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Source Code / Visual Basic Source Code.iso / vbsource / tcpech1a / modtcp.bas < prev    next >
Encoding:
BASIC Source File  |  1999-09-12  |  33.6 KB  |  875 lines

  1. Attribute VB_Name = "TCP"
  2. Option Explicit
  3.  
  4. 'Constants
  5. Private Const BUFSIZE = 4096
  6. Private Const MyModule = "modMain"
  7. Private Const WSA_NoName = "Unknown"
  8. Private Const GWL_WNDPROC = (-4)
  9. Private Const GW_OWNER = 4
  10. Private Const WM_LBUTTONUP = &H202
  11. Private Const FD_SETSIZE = 64
  12. Private Const hostent_size = 16
  13. Private Const INADDR_NONE = &HFFFFFFFF
  14. Private Const INADDR_ANY = &H0
  15. Private Const sockaddr_size = 16
  16. Private Const WSA_DESCRIPTIONLEN = 256
  17. Private Const WSA_DescriptionSize = WSA_DESCRIPTIONLEN + 1
  18. Private Const WSA_SYS_STATUS_LEN = 128
  19. Private Const WSA_SysStatusSize = WSA_SYS_STATUS_LEN + 1
  20. Private Const INVALID_SOCKET = -1
  21. Private Const SOCKET_ERROR = -1
  22. Private Const SOCK_STREAM = 1
  23. Private Const AF_INET = 2
  24. Private Const WSAEWOULDBLOCK = 10035
  25. Private Const WSAEINPROGRESS = 10036
  26. Private Const FD_READ = &H1&
  27. Private Const FD_WRITE = &H2&
  28. Private Const FD_ACCEPT = &H8&
  29. Private Const FD_CONNECT = &H10&
  30. Private Const FD_CLOSE = &H20&
  31.  
  32. 'Types
  33. Private Type typeCallbackWindow
  34.     Name As String
  35.     hWnd As Long
  36.     Msg As Long
  37.     OldWindowProc As Long
  38. End Type
  39.  
  40. Private Type typeCallbacks
  41.     TCPConnect As typeCallbackWindow
  42.     CallbackClient As typeCallbackWindow
  43. End Type
  44.  
  45. Private Type fd_set
  46.     fd_count As Integer
  47.     fd_array(FD_SETSIZE) As Integer
  48. End Type
  49.  
  50. Private Type timeval
  51.     tv_sec As Long
  52.     tv_usec As Long
  53. End Type
  54.  
  55. Private Type HostEnt
  56.     h_name As Long
  57.     h_aliases As Long
  58.     h_addrtype As Integer
  59.     h_length As Integer
  60.     h_addr_list As Long
  61. End Type
  62.  
  63. Private Type sockaddr
  64.     sin_family As Integer
  65.     sin_port As Integer
  66.     sin_addr As Long
  67.     sin_zero As String * 8
  68. End Type
  69.  
  70. Private Type typeConnection
  71.     Index As Long
  72.     Socket As Long
  73.     Name As String
  74.     InUse As Long
  75.     ConnectBuffer As sockaddr
  76.     MsgQueue As String
  77.     Status As Long
  78.     StartTime As Long
  79. End Type
  80.  
  81. Private Type WSADataType
  82.     wVersion As Integer
  83.     wHighVersion As Integer
  84.     szDescription As String * WSA_DescriptionSize
  85.     szSystemStatus As String * WSA_SysStatusSize
  86.     iMaxSockets As Integer
  87.     iMaxUdpDg As Integer
  88.     lpVendorInfo As Long
  89. End Type
  90.  
  91. Private Type typeSocketLookup
  92.     Socket As Long
  93.     Index As Long
  94. End Type
  95.  
  96.  
  97. 'Variables
  98. Private Callbacks As typeCallbacks
  99. Public Connection(1 To 2000) As typeConnection
  100. Private SocketLookup() As typeSocketLookup
  101. Private SocketConnect As Long
  102. Private WSAStartedUp As Boolean
  103. Private MaxConnections As Long
  104.  
  105. 'Declares
  106. Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  107. Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  108. Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
  109. Private Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (Dest As Any, Src As Any, ByVal cb&)
  110. Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Any) As Long
  111. 'Private Declare Function accept Lib "wsock32.dll" (ByVal s As Long, addr As sockaddr, addrlen As Long) As Long
  112. 'Private Declare Function bind Lib "wsock32.dll" (ByVal s As Long, addr As sockaddr, ByVal namelen As Long) As Long
  113. 'Private Declare Function closesocket Lib "wsock32.dll" (ByVal s As Long) As Long
  114. 'Private Declare Function htons Lib "wsock32.dll" (ByVal hostshort As Long) As Integer
  115. 'Private Declare Function inet_addr Lib "wsock32.dll" (ByVal cp As String) As Long
  116. 'Private Declare Function inet_ntoa Lib "wsock32.dll" (ByVal inn As Long) As Long
  117. 'Private Declare Function listen Lib "wsock32.dll" (ByVal s As Long, ByVal backlog As Long) As Long
  118. 'Private Declare Function recv Lib "wsock32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Any) As Long  'removed ByVal from buf
  119. 'Private Declare Function send Lib "wsock32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
  120. 'Private Declare Function Socket Lib "wsock32.dll" Alias "socket" (ByVal af As Long, ByVal s_type As Long, ByVal protocol As Long) As Long
  121. 'Private Declare Function gethostbyname Lib "wsock32.dll" (ByVal host_name As String) As Long
  122. 'Private Declare Function gethostname Lib "wsock32.dll" (ByVal host_name As String, ByVal namelen As Long) As Long
  123. 'Private Declare Function WSAStartup Lib "wsock32.dll" (ByVal wVR As Long, lpWSAD As WSADataType) As Long
  124. 'Private Declare Function WSACleanup Lib "wsock32.dll" () As Long
  125. 'Private Declare Function WSAGetLastError Lib "wsock32.dll" () As Long
  126. 'Private Declare Function WSAAsyncSelect Lib "wsock32.dll" (ByVal s As Long, ByVal hWnd As Long, ByVal wMsg As Long, ByVal lEvent As Long) As Long
  127. Private Declare Function accept Lib "ws2_32.dll" (ByVal s As Long, addr As sockaddr, addrlen As Long) As Long
  128. Private Declare Function bind Lib "ws2_32.dll" (ByVal s As Long, addr As sockaddr, ByVal namelen As Long) As Long
  129. Private Declare Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long
  130. Private Declare Function htons Lib "ws2_32.dll" (ByVal hostshort As Long) As Integer
  131. Private Declare Function inet_addr Lib "ws2_32.dll" (ByVal cp As String) As Long
  132. Private Declare Function inet_ntoa Lib "ws2_32.dll" (ByVal inn As Long) As Long
  133. Private Declare Function listen Lib "ws2_32.dll" (ByVal s As Long, ByVal backlog As Long) As Long
  134. Private Declare Function recv Lib "ws2_32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Any) As Long  'removed ByVal from buf
  135. Private Declare Function send Lib "ws2_32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
  136. Private Declare Function Socket Lib "ws2_32.dll" Alias "socket" (ByVal af As Long, ByVal s_type As Long, ByVal protocol As Long) As Long
  137. Private Declare Function gethostbyname Lib "ws2_32.dll" (ByVal host_name As String) As Long
  138. Private Declare Function gethostname Lib "ws2_32.dll" (ByVal host_name As String, ByVal namelen As Long) As Long
  139. Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVR As Long, lpWSAD As WSADataType) As Long
  140. Private Declare Function WSACleanup Lib "ws2_32.dll" () As Long
  141. Private Declare Function WSAGetLastError Lib "ws2_32.dll" () As Long
  142. Private Declare Function WSAAsyncSelect Lib "ws2_32.dll" (ByVal s As Long, ByVal hWnd As Long, ByVal wMsg As Long, ByVal lEvent As Long) As Long
  143.  
  144. Public Sub Init()
  145. '------------------------------------------------------------
  146. 'Initializes the connection array, starts up the listening socket
  147. '------------------------------------------------------------
  148.  
  149.     Const MyError = MyModule & "_" & "Init"
  150.     If Timings Then PerformanceStartTime MyError
  151.     On Error GoTo Err_Init
  152.     Dim i As Long, RetVal As Long
  153.     Dim numListen As Integer, PortNum As Integer
  154.     Dim MyName As String, SocketBuffer As sockaddr
  155.     
  156.     'Initialize the connection array
  157.     For i = 1 To UBound(Connection, 1)
  158.         Connection(i).InUse = 0
  159.         Connection(i).Index = i
  160.         Connection(i).Socket = 0
  161.     Next i
  162.     ReDim SocketLookup(1 To 1)
  163.     
  164.     'Start up sockets
  165.     WSAStartedUp = False
  166.     If StartWinsock() = False Then
  167.         CScreen.DebugText = "Error starting up sockets!"
  168.         GoTo Done
  169.     End If
  170.  
  171.     'Start up listening socket
  172.     PortNum = 7
  173.     SocketConnect = Socket(AF_INET, SOCK_STREAM, 0)
  174.     If SocketConnect < 1 Then
  175.         CScreen.DebugText = "Error starting up listening socket!"
  176.         GoTo Done
  177.     End If
  178.     SocketBuffer.sin_family = AF_INET
  179.     SocketBuffer.sin_port = htons(PortNum)
  180.     SocketBuffer.sin_addr = 0
  181.     SocketBuffer.sin_zero = String(8, 0)
  182.     RetVal = bind(SocketConnect, SocketBuffer, sockaddr_size)
  183.     If RetVal <> 0 Then
  184.         RetVal = WSACleanup()
  185.         CScreen.DebugText = "Failed to bind"
  186.         GoTo Done
  187.     End If
  188.     numListen = 5
  189.     RetVal = listen(ByVal SocketConnect, ByVal numListen)
  190.     RetVal = WSAAsyncSelect(SocketConnect, Callbacks.TCPConnect.hWnd, ByVal WM_LBUTTONUP, ByVal FD_CONNECT Or FD_ACCEPT Or FD_CLOSE)
  191.     MyName = GetLocalHostName & " (" & AddrToIP(GetLocalHostName) & ")"
  192.     CScreen.DebugText = "Waiting: " & MyName & " (Socket " & SocketConnect & ")"
  193.     
  194. Done:
  195.     If Timings Then PerformanceEndTime MyError
  196.     Exit Sub
  197.     
  198. Err_Init:
  199.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  200.     Resume Next
  201. End Sub
  202.  
  203. 'returns your local machines name
  204. Private Function GetLocalHostName() As String
  205.     Dim sName$
  206.     sName = String(256, 0)
  207.     If gethostname(sName, 256) Then
  208.         sName = WSA_NoName
  209.     Else
  210.         If InStr(sName, Chr(0)) Then
  211.             sName = Left(sName, InStr(sName, Chr(0)) - 1)
  212.         End If
  213.     End If
  214.     GetLocalHostName = sName
  215. End Function
  216.  
  217. Private Function AddrToIP(ByVal AddrOrIP$) As String
  218.     AddrToIP$ = GetAscIP(GetHostByNameAlias(AddrOrIP$))
  219. End Function
  220.  
  221. Private Function GetAscIP(ByVal inn As Long) As String
  222.     Dim nStr As Long
  223.     Dim lpStr As Long
  224.     Dim retString As String
  225.     retString = String(32, 0)
  226.     lpStr = inet_ntoa(inn)
  227.     If lpStr Then
  228.         nStr = lstrlen(lpStr)
  229.         If nStr > 32 Then nStr = 32
  230.         MemCopy ByVal retString, ByVal lpStr, nStr
  231.         retString = Left(retString, nStr)
  232.         GetAscIP = retString
  233.     Else
  234.         GetAscIP = "255.255.255.255"
  235.     End If
  236. End Function
  237.  
  238. Private Function GetHostByNameAlias(ByVal HostName As String) As Long
  239.     'Return IP address as a long, in network byte order
  240.     Dim phe As Long
  241.     Dim heDestHost As HostEnt
  242.     Dim addrList As Long
  243.     Dim retIP As Long
  244.     retIP = inet_addr(HostName)
  245.     If retIP = INADDR_NONE Then
  246.         phe = gethostbyname(HostName)
  247.         If phe <> 0 Then
  248.             MemCopy heDestHost, ByVal phe, hostent_size
  249.             MemCopy addrList, ByVal heDestHost.h_addr_list, 4
  250.             MemCopy retIP, ByVal addrList, heDestHost.h_length
  251.         Else
  252.             retIP = INADDR_NONE
  253.         End If
  254.     End If
  255.     GetHostByNameAlias = retIP
  256. End Function
  257.  
  258. Public Sub TCPShutDown()
  259.     Dim RetVal As Long, i As Long
  260.     'Close the listening socket
  261.     RetVal = closesocket(SocketConnect)
  262.     'Close all the other sockets
  263.     For i = 1 To UBound(Connection, 1)
  264.         If Connection(i).InUse = 1 Then
  265.             RetVal = closesocket(Connection(i).Socket)
  266.         End If
  267.     Next i
  268.     RetVal = WSACleanup()
  269. End Sub
  270.  
  271. Public Sub TCPConnect(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long)
  272. '------------------------------------------------------------
  273. 'Handles incoming connection requests.
  274. '------------------------------------------------------------
  275.     Const MyError = MyModule & "_" & "TCPConnect"
  276.     Dim SocketRead As Long
  277.     Dim SocketConnectBuffer As sockaddr
  278.     Dim RetVal As Long
  279.     Dim Slot As Long
  280.     On Error GoTo Err_Init
  281.  
  282.     If Timings Then PerformanceStartTime MyError
  283.     
  284.     'CScreen.DebugText = "TCPConnect - hWnd: " & hWnd & ", uMsg: " & uMsg & ", wParam: " & wParam & ", lParam: " & lParam
  285.     
  286.     SocketRead = accept(SocketConnect, SocketConnectBuffer, Len(SocketConnectBuffer))
  287.     If SocketRead = INVALID_SOCKET Then
  288.         'what?
  289.         CScreen.DebugText = "Couldn't accept incoming connection"
  290.     Else
  291.         'Increase the socket lookup array size if necessary
  292.         If UBound(SocketLookup, 1) < SocketRead Then
  293.             ReDim Preserve SocketLookup(1 To SocketRead)
  294.         End If
  295.         'Add the socket to the connection array
  296.         Slot = FindSlot(SocketRead, SocketConnectBuffer)
  297.         If Slot > 0 Then
  298.             'CScreen.Lights(Slot) = ltConnecting
  299.             'successfully added them to the list
  300.             CScreen.OutputText = "Connection accepted on slot " & Slot & " - socket number " & Connection(Slot).Socket
  301.             'Add the socket to the socket lookup array
  302.             SocketLookup(SocketRead).Socket = SocketRead
  303.             SocketLookup(SocketRead).Index = Slot
  304.             CScreen.Lights(Slot) = ltIdle: Connection(Slot).Status = ltIdle
  305.         Else
  306.             'disconnect them
  307.             RetVal = closesocket(SocketRead)
  308.             CScreen.OutputText = "Maximum number of connections - connection refused."
  309.         End If
  310.     End If
  311.     
  312.     If Timings Then PerformanceEndTime MyError
  313.     Exit Sub
  314.     
  315. Err_Init:
  316.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  317.     Resume Next
  318. End Sub
  319.  
  320. Private Function FindSlot(ByVal ReadSocket As Long, ConnectBuffer As sockaddr) As Long
  321. '------------------------------------------------------------
  322. 'Looks for an empty connection slot, and if it finds one,
  323. 'saves the ReadSocket info into the connection array.
  324. '------------------------------------------------------------
  325.     Const MyError = MyModule & "_" & "FindSlot"
  326.     If Timings Then PerformanceStartTime MyError
  327.     On Error GoTo Err_Init
  328.     Dim i As Long, max As Long
  329.     Dim RetVal As Long 'used
  330.     
  331.     max = UBound(Connection, 1)
  332.     FindSlot = 0
  333.     For i = 1 To max
  334.         If Connection(i).InUse = 0 Then
  335.             'found one!
  336.             FindSlot = i
  337.             Exit For
  338.         End If
  339.     Next i
  340.     If FindSlot > 0 Then
  341.         'Add the information to the connection array
  342.         With Connection(FindSlot)
  343.             .Index = i
  344.             .InUse = 1
  345.             .Socket = ReadSocket
  346.             .ConnectBuffer = ConnectBuffer
  347.             .StartTime = timeGetTime
  348.         End With
  349.         'Add them to the list of sockets to monitor for received data
  350.         RetVal = WSAAsyncSelect(ReadSocket, Callbacks.CallbackClient.hWnd, ByVal WM_LBUTTONUP, ByVal FD_READ Or FD_WRITE Or FD_CLOSE)  ' Or FD_CLOSE Or FD_WRITE)
  351.         'Increase the connection count
  352.         If FindSlot > MaxConnections Then
  353.             MaxConnections = FindSlot
  354.         End If
  355.     End If
  356.     
  357.     If Timings Then PerformanceEndTime MyError
  358.     Exit Function
  359.     
  360. Err_Init:
  361.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  362.     Resume Next
  363. End Function
  364.  
  365. Public Sub TCPRead(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal Index As Long)
  366. '------------------------------------------------------------
  367. 'Handles incoming connection requests.
  368. '------------------------------------------------------------
  369.     Dim OutString As String, OutArray() As Byte, Bytes As Long
  370.     Const MyError = MyModule & "_" & "TCPRead"
  371.     Dim SocketRead As Long, c1 As Long, c2 As Long
  372.     Dim RetVal As Long, ErrNo As Long, s As String
  373.     Dim ReadBuffer(0 To BUFSIZE) As Byte '1 more in length
  374.     On Error GoTo Err_Init
  375.     
  376.     If Timings Then PerformanceStartTime MyError
  377.     
  378.     'Figure out which socket has data
  379.     SocketRead = Connection(Index).Socket
  380.     
  381.     'Receive the data
  382.     'CScreen.Lights(Index) = ltReceiving
  383.     Bytes = recv(SocketRead, ReadBuffer(0), UBound(ReadBuffer, 1), vbNullString)
  384.     'CScreen.Lights(Index) = ltidle
  385.  
  386.     'Was any data received?
  387.     If Bytes > 0 Then
  388.         'Display it
  389.         OutString = ArrayToString(ReadBuffer, Bytes)
  390.         'OutString = "Server connection " & Index & ">" & OutString
  391.         CScreen.OutputText = OutString
  392.         'Send it back to the user - this is an 'echo' server.
  393.         ReDim OutArray(0 To Bytes - 1)
  394.         CopyMemory OutArray(0), ReadBuffer(0), Bytes
  395.         c1 = InStr(1, OutString, "~", vbTextCompare)
  396.         c2 = InStr(1, OutString, vbCrLf, vbTextCompare)
  397.         If c1 > 0 And c2 > 0 And c2 > c1 Then
  398.             'it's a logon string
  399.             s = Mid$(OutString, c1 + 1, c2 - c1 - 1)
  400.             Connection(Index).Name = s
  401.         Else
  402.             TCP.SendText OutString, Index
  403.             CScreen.Lights(Index) = ltIdle: Connection(Index).Status = ltIdle
  404.         End If
  405.     Else
  406.         ErrNo = WSAGetLastError
  407.         'CScreen.DebugText = "Error on TCP Read " & Index & ": " & ErrNo & " - " & GetWSAErrorString(ErrNo)
  408.         CScreen.Lights(Index) = ltTCPReadError: Connection(Index).Status = ltTCPReadError
  409.     End If
  410.  
  411.     If Timings Then PerformanceEndTime MyError
  412.     Exit Sub
  413.     
  414. Err_Init:
  415.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  416.     Resume Next
  417. End Sub
  418.  
  419. Public Sub CallbackClient(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long)
  420. '------------------------------------------------------------
  421. 'Handles incoming TCP info
  422. '------------------------------------------------------------
  423.     Const MyError = MyModule & "_" & "CallbackClient"
  424.     Dim lWord As Integer
  425.     Dim hWord As Integer
  426.     Dim Socket As Long
  427.     Dim Index As Long
  428.     On Error GoTo Err_Init
  429.     
  430.     If uMsg& <> WM_LBUTTONUP Then
  431.         'It's a regular button message
  432.         CallWindowProc Callbacks.CallbackClient.OldWindowProc, hWnd, uMsg, wParam, lParam
  433.         Exit Sub
  434.     End If
  435.     
  436.     If Timings Then PerformanceStartTime MyError
  437.     
  438.     'Which socket is requesting this action?
  439.     Socket = wParam
  440.     
  441.     'Look up connection index associated with socket.
  442.     Index = SocketLookup(Socket).Index
  443.     
  444.     'Extract the action request and error message
  445.     lWord = LoWord(lParam)
  446.     hWord = HiWord(lParam)
  447.     
  448.     'Process desired action
  449.     If lWord = FD_READ Then
  450.         TCPRead hWnd, uMsg, wParam, lParam, Index
  451.     ElseIf lWord = FD_WRITE Then
  452.         TCPSend Index, ""
  453.     ElseIf lWord = FD_CLOSE Then
  454.         TCPDisconnect hWnd, uMsg, wParam, lParam, Index
  455.     End If
  456.     
  457.     If Timings Then PerformanceEndTime MyError
  458.     Exit Sub
  459.     
  460. Err_Init:
  461.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  462.     Resume Next
  463. End Sub
  464.  
  465. Private Function ArrayToString(c() As Byte, l As Long) As String
  466. '------------------------------------------------------------
  467. 'Converts an array of bytes to a string.
  468. '------------------------------------------------------------
  469.     Const MyError = MyModule & "_" & "ArrayToString"
  470.     If Timings Then PerformanceStartTime MyError
  471.     On Error GoTo Err_Init
  472.     Dim c2() As Byte, RetVal As Long
  473.     
  474.     ReDim c2(0 To l - 1)
  475.     CopyMemory c2(0), c(0), l
  476.     ArrayToString = StrConv(c2, vbUnicode)
  477.     
  478.     If Timings Then PerformanceEndTime MyError
  479.     Exit Function
  480.     
  481. Err_Init:
  482.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  483.     Resume Next
  484. End Function
  485.  
  486. Private Function StringToArray(StrData As String) As Variant
  487. '------------------------------------------------------------
  488. 'Converts a string to an array of bytes
  489. '------------------------------------------------------------
  490.     Const MyError = MyModule & "_" & "StringToArray"
  491.     If Timings Then PerformanceStartTime MyError
  492.     On Error GoTo Err_Init
  493.     Dim c() As Byte, l As Long
  494.     
  495.     l = Len(StrData)
  496.     ReDim c(0 To l)
  497.     c = StrConv(StrData, vbFromUnicode)
  498.     StringToArray = c
  499.     
  500.     If Timings Then PerformanceEndTime MyError
  501.     Exit Function
  502.     
  503. Err_Init:
  504.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  505.     Resume Next
  506. End Function
  507.  
  508. Public Sub StartCallback()
  509. '------------------------------------------------------------
  510. 'Point all TCP requests to dummy buttons on the form, so we
  511. 'can trap the windows sockets messages.
  512. '--> Must be called in form load <--
  513. '------------------------------------------------------------
  514.     Const MyError = MyModule & "_" & "StartCallback"
  515.     If Timings Then PerformanceStartTime MyError
  516.     On Error GoTo Err_Init
  517.     
  518.     With Callbacks
  519.         StartCallback2 "connect", .TCPConnect, CScreen.mForm.cmdTCPConnect.hWnd
  520.         StartCallback2 "client", .CallbackClient, CScreen.mForm.cmdCallbackClient.hWnd
  521.     End With
  522.  
  523.     If Timings Then PerformanceEndTime MyError
  524.     Exit Sub
  525.     
  526. Err_Init:
  527.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  528.     Resume Next
  529. End Sub
  530.  
  531. Private Sub StartCallback2(Name As String, x As typeCallbackWindow, hWnd As Long)
  532. '------------------------------------------------------------
  533. 'Subroutine called by StartCallback.  Does most of the work.
  534. '------------------------------------------------------------
  535.     Const MyError = MyModule & "_" & "StartCallback2"
  536.     If Timings Then PerformanceStartTime MyError
  537.     On Error GoTo Err_Init
  538.     Dim TestHwnd As Long
  539.     
  540.     x.hWnd = hWnd
  541.     
  542.     TestHwnd = GetWindow(x.hWnd, GW_OWNER)
  543.     Do
  544.         If TestHwnd <> 0 Then
  545.             x.hWnd = TestHwnd
  546.         End If
  547.     Loop Until TestHwnd = 0
  548.     ' Note that looping up the GW_OWNER tree is only necessary if you are using broadcast messages
  549.     ' from the sending program!
  550.     
  551.     x.Name = Name
  552.     x.Msg = WM_LBUTTONUP
  553.     If x.Name = "connect" Then
  554.         x.OldWindowProc = SetWindowLong(x.hWnd, GWL_WNDPROC, AddressOf TCPConnect)
  555.     ElseIf x.Name = "client" Then
  556.         x.OldWindowProc = SetWindowLong(x.hWnd, GWL_WNDPROC, AddressOf CallbackClient)
  557.     Else
  558.         MsgBox "hey!"
  559.     End If
  560.  
  561.     If Timings Then PerformanceEndTime MyError
  562.     Exit Sub
  563.     
  564. Err_Init:
  565.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  566.     Resume Next
  567. End Sub
  568.  
  569. Public Sub StopCallback()
  570. '------------------------------------------------------------
  571. 'Destroy the callback set up to trap the TCP calls.
  572. '--> Must be called in form unload. <--
  573. '------------------------------------------------------------
  574.     Const MyError = MyModule & "_" & "StopCallback"
  575.     If Timings Then PerformanceStartTime MyError
  576.     On Error GoTo Err_Init
  577.     Dim RetVal As Long
  578.     If Timings Then PerformanceEndTime MyError
  579.     
  580.     With Callbacks
  581.         RetVal = SetWindowLong(.TCPConnect.hWnd, GWL_WNDPROC, .TCPConnect.OldWindowProc)
  582.         RetVal = SetWindowLong(.CallbackClient.hWnd, GWL_WNDPROC, .CallbackClient.OldWindowProc)
  583.     End With
  584.     
  585.     If Timings Then PerformanceEndTime MyError
  586.     Exit Sub
  587.     
  588. Err_Init:
  589.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  590.     Resume Next
  591. End Sub
  592.  
  593. Private Function StartWinsock() As Boolean
  594.     Dim StartupData As WSADataType, t As String, Filler As String * 256
  595.     Filler = " "
  596.     If Not WSAStartedUp Then
  597.         StartupData.szDescription = Left$(Filler, Len(StartupData.szDescription))
  598.         StartupData.szSystemStatus = Left$(Filler, Len(StartupData.szSystemStatus))
  599.         If Not WSAStartup(&H101, StartupData) Then
  600.             StartWinsock = True
  601.             WSAStartedUp = True
  602.             CScreen.OutputText = "wVersion=" & StartupData.wVersion & vbCrLf
  603.             CScreen.OutputText = "wHighVersion=" & StartupData.wHighVersion & vbCrLf
  604.             CScreen.OutputText = "If wVersion == 257 then everything is kewl" & vbCrLf
  605.             CScreen.OutputText = "szDescription=" & StartupData.szDescription & vbCrLf & vbCrLf
  606.             CScreen.OutputText = "szSystemStatus=" & StartupData.szSystemStatus & vbCrLf & vbCrLf
  607.             CScreen.OutputText = "iMaxSockets=" & StartupData.iMaxSockets & vbCrLf
  608.             CScreen.OutputText = "iMaxUdpDg= " & StartupData.iMaxUdpDg & vbCrLf
  609.             CScreen.OutputText = vbCrLf
  610.         Else
  611.             StartWinsock = False
  612.         End If
  613.     End If
  614. End Function
  615.  
  616. Public Sub TCPDisconnect(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal Index As Long)
  617. '------------------------------------------------------------
  618. 'Removes connections
  619. '------------------------------------------------------------
  620.     Const MyError = MyModule & "_" & "TCPDisconnect"
  621.     Dim RetVal As Long
  622.     On Error GoTo Err_Init
  623.  
  624.     If Timings Then PerformanceStartTime MyError
  625.     
  626.     If Index = 0 Then
  627.         Exit Sub
  628.     End If
  629.     
  630.     RetVal = closesocket(Connection(Index).Socket)
  631.     'Clear out the connection info
  632.     With Connection(Index)
  633.         .Index = 0
  634.         .InUse = 0
  635.         .Socket = 0
  636.         .MsgQueue = ""
  637.     End With
  638.     
  639.     CScreen.Lights(Index) = ltDisconnected: Connection(Index).Status = ltDisconnected
  640.     
  641.     If Timings Then PerformanceEndTime MyError
  642.     Exit Sub
  643.     
  644. Err_Init:
  645.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  646.     Resume Next
  647. End Sub
  648.  
  649. Public Sub SendText(ByVal StrData As String, ByVal Index As Long)
  650. '------------------------------------------------------------
  651. 'Sends data out through the TCP connection
  652. '------------------------------------------------------------
  653.  
  654.     Const MyError = MyModule & "_" & "SendText"
  655.     If Timings Then PerformanceStartTime MyError
  656.     On Error GoTo Err_Init
  657.     Dim i As Long, r As Long, s As String, p As Long
  658.     
  659.     If Right$(StrData, 2) = vbCrLf Then
  660.         'ok
  661.     Else
  662.         StrData = StrData & vbCrLf
  663.     End If
  664.     If Index = 0 Then
  665.         StrData = "Sysop> " & StrData
  666.         CScreen.OutputText = StrData
  667.     End If
  668.     
  669.     'Send the data to all listening sockets.
  670.     If Left$(StrData, 1) = "@" Then
  671.         'send to a random number of people from 1 to 20
  672.         p = Random(1, IIf(MaxConnections > 20, 20, MaxConnections))
  673.         For i = 1 To p
  674.             r = Random(1, MaxConnections): TCPSend r, StrData
  675.         Next i
  676.         TCPSend Index, "Info> Your data was sent to " & p & " connections."
  677.     Else
  678.         For i = 1 To MaxConnections
  679.             TCPSend i, StrData
  680.         Next i
  681.     End If
  682.     If Timings Then PerformanceEndTime MyError
  683.     Exit Sub
  684.     
  685. Err_Init:
  686.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  687.     Resume Next
  688. End Sub
  689.  
  690. Public Sub TCPSend(ByVal Index As Long, StrData As String)
  691. '------------------------------------------------------------
  692. 'When called with an empty string,
  693. '   tells us a socket is ready for more writing.
  694. 'When called with a non-empty string,
  695. '   requests more data be sent.
  696. '------------------------------------------------------------
  697.     Dim BytesSent As Long, s As String
  698.     Dim OutArray() As Byte, i As Long, RetVal As Long, ErrNo As Long
  699.     Const MyError = MyModule & "_" & "TCPSend"
  700.     If Timings Then PerformanceStartTime MyError
  701.     On Error GoTo Err_Init
  702.     
  703.     With Connection(Index)
  704.     
  705.         If Len(StrData) > 0 Then
  706.             'Tack the data onto the end of the message queue.
  707.             .MsgQueue = .MsgQueue & StrData
  708.         End If
  709.     
  710.         If Len(.MsgQueue) = 0 Then
  711.             'hey! nothing to send-exit. occurs when a socket connects for the first time.
  712.             CScreen.Lights(Index) = ltIdle: Connection(Index).Status = ltIdle
  713.             GoTo Done
  714.         End If
  715.         
  716.         'Set the 'sending data' indicator
  717.         CScreen.Lights(Index) = ltTCPBlocked: Connection(Index).Status = ltTCPBlocked
  718.         
  719.         RetVal = 0
  720.         
  721.         'See MS Knowledge Base Article Q186245
  722.         'Can't rely on FD_WRITE's coming after every data send on all platforms -
  723.         'program should send until blocked. FD_WRITE is SUPPOSED to come only then,
  724.         'not after every send as in MS's implementation on 95/98.
  725.         Do
  726.             If Len(.MsgQueue) = 0 Then
  727.                 'We have no more data to send, and the buffers never filled.
  728.                 Exit Do
  729.             ElseIf Len(.MsgQueue) < BUFSIZE Then
  730.                 'Send the entire message queue - it's small
  731.                 s = .MsgQueue
  732.             Else
  733.                 'Hunka-hunka burnin' data -
  734.                 'send only BUFSIZE chars to prevent choking.
  735.                 s = Left$(.MsgQueue, BUFSIZE)
  736.             End If
  737.             
  738.             'Convert the text to an array
  739.             OutArray = StringToArray(s)
  740.             
  741.             'Send the text
  742.             RetVal = send(Connection(Index).Socket, OutArray(0), UBound(OutArray, 1) + 1, 0)
  743.             
  744.             'Have we reached WSAEWOULDBLOCK yet?
  745.             If RetVal = SOCKET_ERROR Then
  746.                 Exit Do
  747.             Else
  748.                 'retrieve the number of bytes sent
  749.                 BytesSent = RetVal
  750.                 'reduce the message queue
  751.                 .MsgQueue = Right$(.MsgQueue, Len(.MsgQueue) - BytesSent)
  752.             End If
  753.         Loop
  754.         
  755.         'We've reached a 'wouldblock' <normal> or other error condition.
  756.         If RetVal = SOCKET_ERROR Then
  757.             ErrNo = WSAGetLastError
  758.             If ErrNo = 10038 Then
  759.                 'socket is dead
  760.                 CScreen.Lights(Index) = ltTCPSendError: Connection(Index).Status = ltTCPSendError
  761.                 CScreen.DebugText = "Fatal error on TCP Send " & Index & ": " & ErrNo & " - " & GetWSAErrorString(ErrNo)
  762.                 TCPDisconnect 0, 0, 0, 0, Index
  763.             ElseIf ErrNo = 10035 Then
  764.                 'it's done - wasn't able to send all data, would block
  765.                 'Waiting for client to receive before we send some more.
  766.                 CScreen.Lights(Index) = ltTCPBlocked: Connection(Index).Status = ltTCPBlocked
  767.             Else
  768.                 'I don't know what causes this error.
  769.                 CScreen.Lights(Index) = ltTCPSendError: Connection(Index).Status = ltTCPSendError
  770.             End If
  771.         Else
  772.             'it's done - didn't block
  773.             CScreen.Lights(Index) = ltIdle: Connection(Index).Status = ltIdle
  774.         End If
  775.     End With
  776.     
  777. Done:
  778.     
  779.     If Timings Then PerformanceEndTime MyError
  780.     Exit Sub
  781.     
  782. Err_Init:
  783.     CScreen.DebugText = MyError & ": " & Err.Number & " - " & Err.Description
  784.     Resume Next
  785. End Sub
  786.  
  787. Private Function LoWord(DWord As Long) As Integer
  788.     If DWord And &H8000& Then
  789.         LoWord = DWord Or &HFFFF0000
  790.     Else
  791.         LoWord = DWord And &HFFFF&
  792.     End If
  793. End Function
  794.  
  795. Private Function HiWord(DWord As Long) As Integer
  796.     HiWord = (DWord And &HFFFF0000) \ &H10000
  797. End Function
  798.  
  799. Private Function GetWSAErrorString(ByVal ErrNum As Long) As String
  800.     On Error Resume Next
  801.     Select Case ErrNum
  802.         Case 10004: GetWSAErrorString = "Interrupted system call."
  803.         Case 10009: GetWSAErrorString = "Bad file number."
  804.         Case 10013: GetWSAErrorString = "Permission Denied."
  805.         Case 10014: GetWSAErrorString = "Bad Address."
  806.         Case 10022: GetWSAErrorString = "Invalid Argument."
  807.         Case 10024: GetWSAErrorString = "Too many open files."
  808.         Case 10035: GetWSAErrorString = "Operation would block."
  809.         Case 10036: GetWSAErrorString = "Operation now in progress."
  810.         Case 10037: GetWSAErrorString = "Operation already in progress."
  811.         Case 10038: GetWSAErrorString = "Socket operation on nonsocket."
  812.         Case 10039: GetWSAErrorString = "Destination address required."
  813.         Case 10040: GetWSAErrorString = "Message too long."
  814.         Case 10041: GetWSAErrorString = "Protocol wrong type for socket."
  815.         Case 10042: GetWSAErrorString = "Protocol not available."
  816.         Case 10043: GetWSAErrorString = "Protocol not supported."
  817.         Case 10044: GetWSAErrorString = "Socket type not supported."
  818.         Case 10045: GetWSAErrorString = "Operation not supported on socket."
  819.         Case 10046: GetWSAErrorString = "Protocol family not supported."
  820.         Case 10047: GetWSAErrorString = "Address family not supported by protocol family."
  821.         Case 10048: GetWSAErrorString = "Address already in use."
  822.         Case 10049: GetWSAErrorString = "Can't assign requested address."
  823.         Case 10050: GetWSAErrorString = "Network is down."
  824.         Case 10051: GetWSAErrorString = "Network is unreachable."
  825.         Case 10052: GetWSAErrorString = "Network dropped connection."
  826.         Case 10053: GetWSAErrorString = "Software caused connection abort."
  827.         Case 10054: GetWSAErrorString = "Connection reset by peer."
  828.         Case 10055: GetWSAErrorString = "No buffer space available."
  829.         Case 10056: GetWSAErrorString = "Socket is already connected."
  830.         Case 10057: GetWSAErrorString = "Socket is not connected."
  831.         Case 10058: GetWSAErrorString = "Can't send after socket shutdown."
  832.         Case 10059: GetWSAErrorString = "Too many references: can't splice."
  833.         Case 10060: GetWSAErrorString = "Connection timed out."
  834.         Case 10061: GetWSAErrorString = "Connection refused."
  835.         Case 10062: GetWSAErrorString = "Too many levels of symbolic links."
  836.         Case 10063: GetWSAErrorString = "File name too long."
  837.         Case 10064: GetWSAErrorString = "Host is down."
  838.         Case 10065: GetWSAErrorString = "No route to host."
  839.         Case 10066: GetWSAErrorString = "Directory not empty."
  840.         Case 10067: GetWSAErrorString = "Too many processes."
  841.         Case 10068: GetWSAErrorString = "Too many users."
  842.         Case 10069: GetWSAErrorString = "Disk quota exceeded."
  843.         Case 10070: GetWSAErrorString = "Stale NFS file handle."
  844.         Case 10071: GetWSAErrorString = "Too many levels of remote in path."
  845.         Case 10091: GetWSAErrorString = "Network subsystem is unusable."
  846.         Case 10092: GetWSAErrorString = "Winsock DLL cannot support this application."
  847.         Case 10093: GetWSAErrorString = "Winsock not initialized."
  848.         Case 10101: GetWSAErrorString = "Disconnect."
  849.         Case 11001: GetWSAErrorString = "Host not found."
  850.         Case 11002: GetWSAErrorString = "Nonauthoritative host not found."
  851.         Case 11003: GetWSAErrorString = "Nonrecoverable error."
  852.         Case 11004: GetWSAErrorString = "Valid name, no data record of requested type."
  853.         Case Else: GetWSAErrorString = "Couldn't find description of error " & ErrNum
  854.     End Select
  855. End Function
  856.  
  857. Public Sub Kick()
  858.     Dim i As Long, RetVal As Long, TheTime As Long
  859.     
  860.     TheTime = timeGetTime
  861.     For i = 1 To MaxConnections
  862.         With Connection(i)
  863.             If .InUse = 1 Then
  864.                 If Connection(i).Status <> ltIdle Then
  865.                     If TheTime - Connection(i).StartTime > 60000 Then
  866.                         'disconnect them
  867.                         TCPDisconnect 0, 0, 0, 0, i
  868.                     End If
  869.                 End If
  870.             End If
  871.         End With
  872.     Next i
  873. End Sub
  874.  
  875.