Что нового

Повышение стабильности работы TCP связки клиент\сервер

Medic84

Омега
Команда форума
Администратор
Сообщения
1,590
Репутация
341
Суть такая, повышаю скилл в TCP, однако уже сутки бьюсь над стабильной работой программы. Программа нужна для работы - администрирование компьютерного класса. Принцип работы: отправляем с сервера команду в виде AutoIT-кода и все компьютеры в сети его выполняют.
Хочу сделать так, чтобы клиенты ждали пока серверная программа появится в сети и отдаст команду, и ждали его даже при повторных перезапусках серверной программы.
То же самое с серверной программой, должна принимать все соединения, и если сокет клиента стал недоступен - удалять из списка.

Основу программы написать удалось. Все работает, но только если ничего не закрывать, и то бывают ошибки. Если пробовать что то перезапускать. поведение программы становится непредсказуемым. Есть у кого нибудь какие идеи?

Сервер:
Код:
#pragma compile(Console, False)
#pragma compile(x64, False)
#pragma compile(ExecLevel, AsInvoker)
#pragma compile(AutoItExecuteAllowed, False)
#pragma compile(Stripper, True)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <EditConstants.au3>
#include <Array.au3>
#include <Misc.au3>
#include <GuiListView.au3>

Opt("TCPTimeout", 20)

AdlibRegister("RegisterComputers", 1000)

TCPStartup()

OnAutoItExitRegister("OnAutoItExit")

Global $sIPAddress = @IPAddress1
Global $iPort = 65431, $iClientPort = 65432, $iError = 0
Dim $aConnectedComputers[1][3] = [[0, "ClientConnect", "ListenPort"]]
Global $iListenSocket = TCPListen($sIPAddress, $iPort, 20)

$sTitle = "Администрирование компьютерного класса"
$hGUI = GUICreate($sTitle, 800, 200)
$idText = GUICtrlCreateEdit("Ожидание подключения компьютеров...", 10, 10, 580, 145, BitOR($ES_WANTRETURN, $ES_READONLY))
$idInput = GUICtrlCreateInput("", 10, 165, 495, 25)
GUICtrlSendMsg($idInput, $EM_SETCUEBANNER, True, "Отправить команду ...")
$idBtnSend = GUICtrlCreateButton("Send", 510, 165, 80, 25)
$hList = GUICtrlCreateListView("Компьютеры", 600, 10, 190, 180)
_GUICtrlListView_SetColumnWidth($hList, 0, 180)

GUISetState(@SW_SHOW, $hGUI)

While 1
	If _IsPressed('0D') Then _Send()
	Switch GUIGetMsg()
		Case $GUI_EVENT_CLOSE
			ExitLoop
		Case $idBtnSend
			_Send()
	EndSwitch
WEnd

Func _Send()
	For $i = 1 To $aConnectedComputers[0][0]
		TCPSend($aConnectedComputers[$i][1], GUICtrlRead($idInput))
		If @error Then
			ConsoleWrite("Send Error: " & @error & @CRLF)
			_Delete($i)
		EndIf
	Next
	GUICtrlSetData($idInput, '')
EndFunc   ;==>_Send

Func RegisterComputers()
	Local $iSocket, $_sIP

	$iSocket = TCPAccept($iListenSocket)
	If @error Then
		_LogSetText("Could not accept the incoming connection, Error code: " & @error)
	Else
		If $iSocket = -1 Then Return

		$_sIP = SocketToIP($iSocket)
		_ArrayAdd($aConnectedComputers, $_sIP)
		$aConnectedComputers[0][0] = UBound($aConnectedComputers) - 1
		_GUICtrlListView_AddItem($hList, $_sIP)

		For $i = 1 To $aConnectedComputers[0][0]
			If Not $aConnectedComputers[$i][1] Then
				$aConnectedComputers[$i][1] = TCPConnect($aConnectedComputers[$i][0], $iClientPort)
				$aConnectedComputers[$i][2] = $iSocket
				If @error Then
					ConsoleWrite("Connect Error: " & @error & @CRLF)
					_Delete($i)
					Return
				EndIf
			EndIf

			$sRecv = TCPRecv($aConnectedComputers[$i][2], 1024)
			If @error Then
				ConsoleWrite("Recv Error: " & @error & @CRLF)
				_Delete($i)
				Return
			EndIf
			If $sRecv Then _LogSetText($sRecv)
		Next
	EndIf
	TCPCloseSocket($iSocket)
EndFunc   ;==>RegisterComputers

Func _Delete($iIndex)
	_ArrayDelete($aConnectedComputers, $iIndex)
	$aConnectedComputers[0][0] = UBound($aConnectedComputers) - 1
EndFunc

Func OnAutoItExit()
	TCPCloseSocket($iListenSocket)
	TCPShutdown()
EndFunc   ;==>OnAutoItExit

Func SocketToIP($iSocket)
	Local $tSockAddr = 0, $aRet = 0
	$tSockAddr = DllStructCreate("short;ushort;uint;char[8]")
	$aRet = DllCall("Ws2_32.dll", "int", "getpeername", "int", $iSocket, "struct*", $tSockAddr, "int*", DllStructGetSize($tSockAddr))
	If Not @error And $aRet[0] = 0 Then
		$aRet = DllCall("Ws2_32.dll", "str", "inet_ntoa", "int", DllStructGetData($tSockAddr, 3))
		If Not @error Then Return $aRet[0]
	EndIf
	Return 0
EndFunc   ;==>SocketToIP

Func _LogSetText($sText)
	GUICtrlSetData($idText, GUICtrlRead($idText) & @CRLF & $sText)
EndFunc   ;==>_LogSetText


Клиент:
Код:
#pragma compile(ExecLevel, AsInvoker)
#pragma compile(AutoItExecuteAllowed, True)
#pragma compile(InputBoxRes, False)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <Array.au3>

TCPStartup()
Opt("TCPTimeout", 10)

OnAutoItExitRegister("OnAutoItExit")

Global $sIPAddress = @IPAddress1, $sServerIPAddress = @IPAddress1 ;"10.1.10.188"
Global $iServerPort = 65431, $iPort = 65432
Global $iSocket = 0, $iListenSocket = 0, $iConnectSocket = 0
Global $iLSocket = 0

_TryConnect($sServerIPAddress, $iServerPort)

Global $iListenSocket = TCPListen($sIPAddress, $iPort, 2)
If @error Then _Log("Could not listen, Error code: " & @error)

While 1
	$iSocket = TCPAccept($iListenSocket)
	If @error Then
		_Log("Could not accept the incoming connection, Error code: " & @error & " Try New Connect")
		_TryConnect($sServerIPAddress, $iServerPort)
	Else
		If Not ($iSocket = -1) Then $iLSocket = $iSocket
		$sRecv = TCPRecv($iLSocket, 1024)
		If @error Then
			_Log("Recv Error: " & @error & " Try to reconnect")
		EndIf
		If $sRecv Then
			Run(@ScriptFullPath & ' /AutoIt3ExecuteLine ' & $sRecv)
			Sleep(100)
			If WinExists("AutoIt Error") Then
				TCPSend($iConnectSocket, $sIPAddress & " - Script Error")
				ControlClick("AutoIt Error", "", "[CLASS:Button; ID:2]")
			EndIf
		EndIf
	EndIf
WEnd

Func _TryConnect($_sServerIPAddress, $_iPort)
	If $iConnectSocket <> 0 Then
		TCPCloseSocket($iConnectSocket)
		$iConnectSocket = 0
	EndIf
	While 1
		$iConnectSocket = TCPConnect($_sServerIPAddress, $_iPort)
		If Not @error  Then
			TCPSend($iConnectSocket, $sIPAddress & " - подключен")
			ExitLoop
		EndIf
	WEnd
EndFunc   ;==>_TryConnect

Func OnAutoItExit()
	TCPSend($iConnectSocket, $sIPAddress & " - отключен")
	TCPCloseSocket($iListenSocket)
	TCPCloseSocket($iConnectSocket)
	TCPShutdown()
EndFunc   ;==>OnAutoItExit

Func _Log($sLogText)
	FileWriteLine("Log.txt", $sLogText)
EndFunc


P.S. Да, я знаю что можно придумать варианты проще, но хочу сделать через TCP-на данный момент.
 

Prog

Продвинутый
Сообщения
537
Репутация
65
В коде нет проверки правильности принятых данных. Клиент может получить только часть данных и попытаться их выполнить...
Нужен протокол обмена, где должны быть как минимум поля "Команда", "Длина посылки", "Данные" и "Контрольная сумма".
Выполнять принятый код (Поле "Данные") только в том случае, если длина посылки и контрольная сумма правильные.
 
Автор
Medic84

Medic84

Омега
Команда форума
Администратор
Сообщения
1,590
Репутация
341
Prog [?]
В коде нет проверки правильности принятых данных. Клиент может получить только часть данных и попытаться их выполнить...Нужен протокол обмена, где должны быть как минимум поля "Команда", "Длина посылки", "Данные" и "Контрольная сумма".Выполнять принятый код (Поле "Данные") только в том случае, если длина посылки и контрольная сумма правильные.

Все это - позже, когда будет целостная картина более менее стабильного соединения. В данный момент я посылаю небольшие данные, данные принимаются полностью.
 

SlavaS

Знающий
Сообщения
35
Репутация
5
Извиняюсь конечно с запоздалым ответом :smile:
Давненько заморачивался таким вопросом, тоже мучился со стабильностью. Подправил скрипт из первого поста, делал на версии 3.3.8.1.
Конечно криво и через костыли написано, но соединение было стабильным и работало все норм.
Так что сильно не критикуйте :smile:
Сервер:
Код:
#include <GUIConstantsEx.au3>
;~ #include <MsgBoxConstants.au3>
#include <EditConstants.au3>
#include <Array.au3>
#include <Misc.au3>
#include <GuiListView.au3>

Opt("TCPTimeout", 20)

TCPStartup()

OnAutoItExitRegister("OnAutoItExit")

Global $sIPAddress = @IPAddress1
Global $iPort = 65431, $iClientPort = 65432, $iError = 0
Dim $aConnectedComputers[1][3] = [[0, "ClientConnect", "ListenPort"]]
Global $iListenSocket
Global $chr_crlf = "-CRLF-", $chr_array = "-A_S-"
Global $l = 0
Dim $iConnections[20]

$sTitle = "Администрирование компьютерного класса"
$hGUI = GUICreate($sTitle, 800, 200)
$idText = GUICtrlCreateEdit("Ожидание подключения компьютеров...", 10, 10, 580, 145, BitOR($ES_WANTRETURN, $ES_READONLY))
$idInput = GUICtrlCreateInput("", 10, 165, 495, 25)
GUICtrlSendMsg($idInput, $EM_SETCUEBANNER, True, "Отправить команду ...")
$idBtnSend = GUICtrlCreateButton("Send", 510, 165, 80, 25)
$hList = GUICtrlCreateListView("Компьютеры|Socket|", 600, 10, 190, 180)
_GUICtrlListView_SetColumnWidth($hList, 0, 100)
_GUICtrlListView_SetColumnWidth($hList, 1, 80)

GUISetState(@SW_SHOW, $hGUI)

StartListen()

While 1
	Switch GUIGetMsg()
		Case $GUI_EVENT_CLOSE
			ExitLoop
		Case $idBtnSend
			_Send()
	EndSwitch

	;Если пропадает соединение тогда уведомление и удаление из Listview
	If _GUICtrlListView_GetItemCount($hList) > 0 Then
		If $l > _GUICtrlListView_GetItemCount($hList) - 1 Then $l = 0
		TCPRecv(_GUICtrlListView_GetItemText($hList, $l, 1), 0)
		If @error Then
			_LogSetText("Отключен: " & _GUICtrlListView_GetItemText($hList, $l))
			$iConnections[$l] = ""
			_GUICtrlListView_DeleteItem(GUICtrlGetHandle($hList), $l)
			$l = 0
		EndIf
		$l = $l + 1
	EndIf

	$iTempConnection = TCPAccept($iListenSocket)
	If $iTempConnection = -1 Then ContinueLoop

	$index = _ArraySearch($iConnections, $iTempConnection)
	If @error Then
		For $j = 0 To UBound($iConnections) - 1
			If $iConnections[$j] = "" Then
				$iConnections[$j] = $iTempConnection
				$string = TCPRecv($iConnections[$j], 2048)

				If $string = "" Or @error Then
					TCPCloseSocket($iConnections[$j])
					$iConnections[$j] = ""
					ContinueLoop 2
				EndIf

				$aString = _recive_data($string)
				If IsArray($aString) = 0 Then
					TCPCloseSocket($iConnections[$j])
					$iConnections[$j] = ""
					ContinueLoop 2
				EndIf

				;авторизация клиента
				Dim $aNetwrkSend[2] = ["AUTH", "WELCOME"]
				_send_data($iConnections[$j], $aNetwrkSend)

				GUICtrlCreateListViewItem($aString[0] & "|" & $iTempConnection, $hList)
				_LogSetText("Подключен: " & $aString[0])
				ExitLoop
			EndIf
		Next
	EndIf
WEnd

Func _Send()
	$dummy = _GUICtrlListView_GetSelectedIndices($hList, True)
	If $dummy[0] = 0 Then
		MsgBox(0, '0', 'Не выбран клиент')
	Else
		$iSocket = _GUICtrlListView_GetItemText($hList, $dummy[1], 1)
		Dim $aNetwrkSend[3] = ["COMMAND", "MSGBOX", GUICtrlRead($idInput)]
		_send_data($iSocket, $aNetwrkSend)
	EndIf
EndFunc   ;==>_Send

Func StartListen() ;Запуск листинга входящих соединений по порту
	$iListenSocket = TCPListen($sIPAddress, $iPort)
	If $iListenSocket = -1 Then
		$iListenSocket = -1
	EndIf
	For $k = 0 To UBound($iConnections) - 1
		$iConnections[$k] = ""
	Next
EndFunc   ;==>StartListen

;конвертируем массив в строку и отправляем
Func _send_data($iConnectedSocket, $aData)
	If $iConnectedSocket <> -1 Then
		If IsArray($aData) = 1 Then
			$sSendString = _ArrayToString($aData, $chr_array)
			$sSendString = StringReplace($sSendString, @CRLF, $chr_crlf)
			TCPSend($iConnectedSocket, $sSendString)
		EndIf
	EndIf
	Sleep(200)
EndFunc   ;==>_send_data

;конвертируем полученную строку в массив
Func _recive_data($sData)
	If StringInStr($sData, $chr_array) <> 0 Then
		$sReceived = StringReplace($sData, $chr_crlf, @CRLF)
		$aNetworkReceived = StringSplit($sReceived, $chr_array, 3)
		Return $aNetworkReceived
	Else
		Return 0
	EndIf
EndFunc   ;==>_recive_data

Func OnAutoItExit()
	TCPCloseSocket($iListenSocket)
	TCPShutdown()
EndFunc   ;==>OnAutoItExit

Func _LogSetText($sText)
	GUICtrlSetData($idText, GUICtrlRead($idText) & @CRLF & $sText)
EndFunc   ;==>_LogSetText

Клиент:
Код:
#include <GUIConstantsEx.au3>
;~ #include <MsgBoxConstants.au3>
#include <Array.au3>

TCPStartup()
Opt("TCPTimeout", 10)

OnAutoItExitRegister("OnAutoItExit")

Global $sIPAddress = @IPAddress1, $sServerIPAddress = @IPAddress1 ;"10.1.10.188"
Global $iServerPort = 65431, $iPort = 65432
Global $iSocket = 0, $iListenSocket = 0, $iConnectSocket = 0
Global $iLSocket = 0, $tcpconn = 0
Global $chr_crlf = "-CRLF-", $chr_array = "-A_S-"
Global $WaitingAuth = True

AdlibRegister('_CheckPort', 1 * (30 * 1000)) ;проверяем соединение с сервером каждые 30 секунд
_CheckPort()

While 1
	$awaitingauth = True

	While ($tcpconn = 1)
		$iSocket = TCPConnect($sServerIPAddress, $iServerPort) 
		If @error Then
			TCPCloseSocket($iSocket)
			$tcpconn = 0
			ExitLoop
		EndIf

		Dim $aNetwrksend[4] = [@IPAddress1, @UserName, @ComputerName, @OSVersion]
		_send_data($iSocket, $aNetwrksend)
		If @error Then
			TCPCloseSocket($iSocket)
			$tcpconn = 0
			ExitLoop
		EndIf

		$bytes = 1024
		Do
			$_vRecv = TCPRecv($iSocket, $bytes)
			If Not @error And $_vRecv <> "" Then
				_datarecieved($iSocket, $_vRecv, $bytes)
			EndIf
			If @error Then
				TCPCloseSocket($iSocket)
				$tcpconn = 0
				ExitLoop
			EndIf
		Until False
		$tcpconn = 0
	WEnd
WEnd ; => END TCPCONNECT

#region ************ Получение и отправка данных ************
Func _datarecieved($iConn, $sReceivedData, $bytes)
	$aParsedData = _recive_data($sReceivedData)
	If Not IsArray($aParsedData) Then Return 0

	;авторизация клиента
	If $aParsedData[0] = "AUTH" Then
		If $aParsedData[1] = "WELCOME" Then
			$WaitingAuth = False
		Else
			$WaitingAuth = True
			TCPCloseSocket($iConn)
			Sleep(50)
		EndIf
	EndIf

	;Получение команд от сервера при успешной авторизации
	If $WaitingAuth = False And $aParsedData[0] = "COMMAND" Then
		Switch StringUpper($aParsedData[1])
			Case "MSGBOX"
				$title = 'Тест'
				$text = $aParsedData[2]
				$icon = '0'
				Run(@ScriptFullPath & ' /AutoIt3ExecuteLine "MsgBox(' & $icon & ', ''' & $title & ''', ''' & $text & ''')"')
		EndSwitch
	EndIf

EndFunc   ;==>_datarecieved

Func _send_data($iConnectedSocket, $aData) ;отправка данных массивом
	If $iConnectedSocket <> -1 Then
		If IsArray($aData) = 1 Then
			$sSendString = _ArrayToString($aData, $chr_array)
			$sSendString = StringReplace($sSendString, @CRLF, $chr_crlf)
			TCPSend($iConnectedSocket, $sSendString)
			If @error Then
				Return SetError(@error, @extended, 0)
			EndIf
		EndIf
	EndIf
	Sleep(200)
EndFunc   ;==>_send_data

Func _recive_data($sData) ;получение данных в виде массива
	If StringInStr($sData, $chr_array) <> 0 Then
		$sReceived = StringReplace($sData, $chr_crlf, @CRLF)
		$aNetwrkReceived = StringSplit($sReceived, $chr_array, 3)
		Return $aNetwrkReceived
	Else
		Return 0
	EndIf
EndFunc   ;==>_recive_data
#endregion ************ Получение и отправка данных ************

Func OnAutoItExit()
    TCPSend($iConnectSocket, $sIPAddress & " - отключен")
    TCPCloseSocket($iListenSocket)
    TCPCloseSocket($iConnectSocket)
    TCPShutdown()
EndFunc   ;==>OnAutoItExit

Func _Log($sLogText)
    FileWriteLine("Log.txt", $sLogText)
EndFunc

Func _CheckPort()
	If $tcpconn = 0 Then
		$sTestSocket = TCPConnect($sServerIPAddress, $iServerPort) ;Проверка перед запуском на наличие запущенного сервера
		If $sTestSocket = -1 And @error Then
			$tcpconn = 0
		Else
			$tcpconn = 1
		EndIf
		TCPCloseSocket($sTestSocket)
	EndIf
EndFunc   ;==>_CheckPort
 

40OIL

Новичок
Сообщения
1
Репутация
0
[deleted]
 
Последнее редактирование:
Верх