IP Scanner (Threaded)

Share your advanced PureBasic knowledge/code with the community.
tatanas
Enthusiast
Enthusiast
Posts: 199
Joined: Wed Nov 06, 2019 10:28 am
Location: France

IP Scanner (Threaded)

Post by tatanas »

Hi,

I was looking for a ip scanner in PureBasic and found this post : http://forums.purebasic.com/english/vie ... 12&t=11009
It was buggy because of the 19 years old code so I updated and upgraded it.

Code: Select all

; Inspired from fweil
; http://forums.purebasic.com/english/viewtopic.php?f=12&t=11009

Import "Ws2_32.lib"
	getnameinfo(*pSockAddr, SockaddrLength.i, *pNodeBuffer, NodeBufferSize.l, *ServiceBuffer, ServiceBufferSize.l, Flags.i)
EndImport

Enumeration
	#Window_Main
	#StatusBar
	#Gadget_Text_Start
	#Gadget_Text_End
	#Gadget_Button_OK
	#Gadget_Button_Cancel
	#Gadget_IP_Start
	#Gadget_IP_End
	#Gadget_ListIcon
	#Gadget_ShowOffline
	#Gadget_SearchHostName
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
	#EventProcessingFinished
EndEnumeration

Enumeration ThreadState
	#InProgress
	#Interrupted
	#Done
EndEnumeration

Enumeration HostState
	#Online
	#Offline
EndEnumeration

Structure PINGINFO
	IPAddress.i
	RoundTrip.i
	HostName.s
	HostState.i
	ThreadState.i
	ThreadID.i
	ItemAdded.b
EndStructure


#MaxThreads = 512

Global Run.b
Global TotalThreads, CurrentConnection.s
Global OldTotalConnections, OldTotalThreads, OldCurrentConnection.s
Global MainThreadID
Global TotalAddresses, ScannedAddresses
Global SearchHostName = #True

Global Dim PingInfoArray.PINGINFO(1)


Procedure TestConnectionToIP(ArrayIndex)
	PingInfoArray(ArrayIndex)\ThreadState = #InProgress
	Protected sIPAddress.s = IPString(PingInfoArray(ArrayIndex)\IPAddress) ;IPString(IPAddress)
	#PING_TIMEOUT = 1000

	Protected RoundTrip
	Protected *Echo.ICMP_ECHO_REPLY = AllocateStructure(ICMP_ECHO_REPLY)
	Protected EchoMessage.s = "Ping"
	Protected EchoSize = SizeOf(ICMP_ECHO_REPLY) + Len(EchoMessage)
	Protected hFile = IcmpCreateFile_()
	Protected lngResult = IcmpSendEcho_(hFile, PingInfoArray(ArrayIndex)\IPAddress, EchoMessage, Len(EchoMessage), 0, *Echo, EchoSize, #PING_TIMEOUT)

	If lngResult > 0
		RoundTrip = *Echo\RoundTripTime
		PingInfoArray(ArrayIndex)\HostState = #Online
	Else
		RoundTrip = -1
		PingInfoArray(ArrayIndex)\HostState = #Offline
	EndIf
	lngResult = IcmpCloseHandle_(hFile)

	If RoundTrip >= 0

		If SearchHostName
			Protected *NodeBuffer, dwRetval
			Protected WSAData_.WSADATA
			Protected saGNI.SOCKADDR_IN
				
			*NodeBuffer = AllocateMemory(1024)
			If *NodeBuffer
				WSAStartup_(1, @WSAData_)
				
				saGNI\sin_family = #AF_INET
				saGNI\sin_addr = PingInfoArray(ArrayIndex)\IPAddress
				
				dwRetval = getnameinfo(@saGNI, SizeOf(SOCKADDR_IN), *NodeBuffer, MemorySize(*NodeBuffer), 0, 0, 0)
				If dwRetval = 0
					PingInfoArray(ArrayIndex)\HostName = PeekS(*NodeBuffer, -1, #PB_Ascii)
				EndIf
				
				WSACleanup_()
				FreeMemory(*NodeBuffer)
			EndIf
		EndIf

		If RoundTrip = 0 : RoundTrip = 1 : EndIf ; 1 ms round trip time minimum
		PingInfoArray(ArrayIndex)\RoundTrip = RoundTrip

	EndIf

	TotalThreads - 1
	ScannedAddresses + 1

	FreeStructure(*Echo)

	PingInfoArray(ArrayIndex)\ThreadState = #Done
	PostEvent(#EventProcessingFinished)
EndProcedure


Procedure RunScan(a=0)
	Protected IPAddress, sIPAddress.s
	Protected i = 0
	Delay(500)
	Protected StartAddress = GetGadgetState(#Gadget_IP_Start)
	Protected EndAddress = GetGadgetState(#Gadget_IP_End)
	Protected iStartAddress = IPAddressField(StartAddress, 0) << 24 + IPAddressField(StartAddress, 1) << 16 + IPAddressField(StartAddress, 2) << 8 + IPAddressField(StartAddress, 3)
	Protected iEndAddress 	= IPAddressField(EndAddress, 0) << 24 + IPAddressField(EndAddress, 1) << 16 + IPAddressField(EndAddress, 2) << 8 + IPAddressField(EndAddress, 3)
	TotalAddresses = iEndAddress - iStartAddress + 1

	Dim PingInfoArray(TotalAddresses)

	For iIPAddress = iStartAddress To iEndAddress
		IPAddress = MakeIPAddress(IPAddressField(iIPAddress, 3), IPAddressField(iIPAddress, 2), IPAddressField(iIPAddress, 1), IPAddressField(iIPAddress, 0))
		sIPAddress.s = IPString(IPAddress)

		CurrentConnection = "Scanning " + sIPAddress

		PingInfoArray(i)\IPAddress = IPAddress
		PingInfoArray(i)\ItemAdded = #False
		PingInfoArray(i)\ThreadID = CreateThread(@TestConnectionToIP(), i)
		i + 1

		TotalThreads + 1

		If TotalThreads > #MaxThreads
			While TotalThreads > #MaxThreads / 2
				Delay(500)
			Wend
		EndIf

		Delay(10) ; between each ping
	Next
	Run = #False

	MainThreadID = 0
EndProcedure


Procedure Stop()
	Run = #False
	CurrentConnection = "Canceling ..."

	If IsThread(MainThreadID)
		CurrentConnection = "Canceling " + Str(MainThreadID)
		KillThread(MainThreadID)	
		MainThreadID = 0
	EndIf

	CurrentConnection = "Done"
EndProcedure


Procedure DoWeRun()
	If MainThreadID <> 0
		Stop()
	EndIf
	FreeArray(PingInfoArray())
	ClearGadgetItems(#Gadget_ListIcon)
	Run = #True
	ScannedAddresses = 0
	TotalAddresses = 0
	CurrentConnection = "Scanning ..."
	MainThreadID = CreateThread(@RunScan(), 0)
EndProcedure
;
;
;
;
Define sStartAddress.s = "192.168.0.1"
Define sEndAddress.s = "192.168.0.254"
Define StartAddress.l
Define EndAddress.l
Define Quit = #False
Define WindowXSize = 640
Define WindowYSize = 480
Define RoundTripTime$ = ""
Define OnlineHostCount

Run = #False

If OpenWindow(#Window_Main, 0, 0, WindowXSize, WindowYSize, "Ip Scanner", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

	If CreateStatusBar(#StatusBar, WindowID(#Window_Main))
		AddStatusBarField(250)
		AddStatusBarField(140)
		AddStatusBarField(140)
		AddStatusBarField(110)
	EndIf

	TextGadget(#Gadget_Text_Start, 10, 10, 80, 20, "Start address")
	TextGadget(#Gadget_Text_End, 10, 30, 80, 20, "End address")
	ButtonGadget(#Gadget_Button_OK, 230, 10, 60, 20, "Go")
	ButtonGadget(#Gadget_Button_Cancel, 230, 30, 60, 20, "Cancel")
	IPAddressGadget(#Gadget_IP_Start, 100, 10, 120, 20)
	IPAddressGadget(#Gadget_IP_End, 100, 30, 120, 20)
	ListIconGadget(#Gadget_ListIcon, 10, 50, WindowXSize - 20, WindowYSize - 80, "Host found", 120)
	AddGadgetColumn(#Gadget_ListIcon, 1, "HostName", 100)
	AddGadgetColumn(#Gadget_ListIcon, 2, "Time", 80)
	CheckBoxGadget(#Gadget_ShowOffline, 360, 10, 120, 20, "Show offline hosts")
	SetGadgetState(#Gadget_ShowOffline, #PB_Checkbox_Checked)
	CheckBoxGadget(#Gadget_SearchHostName, 360, 30, 120, 20, "Get hostname")
	SetGadgetState(#Gadget_SearchHostName, #PB_Checkbox_Checked)

	SetGadgetState(#Gadget_IP_Start, MakeIPAddress(Val(StringField(sStartAddress, 1, ".")), Val(StringField(sStartAddress, 2, ".")), Val(StringField(sStartAddress, 3, ".")), Val(StringField(sStartAddress, 4, "."))))
	SetGadgetState(#Gadget_IP_End, MakeIPAddress(Val(StringField(sEndAddress, 1, ".")), Val(StringField(sEndAddress, 2, ".")), Val(StringField(sEndAddress, 3, ".")), Val(StringField(sEndAddress, 4, "."))))


	Repeat
		Select WindowEvent()

			Case #PB_Event_CloseWindow
				Quit = #True

			Case #PB_Event_Gadget
				Select EventGadget()
					Case #Gadget_IP_Start
						sStartAddress = GetGadgetText(#Gadget_IP_Start)
						StartAddress = GetGadgetState(#Gadget_IP_Start)
					Case #Gadget_IP_End
						sEndAddress = GetGadgetText(#Gadget_IP_End)
						EndAddress = GetGadgetState(#Gadget_IP_End)
					Case #Gadget_Button_OK
						OnlineHostCount = 0
						If GetGadgetState(#Gadget_SearchHostName) = #PB_Checkbox_Checked
							SearchHostName = #True
						Else
							SearchHostName = #False
						EndIf
						DoWeRun()
					Case #Gadget_Button_Cancel
						Stop()
				EndSelect

			Case #EventProcessingFinished
				; pour afficher les ip dans l'ordre
				For i = 0 To ArraySize(PingInfoArray())
					If PingInfoArray(i)\ThreadState = #Done
						If Not PingInfoArray(i)\ItemAdded

							If GetGadgetState(#Gadget_ShowOffline) = #PB_Checkbox_Unchecked
								If PingInfoArray(i)\HostState = #Offline
									Continue
								EndIf
							EndIf

							If PingInfoArray(i)\HostState = #Offline
								RoundTripTime$ = ""
							Else
								RoundTripTime$ = Str(PingInfoArray(i)\RoundTrip)
								OnlineHostCount + 1
							EndIf

							AddGadgetItem(#Gadget_ListIcon, -1, IPString(PingInfoArray(i)\IPAddress) + Chr(10) + PingInfoArray(i)\HostName + Chr(10) + RoundTripTime$)

							PingInfoArray(i)\ItemAdded = #True
							StatusBarText(#StatusBar, 1, Str(OnlineHostCount) + " Online Hosts")
						EndIf
					Else
						Break
					EndIf
				Next

		EndSelect

		If OldCurrentConnection <> CurrentConnection
			OldCurrentConnection = CurrentConnection
			StatusBarText(#StatusBar, 0, CurrentConnection)
		EndIf
		If OldScannedAddresses <> ScannedAddresses
			OldScannedAddresses = ScannedAddresses
			StatusBarText(#StatusBar, 2, Str(ScannedAddresses) + " / " + Str(TotalAddresses) + " IP Addresses")
		EndIf
		If OldTotalThreads <> TotalThreads
			OldTotalThreads = TotalThreads
			StatusBarText(#StatusBar, 3, Str(TotalThreads) + " Threads")
		EndIf

		Delay(1)
	Until Quit

	Stop()
EndIf


End
Have fun.
Windows 10 Pro x64
PureBasic 6.04 x64
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: IP Scanner (Threaded)

Post by infratec »

First I forgot to enable threadsafe (you should add a CompilerError)

But also after enable this, the program crashes at different places.

PB 6.01 x86 on Win10 x64

I will try to find out why.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: IP Scanner (Threaded)

Post by infratec »

After a first look in the code ...

sorry to say this, but it's not well done.

Mutex for write access to global variables missing.
No clean thread termination included.
No checks for allocations or other successful procedure calls.
Calculation of the address range only handles <= /24 networks.

Why not WaitWIndowEvent() ?
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: IP Scanner (Threaded)

Post by infratec »

As start I checked the inner ping procedure.
With the followng version I have no crashes.

The memorysizes were wrong calculated.

Code: Select all

Procedure TestConnectionToIP(ArrayIndex)
  
  Protected.i RoundTrip, EchoSize, hFile, lngResult
  Protected sIPAddress.s, EchoMessage.s
  Protected *Echo.ICMP_ECHO_REPLY
  Protected *NodeBuffer, dwRetval
  Protected WSAData_.WSADATA
  Protected saGNI.SOCKADDR_IN
  
  
  PingInfoArray(ArrayIndex)\ThreadState = #InProgress
  sIPAddress = IPString(PingInfoArray(ArrayIndex)\IPAddress)
  
  EchoMessage = "Ping"
  EchoSize = SizeOf(ICMP_ECHO_REPLY) + StringByteLength(EchoMessage) + 1
  *Echo = AllocateMemory(EchoSize)
  If *Echo
    
    hFile = IcmpCreateFile_()
    If hFile
      lngResult = IcmpSendEcho_(hFile, PingInfoArray(ArrayIndex)\IPAddress, EchoMessage, StringByteLength(EchoMessage) + 1, 0, *Echo, EchoSize, #PING_TIMEOUT)
      
      If lngResult > 0
        RoundTrip = *Echo\RoundTripTime
        PingInfoArray(ArrayIndex)\HostState = #Online
      Else
        RoundTrip = -1
        PingInfoArray(ArrayIndex)\HostState = #Offline
      EndIf
      IcmpCloseHandle_(hFile)
    EndIf
    
    If RoundTrip >= 0
      If SearchHostName
        If WSAStartup_(1, @WSAData_) = 0
          saGNI\sin_family = #AF_INET
          saGNI\sin_addr = PingInfoArray(ArrayIndex)\IPAddress
          
          *NodeBuffer = AllocateMemory(1024)
          If *NodeBuffer
            dwRetval = getnameinfo(@saGNI, SizeOf(SOCKADDR_IN), *NodeBuffer, MemorySize(*NodeBuffer), #Null, 0, 0)
            If dwRetval = 0
              PingInfoArray(ArrayIndex)\HostName = PeekS(*NodeBuffer, -1, #PB_Ascii)
            EndIf
            FreeMemory(*NodeBuffer)
          EndIf
          WSACleanup_()
        EndIf
      EndIf
      
      If RoundTrip = 0 : RoundTrip = 1 : EndIf ; 1 ms round trip time minimum
      PingInfoArray(ArrayIndex)\RoundTrip = RoundTrip
      
    EndIf
    
    FreeMemory(*Echo)
  EndIf
  
  TotalThreads - 1
  ScannedAddresses + 1
  
  PingInfoArray(ArrayIndex)\ThreadState = #Done
  PostEvent(#EventProcessingFinished)
  
EndProcedure
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: IP Scanner (Threaded)

Post by infratec »

Still not perfect:

Code: Select all

; Inspired from fweil
; http://forums.purebasic.com/english/viewtopic.php?f=12&t=11009

EnableExplicit

Import "Ws2_32.lib"
  getnameinfo(*pSockAddr, SockaddrLength.i, *pNodeBuffer, NodeBufferSize.l, *ServiceBuffer, ServiceBufferSize.l, Flags.i)
EndImport

Enumeration
  #Window_Main
  #StatusBar
  #Gadget_Text_Start
  #Gadget_Text_End
  #Gadget_Button_OK
  #Gadget_Button_Cancel
  #Gadget_IP_Start
  #Gadget_IP_End
  #Gadget_ListIcon
  #Gadget_ShowOffline
  #Gadget_SearchHostName
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
  #OwnEvent_TestConnectionToIP_Finished
  #OwnEvent_StatusBarField_0
  #OwnEvent_StatusBarField_1
  #OwnEvent_StatusBarField_2
  #OwnEvent_StatusBarField_3
  #OwnEvent_Stopped
EndEnumeration

Enumeration ThreadState
  #InProgress
  #Interrupted
  #Done
EndEnumeration

Enumeration HostState
  #Online
  #Offline
EndEnumeration

Enumeration Action
  #StartScan
  #StopScan
  #QuitScan
EndEnumeration

#PING_TIMEOUT = 1000

#MaxThreads = 256

#WindowWidth = 640
#WindowHeight = 480


Structure PINGINFO
  Index.i
  IPAddress.i
  RoundTrip.i
  HostName.s
  HostState.i
  Thread.i
EndStructure

Structure MainThread_Structure
  Mutex.i
  Thread.i
  Exit.i
  List PingInfoList.PINGINFO()
EndStructure


Global.i TotalAddresses, TotalThreads, ScannedAddresses
Global CurrentConnection.s
Global SearchHostName = #True
Global MainThread.MainThread_Structure


Procedure TestConnectionToIP(*Address.PINGINFO)
  
  Protected.i RoundTrip, EchoSize, hFile, lngResult
  Protected sIPAddress.s, EchoMessage.s
  Protected *Echo.ICMP_ECHO_REPLY
  Protected *NodeBuffer, dwRetval
  Protected WSAData_.WSADATA
  Protected saGNI.SOCKADDR_IN
  
  
  sIPAddress = IPString(*Address\IPAddress)
  
  EchoMessage = "Ping"
  EchoSize = SizeOf(ICMP_ECHO_REPLY) + StringByteLength(EchoMessage) + 1
  *Echo = AllocateMemory(EchoSize)
  If *Echo
    
    hFile = IcmpCreateFile_()
    If hFile
      lngResult = IcmpSendEcho_(hFile, *Address\IPAddress, EchoMessage, StringByteLength(EchoMessage) + 1, 0, *Echo, EchoSize, #PING_TIMEOUT)
      
      If lngResult > 0
        RoundTrip = *Echo\RoundTripTime
        *Address\HostState = #Online
      Else
        RoundTrip = -1
        *Address\HostState = #Offline
      EndIf
      IcmpCloseHandle_(hFile)
    EndIf
    
    If RoundTrip >= 0
      If SearchHostName
        If WSAStartup_(1, @WSAData_) = 0
          saGNI\sin_family = #AF_INET
          saGNI\sin_addr = *Address\IPAddress
          
          *NodeBuffer = AllocateMemory(1024)
          If *NodeBuffer
            dwRetval = getnameinfo(@saGNI, SizeOf(SOCKADDR_IN), *NodeBuffer, MemorySize(*NodeBuffer), #Null, 0, 0)
            If dwRetval = 0
              *Address\HostName = PeekS(*NodeBuffer, -1, #PB_Ascii)
            EndIf
            FreeMemory(*NodeBuffer)
          EndIf
          WSACleanup_()
        EndIf
      EndIf
      
      If RoundTrip = 0 : RoundTrip = 1 : EndIf ; 1 ms round trip time minimum
      *Address\RoundTrip = RoundTrip
      
    EndIf
    
    FreeMemory(*Echo)
  EndIf
  
  LockMutex(MainThread\Mutex)
  
  TotalThreads - 1
  PostEvent(#OwnEvent_StatusBarField_3, 0, 0, 0, TotalThreads)
  
  ScannedAddresses + 1
  PostEvent(#OwnEvent_StatusBarField_2, 0, 0, 0, ScannedAddresses)
  
  UnlockMutex(MainThread\Mutex)
  
  PostEvent(#OwnEvent_TestConnectionToIP_Finished, 0, 0, 0, *Address)
  
EndProcedure


Procedure RunScan(*Parameter.MainThread_Structure)
  
  Protected.i IPAddress, i, StartAddress, EndAddress, iIPAddress, iStartAddress, iEndAddress
  Protected.s sIPAddress
  
  
  StartAddress = GetGadgetState(#Gadget_IP_Start)
  EndAddress = GetGadgetState(#Gadget_IP_End)
  iStartAddress = IPAddressField(StartAddress, 0) << 24 + IPAddressField(StartAddress, 1) << 16 + IPAddressField(StartAddress, 2) << 8 + IPAddressField(StartAddress, 3)
  iEndAddress = IPAddressField(EndAddress, 0) << 24 + IPAddressField(EndAddress, 1) << 16 + IPAddressField(EndAddress, 2) << 8 + IPAddressField(EndAddress, 3)
  
  TotalAddresses = iEndAddress - iStartAddress + 1
  
  For iIPAddress = iStartAddress To iEndAddress
    
    LockMutex(MainThread\Mutex)
    If TotalThreads >= #MaxThreads
      While TotalThreads > #MaxThreads / 2
        UnlockMutex(MainThread\Mutex)
        Delay(500)
        LockMutex(MainThread\Mutex)
      Wend
    EndIf
    UnlockMutex(MainThread\Mutex)
    
    IPAddress = MakeIPAddress(IPAddressField(iIPAddress, 3), IPAddressField(iIPAddress, 2), IPAddressField(iIPAddress, 1), IPAddressField(iIPAddress, 0))
    sIPAddress.s = IPString(IPAddress)
    
    CurrentConnection = "Scanning " + sIPAddress
    PostEvent(#OwnEvent_StatusBarField_0)
    
    AddElement(*Parameter\PingInfoList())
    *Parameter\PingInfoList()\Index = ListIndex(*Parameter\PingInfoList())
    *Parameter\PingInfoList()\IPAddress = IPAddress
    *Parameter\PingInfoList()\Thread = CreateThread(@TestConnectionToIP(), *Parameter\PingInfoList())
    
    If *Parameter\PingInfoList()\Thread
      LockMutex(MainThread\Mutex)
      TotalThreads + 1
      PostEvent(#OwnEvent_StatusBarField_3, 0, 0, 0, TotalThreads)
      UnlockMutex(MainThread\Mutex)
    EndIf
    
    i + 1
    
    Delay(10) ; between each ping
    
    If MainThread\Exit
      Break
    EndIf
    
  Next
  
EndProcedure


Procedure Stop(*Parameter.MainThread_Structure)
  
  Protected.i ThreadsRunning
  
  
  CurrentConnection = "Canceling ..."
  PostEvent(#OwnEvent_StatusBarField_0)
  
  If IsThread(*Parameter\Thread)
    *Parameter\Exit = #True
    CurrentConnection = "Canceling " + Str(*Parameter\Thread)
    PostEvent(#OwnEvent_StatusBarField_0)
    
    If WaitThread(*Parameter\Thread, #PING_TIMEOUT) = 0
      Debug "MainThread need manual kill !!!"
      KillThread(*Parameter\Thread)
    EndIf
    *Parameter\Thread = #Null
    
    Repeat
      ThreadsRunning = #False
      ForEach *Parameter\PingInfoList()
        If IsThread(*Parameter\PingInfoList()\Thread)
          ThreadsRunning = #True
          Break
        EndIf
      Next
    Until ThreadsRunning = #False
    
  EndIf
  
  CurrentConnection = "Done"
  PostEvent(#OwnEvent_StatusBarField_0)
  
  PostEvent(#OwnEvent_Stopped)
  
EndProcedure


Procedure Run(*Parameter.MainThread_Structure)
  ClearList(*Parameter\PingInfoList())
  ClearGadgetItems(#Gadget_ListIcon)
  ScannedAddresses = 0
  TotalAddresses = 0
  CurrentConnection = "Scanning ..."
  PostEvent(#OwnEvent_StatusBarField_0)
  MainThread\Exit = #False
  MainThread\Thread = CreateThread(@RunScan(), @MainThread)
EndProcedure




;-Main
Define sStartAddress.s = "192.168.0.1"
Define sEndAddress.s = "192.168.0.254"
Define RoundTripTime$
Define.i Quit, OnlineHostCount, ShowItemFlag, NextAction, i
Define *Address.PINGINFO


If OpenWindow(#Window_Main, 0, 0, #WindowWidth, #WindowHeight, "IP Scanner", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  If CreateStatusBar(#StatusBar, WindowID(#Window_Main))
    AddStatusBarField(250)
    AddStatusBarField(140)
    AddStatusBarField(140)
    AddStatusBarField(110)
  EndIf
  
  TextGadget(#Gadget_Text_Start, 10, 10, 80, 20, "Start address")
  IPAddressGadget(#Gadget_IP_Start, 100, 10, 120, 20)
  SetGadgetState(#Gadget_IP_Start, MakeIPAddress(Val(StringField(sStartAddress, 1, ".")), Val(StringField(sStartAddress, 2, ".")), Val(StringField(sStartAddress, 3, ".")), Val(StringField(sStartAddress, 4, "."))))
  
  TextGadget(#Gadget_Text_End, 10, 30, 80, 20, "End address")
  IPAddressGadget(#Gadget_IP_End, 100, 30, 120, 20)
  SetGadgetState(#Gadget_IP_End, MakeIPAddress(Val(StringField(sEndAddress, 1, ".")), Val(StringField(sEndAddress, 2, ".")), Val(StringField(sEndAddress, 3, ".")), Val(StringField(sEndAddress, 4, "."))))
  
  ButtonGadget(#Gadget_Button_OK, 230, 10, 60, 20, "Go")
  ButtonGadget(#Gadget_Button_Cancel, 230, 30, 60, 20, "Cancel")
  
  CheckBoxGadget(#Gadget_ShowOffline, 360, 10, 120, 20, "Show offline hosts")
  SetGadgetState(#Gadget_ShowOffline, #PB_Checkbox_Checked)
  
  CheckBoxGadget(#Gadget_SearchHostName, 360, 30, 120, 20, "Get hostname")
  SetGadgetState(#Gadget_SearchHostName, #PB_Checkbox_Checked)
  
  ListIconGadget(#Gadget_ListIcon, 10, 50, #WindowWidth - 20, #WindowHeight - 80, "Address", 120, #PB_ListIcon_FullRowSelect|#PB_ListIcon_GridLines)
  AddGadgetColumn(#Gadget_ListIcon, 1, "HostName", 100)
  AddGadgetColumn(#Gadget_ListIcon, 2, "Time", 80)
  
  MainThread\Mutex = CreateMutex()
  
  ;-MainLoop
  Repeat
    Select WaitWindowEvent()
      
      Case #PB_Event_CloseWindow
        NextAction = #QuitScan
        CreateThread(@Stop(), @MainThread)
        
      Case #PB_Event_Gadget
        Select EventGadget()            
          Case #Gadget_Button_OK
            DisableGadget(#Gadget_IP_Start, #True)
            DisableGadget(#Gadget_IP_End, #True)
            DisableGadget(#Gadget_Button_OK, #True)
            DisableGadget(#Gadget_SearchHostName, #True)
            DisableGadget(#Gadget_ShowOffline, #True)
            
            If GetGadgetState(#Gadget_SearchHostName) = #PB_Checkbox_Checked
              SearchHostName = #True
            Else
              SearchHostName = #False
            EndIf
            OnlineHostCount = 0
            NextAction = #StartScan
            CreateThread(@Stop(), @MainThread)
            
          Case #Gadget_Button_Cancel
            NextAction = #StopScan
            CreateThread(@Stop(), @MainThread)
            
        EndSelect
        
      Case #OwnEvent_TestConnectionToIP_Finished
        *Address = EventData()
        
        ShowItemFlag = #True
;         If GetGadgetState(#Gadget_ShowOffline) = #PB_Checkbox_Unchecked
;           If *Address\HostState = #Offline
;             ShowItemFlag = #False
;           EndIf
;         EndIf
        
        If ShowItemFlag
          If *Address\HostState = #Offline
            RoundTripTime$ = ""
          Else
            RoundTripTime$ = Str(*Address\RoundTrip)
            OnlineHostCount + 1
          EndIf
          
          While *Address\Index >= CountGadgetItems(#Gadget_ListIcon)
            AddGadgetItem(#Gadget_ListIcon, -1, "")
          Wend
          SetGadgetItemText(#Gadget_ListIcon, *Address\Index, IPString(*Address\IPAddress) + #LF$ + *Address\HostName + #LF$ + RoundTripTime$)
          
;           For i = CountGadgetItems(#Gadget_ListIcon) - 1 To 0 Step -1
;             If GetGadgetItemText(#Gadget_ListIcon, i, 2) = ""
;               RemoveGadgetItem(#Gadget_ListIcon, i)
;             EndIf
;           Next i
          
          StatusBarText(#StatusBar, 1, Str(OnlineHostCount) + " Online Hosts")
        EndIf
        
      Case #OwnEvent_StatusBarField_0
        StatusBarText(#StatusBar, 0, CurrentConnection)
        
      Case #OwnEvent_StatusBarField_2
        StatusBarText(#StatusBar, 2, Str(ScannedAddresses) + " / " + Str(TotalAddresses) + " IP Addresses")
        
      Case #OwnEvent_StatusBarField_3
        If TotalThreads = 0
          CurrentConnection = "Done"
          PostEvent(#OwnEvent_StatusBarField_0)
          NextAction = #StopScan
          PostEvent(#OwnEvent_Stopped)
        EndIf
        StatusBarText(#StatusBar, 3, Str(TotalThreads) + " Threads")
        
      Case #OwnEvent_Stopped
        Select NextAction 
          Case #StartScan
            Run(@MainThread)
            
          Case #StopScan
            If GetGadgetState(#Gadget_ShowOffline) = #PB_Checkbox_Unchecked
              For i = CountGadgetItems(#Gadget_ListIcon) - 1 To 0 Step -1
                If GetGadgetItemText(#Gadget_ListIcon, i, 2) = ""
                  RemoveGadgetItem(#Gadget_ListIcon, i)
                EndIf
              Next i
            EndIf
            
            DisableGadget(#Gadget_IP_Start, #False)
            DisableGadget(#Gadget_IP_End, #False)
            DisableGadget(#Gadget_Button_OK, #False)
            DisableGadget(#Gadget_SearchHostName, #False)
            DisableGadget(#Gadget_ShowOffline, #False)
            DisableGadget(#Gadget_Button_Cancel, #True)
            
          Case #QuitScan
            Quit = #True
            
        EndSelect
      
    EndSelect
    
  Until Quit
  
  FreeMutex(MainThread\Mutex)
EndIf
tatanas
Enthusiast
Enthusiast
Posts: 199
Joined: Wed Nov 06, 2019 10:28 am
Location: France

Re: IP Scanner (Threaded)

Post by tatanas »

Strangely, I played with it a lot and didn't get crashes with "my version".

Thank you very much for the fixes.

questions :
- You use Mutex when using TotalThreads or ScannedAddresses global variables, is it mandatory when we just increment/decrement those variables by the worker threads ?
- You replaced FreeStructure() by FreeMemory() (with the changes of structure declaration), i thought FreeStructure released the memory too ?
Windows 10 Pro x64
PureBasic 6.04 x64
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: IP Scanner (Threaded)

Post by infratec »

You are using threads which all access the same variable.
This is not an atomic operation. You can not be sure that 2 threads want to access the same variable at the same time.
You have to avoid this. Exactly for this is a mutex.

The length of the buffer is allocated by AllocateMemory(), because it is not only the fixed structure, you have also to add the space for the message.
AllocateStructure() can not handle this.
tatanas
Enthusiast
Enthusiast
Posts: 199
Joined: Wed Nov 06, 2019 10:28 am
Location: France

Re: IP Scanner (Threaded)

Post by tatanas »

Thank you for the clarifications
Windows 10 Pro x64
PureBasic 6.04 x64
Post Reply