2004. április 26., hétfő

Handling Winsock errors


Problem/Question/Abstract:

For any of the following exception handling methods to work, the VCL must in some way become aware that an error condition exists. If a call to the Winsock does not return, or does not provide information to the TCustomWinSocket descendant that called it, then there is no mechanism to handle the condition. OnError exception handler.

Answer:

One method for trapping exception conditions in a descendant of TCustomWinSocket is to use an OnError exception handler. This method will only handle a limited set of conditions because the mechanism provided by the Winsock for event notification only reacts to a limited list of conditions.  To be notified of an exception condition within the Winsock, TCustomWinSocket registers user message CM_SocketMessage to be sent to the component, and the CMSocketMessage message handler raises an exception.  The message is registered with the Winsock by an API call to WSASyncSelect. WSASyncSelect is a request for event notification of socket read, writes, connect, close, and accept events.  If the exception condition is not read, write, connect, close or accept, or if the CM_SocketMessage is not sent by the Winsock for any reason, the error handler will not fire.

Usage:

procedure TChatForm.ClientSocketError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
const
  ErrorEvents: array[eeGeneral..eeAccept] of string = (
    'eeGeneral',
    'eeSend',
    'eeReceive',
    'eeConnect',
    'eeDisconnect',
    'eeAccept'
    );
begin
  ListBox1.Items.Add('ClientSocketError.   TErrorEvent: ' +
    ErrorEvents[ErrorEvent] + '    ErrorCode: ' + IntToStr(ErrorCode));
  ErrorCode := 0; // don't raise an exception
end;

Definition:

procedure TCustomWinSocket.CMSocketMessage(var Message: TCMSocketMessage);

  function CheckError: Boolean;
  var
    ErrorEvent: TErrorEvent;
    ErrorCode: Integer;
  begin
    if Message.SelectError <> 0 then
    begin
      Result := False;
      ErrorCode := Message.SelectError;
      case Message.SelectEvent of
        FD_CONNECT: ErrorEvent := eeConnect;
        FD_CLOSE: ErrorEvent := eeDisconnect;
        FD_READ: ErrorEvent := eeReceive;
        FD_WRITE: ErrorEvent := eeSend;
        FD_ACCEPT: ErrorEvent := eeAccept;
      else
        ErrorEvent := eeGeneral;
      end;
      Error(Self, ErrorEvent, ErrorCode);
      if ErrorCode <> 0 then
        raise ESocketError.CreateFmt(sASyncSocketError, [ErrorCode]);
    end
    else
      Result := True;
  end;

begin
  with Message do
    if CheckError then
      case SelectEvent of
        FD_CONNECT: Connect(Socket);
        FD_CLOSE: Disconnect(Socket);
        FD_READ: Read(Socket);
        FD_WRITE: Write(Socket);
        FD_ACCEPT: Accept(Socket);
      end;
end;

Object Pascal Exception Handling
You can also wrap a specific call in a try..except block or setting an application level exception handler.  For this to
work, the component must in some way become aware of the exception condition and an exception must be raised for the exception to be trapped here.

Example of Application Exception Handler:

TChatForm = class(TForm)
  {.
  . }
public
  procedure AppException(Sender: TObject; E: Exception);
end;
{.
. }
implementation
{.
. }

procedure TChatForm.AppException(Sender: TObject; E: Exception);
begin
  ListBox1.Items.Add('AppException: ' + E.Message);
end;

procedure TChatForm.FormCreate(Sender: TObject);
begin
  Application.OnException := AppException;
end;

Example of Try..Except block:

with ClientSocket do
try
  Active := True;
except
  on E: Exception do
    ListBox1.Items.Add('Try..except during open: ' + E.Message);
end;
end;

SocketErrorProc
For calls that use the CheckSocketResult function to check the result returned by WSAGetLastError, errors can be handled in a programmer defined function by setting the SocketErrorProc.

Usage:

interface
{.
. }
procedure MySocketError(ErrorCode: Integer);

implementation
{.
. }

procedure MySocketError(ErrorCode: Integer);
begin
  ShowMessage('MySocketError: ' + IntToStr(ErrorCode));
end;

procedure TChatForm.FormCreate(Sender: TObject);
begin
  SocketErrorProc := MySocketError;
end;

Defined:

function CheckSocketResult(ResultCode: Integer; const Op: string):
  Integer;
begin
  if ResultCode <> 0 then
  begin
    Result := WSAGetLastError;
    if Result <> WSAEWOULDBLOCK then
      if Assigned(SocketErrorProc) then
        SocketErrorProc(Result)
      else
        raise ESocketError.CreateFmt(sWindowsSocketError,
          [SysErrorMessage(Result), Result, Op]);
  end
  else
    Result := 0;
end;


Help Text for SocketErrorProc:

Unit ScktComp

SocketErrorProc handles error messages that are received from a Windows socket connection.

threadvar
  SocketErrorProc: procedure(ErrorCode: Integer);

Assign a value to SocketErrorProc to handle error messages from Windows socket API calls before the socket component raises an exception. Setting SocketErrorProc prevents the socket component from raising an exception.  SocketErrorProc is a thread-local variable. It only handles errors that arise from the Windows socket API calls made within a single execution thread.

Nincsenek megjegyzések:

Megjegyzés küldése