本帖最后由 imutraveler 于 2017-2-13 00:36 编辑
最近在写自己使用的服务端和客户端程序
服务端采用 “服务” 的 方式,读取ACCESS,给客户端发送数据
使用的是P版的服务程序,运行过程中没问题!
只是,当手动停止该服务时,
会出现:服务显示已停止,但是进程依然在,需要等待 30秒,服务超时,进程才会退出。
为什么会出现这种问题呢?
经排查应该是循环的问题,不过在while 1 中添加了判断,当服务状态被设置为stopped时候,循环退出。循环是退出了,可进程还是在~
才疏学浅,请指点下!
服务端代码,编译之后,手动运行即可安装服务
#NoTrayIcon
#Region ;**** 由 AccAu3Wrapper_GUI 创建指令 ****
#AccAu3Wrapper_OutFile=Server.exe
#AccAu3Wrapper_UseX64=n
#AccAu3Wrapper_Res_Comment=Traveler.
#AccAu3Wrapper_Res_Description=服务程序
#AccAu3Wrapper_Res_Fileversion=1.0
#AccAu3Wrapper_Res_ProductVersion=1.0
#AccAu3Wrapper_Res_LegalCopyright=Traveler.
#AccAu3Wrapper_Res_Language=2052
#AccAu3Wrapper_Res_requestedExecutionLevel=None
#EndRegion ;**** 由 AccAu3Wrapper_GUI 创建指令 ****
;~ #AccAu3Wrapper_Res_Field=ProductName|TestName
;~ #AccAu3Wrapper_Res_Field=CompanyName|TestCompany
#include <File.au3>
#include <LocalSecurityAuthority.au3>
Const $SERVICE_WIN32_OWN_PROCESS = 0x00000010
Const $SERVICE_WIN32_SHARE_PROCESS = 0x00000020
Const $SERVICE_INTERACTIVE_PROCESS = 0x00000100
Const $SERVICE_STOPPED = 0x00000001
Const $SERVICE_RUNNING = 0x00000004
Const $SERVICE_CONTINUE_PENDING = 0x00000005
Const $SERVICE_PAUSED = 0x00000007
Const $SERVICE_ACCEPT_STOP = 0x00000001
Const $SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002
Const $SERVICE_CONTROL_STOP = 0x00000001
Const $SERVICE_CONTROL_PAUSE = 0x00000002
Const $SERVICE_CONTROL_CONTINUE = 0x00000003
Const $SERVICE_CONTROL_INTERROGATE = 0x00000004
Const $tagSERVICE_STATUS = "dword ServiceType;dword CurrentState;dword ControlsAccepted;dword Win32ExitCode;dword ServiceSpecificExitCode;dword CheckPoint;dword WaitHint"
; 定义主服务全局变量
Global $sIPAddress = @IPAddress1
Global $sPort = 33891
Global $DbPath, $DbPass, $TableGroup, $TableServer, $TableUsers, $RS, $Conn
Global $MainSocket, $ConnectedSocket, $szIP_Accepted, $Recv, $sRecv, $Msg
Global $OnlineList[0][2] ;在线列表
Global $LogDir = @ScriptDir & "\Log"
Global $LogFile = $LogDir & "\" & @YEAR & "-" & @MON & "-" & @MDAY & ".log"
; SCM - 服务控制器 (Service Control Manager)。
; 此服务程序的名称。
Global $sServiceName = "066scTest"
; 判断此服务程序是否由SCM启动。
Local $tProcessBasic = DllStructCreate("dword;ptr;ulong_ptr;ulong[3]")
_NtQueryInformationProcess(-1, 0, DllStructGetPtr($tProcessBasic), 24)
If DllStructGetData($tProcessBasic, 4, 3) <> ProcessExists("Services.exe") Then
$hService = _LsaOpenService($sServiceName, 0xF01FF)
If ($hService = 0) Then $hService = _CreateService($sServiceName, $sServiceName, _
bitOr($SERVICE_WIN32_OWN_PROCESS, $SERVICE_INTERACTIVE_PROCESS), _
2, 0, @ScriptFullPath, "", 0)
_StartService($hService)
_LsaCloseServiceHandle($hService)
exit
EndIf
; 定义几个全局变量,以便在多个函数中共享它们的值。
Global $hServiceMain, $hHandlerEx, $tServiceTable
Global $tServiceStatus, $pServiceStatus, $hServiceStatus
; 注册服务入口点函数,用于在SCM启动服务时,向SCM报告自己的状态。
$hServiceMain = DllCallbackRegister("_ServiceMain", "none", "dword;ptr")
; 子控制请求函数,用于接收SCM发来的请求,并报告自己的状态。
$hHandlerEx = DllCallbackRegister("_HandlerEx", "dword", "dword;dword;ptr;ptr")
$tServiceTable = DllStructCreate("ptr;ptr;ptr;ptr;char[256]")
DllStructSetData($tServiceTable, 1, DllStructGetPtr($tServiceTable, 5)) ; 服务名称。
DllStructSetData($tServiceTable, 2, DllCallbackGetPtr($hServiceMain)) ; 入口点函数地址。
DllStructSetData($tServiceTable, 5, $sServiceName)
$tServiceStatus = DllStructCreate($tagSERVICE_STATUS)
$pServiceStatus = DllStructGetPtr($tServiceStatus)
DllStructSetData($tServiceStatus, "ServiceType", _ ; 服务类型
bitOr($SERVICE_WIN32_OWN_PROCESS, _ ; 独享进程。
$SERVICE_INTERACTIVE_PROCESS)) ; 允许与桌面交互。
DllStructSetData($tServiceStatus, "ControlsAccepted", _ ; 指定允许接收的后续控制请求。
$SERVICE_ACCEPT_STOP) ; 服务可以停止。
;~ bitOr($SERVICE_ACCEPT_STOP, _ ; 服务可以停止。
;~ $SERVICE_ACCEPT_PAUSE_CONTINUE)) ; 服务可以暂停和继续。
; 启动服务的控制调度分派线程。
; 当SCM启动某个服务时,服务进程的主线程必须在30秒内调用此函数。
; SCM将分派表传递给StartServiceCtrlDispatcher,这将把调用进程的主线程转换为控制分派器,
; 该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 入口点函数。
; 如果 StartServiceCtrlDispatcher 函数30秒没有被调用, 便会出现1053的错误(服务没有及时响应控制请求)。
_StartServiceCtrlDispatcher(DllStructGetPtr($tServiceTable))
Func _ServiceMain($iNumberofArgs, $pArguments)
Local $aProcess, $hProcess, $hToken, $aPriv[1][2] = [[$SE_DEBUG_NAME, 2]]
; 注册服务的“控制处理器”,以用于接收停止、暂停等请求操作,应在ServiceMain函数中尽早调用。
$hServiceStatus = _RegisterServiceCtrlHandlerEx($sServiceName, DllCallbackGetPtr($hHandlerEx))
; 向SCM报告自己的状态。
DllStructSetData($tServiceStatus, "CurrentState", $SERVICE_RUNNING)
_SetServiceStatus($hServiceStatus, $pServiceStatus)
; 自定义功能
; 服务主函数
Call("MainSvr")
EndFunc ;==>_ServiceMain
; 服务的“控制处理器”,用于处理SCM发出的各种请求。
Func _HandlerEx($iRequest, $iEventType, $pEventData, $pContext)
Switch $iRequest
Case $SERVICE_CONTROL_STOP ; 服务需停止。
; 如果30秒内没有向SCM报告自己的状态,将会出现1053的错误。
DllStructSetData($tServiceStatus, "CurrentState", $SERVICE_STOPPED)
_SetServiceStatus($hServiceStatus, $pServiceStatus)
Call("SvrExit")
Return 0
Case $SERVICE_CONTROL_PAUSE ; 服务需暂停。
; 同上,必须在30秒调用,否则出错。
DllStructSetData($tServiceStatus, "CurrentState", $SERVICE_PAUSED)
_SetServiceStatus($hServiceStatus, $pServiceStatus)
Return 0
Case $SERVICE_CONTROL_CONTINUE ; 服务需要继续。
; 同上。
DllStructSetData($tServiceStatus, "CurrentState", $SERVICE_RUNNING)
_SetServiceStatus($hServiceStatus, $pServiceStatus)
Return 0
Case $SERVICE_CONTROL_INTERROGATE ; 服务需要向SCM报告自己的状态。
_SetServiceStatus($hServiceStatus, $pServiceStatus)
Return 0
EndSwitch
EndFunc ;==>_HandlerEx
Func _SetServiceStatus($hServiceStatus, $pServiceStatus)
Local $iResult
$iResult = DllCall("AdvApi32.dll", "int", "SetServiceStatus", _
"hWnd", $hServiceStatus, "ptr", $pServiceStatus)
Return SetError(_GetLastError(), 0, $iResult[0])
EndFunc ;==>_SetServiceStatus
Func _StartService($hService, $iNumberofArgs = 0, $pArguments = 0)
Local $iResult
$iResult = DllCall("AdvApi32.dll", "int", "StartService", "hWnd", $hService, _
"dword", $iNumberofArgs, "ptr", $pArguments)
Return SetError(_GetLastError(), 0, $iResult[0])
EndFunc ;==>_StartService
Func _RegisterServiceCtrlHandlerEx($sServiceName, $pHandlerEx, $pContext = 0)
Local $iResult
$iResult = DllCall("AdvApi32.dll", "hWnd", "RegisterServiceCtrlHandlerEx", _
"str", $sServiceName, "ptr", $pHandlerEx, "ptr", $pContext)
Return SetError(_GetLastError(), 0, $iResult[0])
EndFunc ;==>_RegisterServiceCtrlHandlerEx
Func _StartServiceCtrlDispatcher($pServiceTable)
Local $iResult
$iResult = DllCall("AdvApi32.dll", "int", "StartServiceCtrlDispatcher", _
"ptr", $pServiceTable)
Return SetError(_GetLastError(), 0, $iResult[0])
EndFunc ;==>_StartServiceCtrlDispatcher
Func _CreateService($sServiceName, $sDisplayName, $iServiceType, $iStartType, _
$iErrorControl, $sBinaryPath, $sLoadOrderGroup, $aDependencies, _
$sStartName = "", $sPassword = "", $iDesiredAccess = 0xF01FF)
Local $iResult, $tServiceName, $tDisplayName, $tBinaryPath, $hSC
Local $tLoadOrder, $tDependencies, $tStartName, $tPassword, $sBuffer
$tServiceName = DllStructCreate("wchar ServiceName[" & StringLen($sServiceName) + 1 & "]")
DllStructSetData($tServiceName, "ServiceName", $sServiceName)
If $sDisplayName <> "" Then
$tDisplayName = DllStructCreate("wchar DisplayName[" & StringLen($sDisplayName) + 1 & "]")
DllStructSetData($tDisplayName, "DisplayName", $sDisplayName)
EndIf
If $sBinaryPath <> "" Then
$tBinaryPath = DllStructCreate("wchar BinaryPath[" & StringLen($sBinaryPath) + 1 & "]")
DllStructSetData($tBinaryPath, "BinaryPath", $sBinaryPath)
EndIf
If $sLoadOrderGroup <> "" Then
$tLoadOrder = DllStructCreate("wchar LoadOrder[" & StringLen($sLoadOrderGroup) + 1 & "]")
DllStructSetData($tLoadOrder, "LoadOrder", $sLoadOrderGroup)
EndIf
If $sStartName <> "" Then
$tStartName = DllStructCreate("wchar StartName[" & StringLen($sStartName) + 1 & "]")
DllStructSetData($tStartName, "StartName", $sStartName)
EndIf
If $sPassword <> "" Then
$tPassword = DllStructCreate("wchar Password[" & StringLen($sPassword) + 1 & "]")
DllStructSetData($tPassword, "Password", $sPassword)
EndIf
If IsArray($aDependencies) And UBound($aDependencies, 0) = 1 Then
For $i = 0 To UBound($aDependencies) - 1
$sBuffer &= "wchar[" & StringLen($aDependencies[$i]) + 1 & "];"
Next
$tDependencies = DllStructCreate($sBuffer & ";wchar[1]")
For $i = 0 To UBound($aDependencies) - 1
DllStructSetData($tDependencies, ($i + 1), $aDependencies[$i])
Next
EndIf
$hSC = _LsaOpenSCManager("", 2)
$iResult = DllCall("AdvApi32.dll", "hWnd", "CreateServiceW", _
"hWnd", $hSC, _
"ptr", DllStructGetPtr($tServiceName), _
"ptr", DllStructGetPtr($tDisplayName), _
"dword", $iDesiredAccess, _
"dword", $iServiceType, _
"dword", $iStartType, _
"dword", $iErrorControl, _
"ptr", DllStructGetPtr($tBinaryPath), _
"ptr", DllStructGetPtr($tLoadOrder), _
"ptr", 0, _
"ptr", DllStructGetPtr($tDependencies), _
"ptr", DllStructGetPtr($tStartName), _
"ptr", DllStructGetPtr($tPassword))
Return SetError(_GetLastError(), _LsaCloseServiceHandle($hSC), $iResult[0])
EndFunc ;==>_CreateService
Func _NtQueryInformationProcess($hProcess, $iClass, $pBuffer, $iSizeofBuffer)
Local $iResult
$iResult = DllCall("Ntdll.dll", "dword", "NtQueryInformationProcess", "hWnd", $hProcess, _
"int", $iClass, "ptr", $pBuffer, "ulong", $iSizeofBuffer, "ulong*", 0)
Return SetError($iResult[0], $iResult[5], $iResult[0] = 0)
EndFunc ;==>_NtQueryInformationProcess
Func MainSvr()
If Not FileExists($LogDir) Then DirCreate($LogDir)
;~ AdlibRegister("ReduceMemory", 1000 * 10) ;10秒整理一次内存
Call("ConnectDB")
TCPStartup()
$ConnectedSocket = -1
;创建监听
$MainSocket = TCPListen($sIPAddress, $sPort)
If $MainSocket = -1 Then
_FileWriteLog($LogFile, "创建监听失败,请重新设置端口" )
Exit
Else
_FileWriteLog($LogFile, "创建监听成功,当前服务端口: " & $sPort )
EndIf
While 1
_Accept()
_Delete()
_RecvMsg()
Sleep(50)
WEnd
EndFunc
Func _Accept() ;接受新客户端连接,并维护socket表
$ConnectedSocket = TCPAccept($MainSocket)
If $ConnectedSocket <> -1 Then ;<>-1表示有新客户端连接
$szIP_Accepted = SocketToIP($ConnectedSocket) ;socket转成IP
$all = UBound($OnlineList) + 1
ReDim $OnlineList[$all][2] ;在线列表数组增加
$OnlineList[$all - 1][0] = $ConnectedSocket ;socket
$OnlineList[$all - 1][1] = $szIP_Accepted ;ip
_FileWriteLog($LogFile, $szIP_Accepted & " 上线 - " & @HOUR & ":" & @MIN & ":" & @SEC)
EndIf
EndFunc ;==>_Accept
Func _Delete() ;删除关闭的socket列表
For $i = 0 To UBound($OnlineList) - 1
$recv = TCPsend($OnlineList[$i][0], "")
If @error Then
;显示离线
_FileWriteLog($LogFile, $OnlineList[$i][1] & " 离线 - " & @HOUR & ":" & @MIN & ":" & @SEC)
;删除数组
_ArrayDelete($OnlineList, $i)
ExitLoop ;这个退出用来防止删除过多的socket,如果没有这个可能导致 $recv = TCPRecv($OnlineList[$i][0], "") 这一句数组错误。
EndIf
Next
EndFunc ;==>_Delete
Func _RecvMsg() ;接收客户机发来数据
For $i = 0 To UBound($OnlineList) - 1
$sRecv = TCPRecv($OnlineList[$i][0], 1024 * 100, 1)
If $sRecv <> "" Then
_FileWriteLog($LogFile, "收到来自 " & $OnlineList[$i][1] & "(" & $OnlineList[$i][0] & ") 的消息: " & BinaryToString($sRecv, 4))
Switch $sRecv
Case "GetTvList"
Local $gList
$RS = ObjCreate("ADODB.Recordset")
$RS.ActiveConnection = $Conn
;~ $RS.Open("Select * From " & $TableGroup & " Order By GroupName")
$RS.Open("Select * From " & $TableGroup)
While Not $RS.eof And Not $RS.bof
If @error = 1 Then ExitLoop
$gList &= "|" & $RS.Fields(1).value
$RS.movenext
WEnd
_SendText($OnlineList[$i][0], $gList)
Case Else
_SendText($OnlineList[$i][0], "已接收到消息: " & BinaryToString($sRecv, 4))
EndSwitch
EndIf
Next
EndFunc ;==>_Delete
Func _SendText($cSocket, $Text);将数据转换成二进制再发送
If $Text <> "" Then
TCPSend($cSocket, StringToBinary($Text, 4))
EndIf
EndFunc ;==>_SendText
Func SocketToIP($SHOCKET)
Local $sockaddr, $aRet
$sockaddr = DllStructCreate("short;ushort;uint;char[8]")
$aRet = DllCall("Ws2_32.dll", "int", "getpeername", "int", $SHOCKET, _
"ptr", DllStructGetPtr($sockaddr), "int*", DllStructGetSize($sockaddr))
If Not @error And $aRet[0] = 0 Then
$aRet = DllCall("Ws2_32.dll", "str", "inet_ntoa", "int", DllStructGetData($sockaddr, 3))
If Not @error Then $aRet = $aRet[0]
Else
$aRet = 0
EndIf
$sockaddr = 0
Return $aRet
EndFunc ;==>SocketToIP
Func ConnectDB() ;连接数据库
$DbPath = @ScriptDir & "\" & "Data.mdb"
$DbPass = "123"
$TableGroup = "TB_Group"
$TableServer = "TB_Server"
$TableUsers = "TB_Users"
$Conn = ObjCreate("ADODB.Connection")
$Conn.Open("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & $DbPath & "; Jet OLEDB:Database Password=" & $DbPass)
EndFunc ;==>ConnectDB
Func ReduceMemory()
$i_PID = @AutoItPID
Local $ai_Handle = DllCall("kernel32.dll", 'int', 'OpenProcess', 'int', 0x1f0fff, 'int', False, 'int', $i_PID)
Local $ai_Return = DllCall("psapi.dll", 'int', 'EmptyWorkingSet', 'long', $ai_Handle[0])
DllCall('kernel32.dll', 'int', 'CloseHandle', 'int', $ai_Handle[0])
Return $ai_Return[0]
EndFunc ;==>_ReduceMemory
Func SvrExit()
TCPShutdown()
$RS.Close
$Conn.Close
_FileWriteLog($LogFile, "服务停止")
EndFunc
测试使用的客户端程序,,目前TCP通讯没发现什么错误!
#Region ;**** 由 AccAu3Wrapper_GUI 创建指令 ****
#AccAu3Wrapper_OutFile_x64=客户机_x64.exe
#AccAu3Wrapper_Res_Language=2052
#AccAu3Wrapper_Res_requestedExecutionLevel=None
#EndRegion ;**** 由 AccAu3Wrapper_GUI 创建指令 ****
#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <StaticConstants.au3>
#include <EditConstants.au3>
Opt("GUIOnEventMode", 1) ;GUIi事件模式
; 设置一些常用信息
;--------------------------
Local $ConnectedSocket, $szData
Local $szIPADDRESS = "192.168.1.232"
Local $nPORT = 33891
$Form1 = GUICreate("客户端", 300, 200)
GUISetOnEvent($Form1, "_gui")
GUISetOnEvent($GUI_EVENT_CLOSE, "_gui")
$Label1 = GUICtrlCreateLabel("", 8, 8, 280, 20)
GUICtrlSetBkColor(-1, 0xcccccc)
$Label2 = GUICtrlCreateLabel("", 8, 32, 280, 20)
GUICtrlSetBkColor(-1, 0xcccccc)
$Input1 = GUICtrlCreateInput("", 8, 55, 220, 20)
$Button1 = GUICtrlCreateButton("发送", 232, 54, 56, 22)
GUICtrlSetOnEvent(-1, "_Gui")
$Edit1 = GUICtrlCreateEdit("", 8, 78, 280, 80, BitOR($ES_WANTRETURN, $WS_VSCROLL))
GUISetState(@SW_SHOW)
; 开始 TCP 服务
TCPStartup()
; 初始化
$ConnectedSocket = -1
Func _Gui()
Switch @GUI_CtrlId
Case $GUI_EVENT_CLOSE
Exit
Case $Button1
SendDataToServer(GUICtrlRead($Input1))
EndSwitch
EndFunc ;==>_Gui
While 1
_Recv()
Sleep(50)
WEnd
Func _Recv()
$recv = TCPRecv($ConnectedSocket, 1024 * 50, 1)
If @error Then
_Conn()
Return 0
Else
GUICtrlSetData($Label1, "Socket : " & $ConnectedSocket)
GUICtrlSetData($Label2, "服务器 : 在线")
GUICtrlSetColor($Label2, 0x00ff00)
If $recv <> "" Then;如果接收不为空
GUICtrlSetData($Edit1, BinaryToString($recv, 4) & @CRLF , "|")
EndIf
EndIf
EndFunc ;==>_Recv
Func _Conn()
GUICtrlSetData($Label1, "Socket : " & $ConnectedSocket)
GUICtrlSetData($Label2, "服务器 : 离线")
GUICtrlSetColor($Label2, 0xff0000)
$ConnectedSocket = TCPConnect($szIPADDRESS, $nPORT)
EndFunc ;==>_Conn
Func SendDataToServer($sData)
If $sData <> "" Then
$sMsg = StringToBinary($sData, 4)
TCPSend($ConnectedSocket, $sMsg)
EndIf
EndFunc ;==>SendDataToServer
|