socketsdelphiexceptionwindows-socket-api

10057 WSA Exception when SendBuf via Socket


Client:

//is called when the client tries to log in
    procedure TLogin_Form.btnLoginClick(Sender: TObject);
    var LoginQuery: TQuery;
    begin
        //If socket not open, open it
        if not LoginSocket.Active then
        begin
            LoginSocket.Open;
        end;

      //create package
      LoginQuery.Login := ledtName.Text;
      LoginQuery.Passwort := ledtPasswort.Text;
      LoginQuery.IP := LoginSocket.Socket.LocalAddress;
      //send package
      LoginSocket.Socket.SendBuf(LoginQuery, SizeOf(LoginQuery));
    end;

Server:

    //This procedure is executed when I click on start server button
    procedure TServer_Form.btnStartStopClick(Sender: TObject);
    begin
        //If not open, open it
        if not ServerSocket.Active then
        begin
          btnStartStop.Caption := 'stop server'; 
          //Open ServerSocket
          ServerSocket.Open;
        end
      else
      begin
        //If Socket open, close it, but watch for active connctions.
        if ServerSocket.Socket.ActiveConnections > 0 then
          begin
            ShowMessage('Clients still logged in');
          end
        else
          begin
            //If no clients connected, close socket
            ServerSocket.Close;
          end;
      end;
    end;

    //This procedure is called to verify weather the user is logged in and to send the verification back
    procedure UserCheckExist(Login, Passwort: string);
    var LoginReply: TReply;
    begin
        begin
          //Connect to DB
          DBConnect(true);
          DM.AQ_LOGIN.Close;
          DM.AQ_LOGIN.SQL.Clear;
          //Count of BLOGINs
          DM.AQ_LOGIN.SQL.Add('select count(BLOGIN) from BENU where BLOGIN = ''' + Login + ''' AND BPW = ''' + Passwort + '''');
          DM.AQ_LOGIN.Open;
          //LoginReply.Action tells the client then what to do with the LoginReply.Value
          LoginReply.Action := 0;
          //if user unique
          if DM.AQ_LOGIN.Fields[0].AsInteger = 1 then
            begin
              //LoginReply.Value = 1 means the client is allowed to log in
              LoginReply.Value := 1;
              //THIS RETURNS THE WSA 10057 EXCEPTION of user is unique
              Server_Form.ServerSocket.Socket.SendBuf(LoginReply, SizeOf(LoginReply));
            end
          else
            begin
                //LoginReply.Value = 0 means the client is NOT allowed to log in
                LoginReply.Value := 0;

                //THIS RETURNS THE WSA 10057 EXCEPTION if user is NOT unique
                Server_Form.ServerSocket.Socket.SendBuf(LoginReply, SizeOf(LoginReply));
            end;
          //Close ADOQuery
          DM.AQ_LOGIN.Close;
          //Close DB Connection
          DBConnect(false);
        end;
    end;

    //Is called when something is in the socket connection
    procedure TServer_Form.ServerSocketClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    var Query: TQuery;
    begin
        //Reads from the Socket (cant use ServerSocket.Socket.ReceiveBuf whysoever, but this is another thread)
        Socket.ReceiveBuf(Query, SizeOf(Query));
      case Query.Action of
        //If Query.Action = 0, which means the client tries to login call UserCheckExist
        0:  UserCheckExist(Query.Login, Query.Passwort);
      //Otherwise, getfuckedup
      else ShowMessage('Query Action not defined');
      end;
    end;

One strange thing is that I have to send the login + pw from the client two times.

The first time send (Client), I get onClientConnect and onAccept at the server. The second time i send (Client), the server executes the code until the line I marked . I get a 10057 WSA Exception.

Why do I get this error? Strange however is, that if I open the socket on the server right before the line where I get the Exception saying that 'socket not open', I get it anyways


Solution

  • The code you have shown will not work on both the client and server sides due to several bugs in your code.

    When TClientSocket is set to ctNonBlocking mode (which I am assuming you are using), Open() will not trigger the OnConnect event until after btnLoginClick() has exited and flow has returned to the message queue. It is not valid to read or write data from a socket until the OnConnect event has fired. So you should move your sending code into the OnConnect event itself. You also need to take into account that SendBuf() may not be able to send all of the data in a single packet. If SendBuf() returns -1 and WSAGetLastError() returns WSAEWOULDBLOCK afterwards (which will always be true if the OnError event was not triggered), then data was not sent in its entirety. You must buffer any unsent bytes somewhere, and then wait for the OnWrite event to fire before trying to write the buffered bytes again, or anything else for the matter, to the socket.

    As for your server code, you are trying to write outbound data to the wrong object. You must read and write data using the TCustomWinSocket object that the OnRead event provided. You are trying to write data to the server's TServerWinSocket object instead, which does not represent a valid socket endpoint for any connected client. You also need to look at the return value of ReceiveBuf() in order to handle partial transmissions as well.

    Try something more like the following:

    Common:

    type
      // helper class that holds buffered input/output data
      SocketBuffers = class
      public
        constructor Create;
        destructor Destroy;
        Inbound: TMemoryStream;
        Outbound: TMemoryStream;
      end;
    
    constructor SocketBuffers.Create;
    begin
      inherited;
      Inbound := TMemoryStream.Create;
      Outbound := TMemoryStream.Create;
    end;
    
    destructor SocketBuffers.Destroy;
    begin
      Inbound.Free;
      Outbound.Free;
      inherited;
    end;
    
    // removes processed bytes from a buffer
    procedure CompactBuffer(Buffer: TMemoryStream);
    begin
      if Buffer.Position > 0 then
      begin
        // bytes have been processed, remove them from the buffer...
        if Buffer.Position < Buffer.Size then
        begin
          // move unprocessed bytes to the front of the buffer...
          Move(Pointer(Longint(Buffer.Memory)+Buffer.Position)^, Buffer.Memory^, Buffer.Size - Buffer.Position);
          // reduce the buffer size just the remaining bytes...
          Buffer.Size := Buffer.Size - Buffer.Position;
        end else
        begin
          // all bytes have been processed, clear the buffer...
          Buffer.Clear;
        end;
      end;
    end;
    
    // sends raw bytes to the specified socket, buffering any unsent bytes
    function SendDataToSocket(Socket: TCustomWinSocket; Data: Pointer; DataSize: Integer; Buffer: TMemoryStream): Integer;
    var
      DataPtr: PByte;
      NumSent: Integer;
    begin
      Result := 0;
      DataPtr := PByte(Data);
      if DataSize > 0 then
      begin
        if Buffer.Size = 0 then
        begin
          // the buffer is empty, send as many bytes as possible...
          repeat
            NumSent := Socket.SendBuf(DataPtr^, DataSize);
            if NumSent <= 0 then Break; // error or disconnected
            Inc(DataPtr, NumSent);
            Dec(DataSize, NumSent);
            Inc(Result, NumSent);
          until DataSize = 0;
          if DataSize = 0 then Exit; // nothing left to send or buffer
        end;
        // add unsent bytes to the end of the buffer...
        Buffer.Seek(0, soFromEnd);
        Buffer.WriteBuf(DataPtr^, DataSize);
        Inc(Result, DataSize);
      end;
    end;
    
    // sends buffered bytes to the specified socket
    procedure SendBufferToSocket(Socket: TCustomWinSocket; Buffer: TMemoryStream);
    var
      DataPtr: PByte;
      NumSent: Integer;
    begin
      // start at the beginning of the buffer
      Buffer.Position := 0;
      DataPtr := PByte(Buffer.Memory);
      while Buffer.Position < Buffer.Size do
      begin
        NumSent := Socket.SendBuf(DataPtr^, Buffer.Size - Buffer.Position);
        if NumSent <= 0 then Break; // error or disconnected
        Inc(DataPtr, NumSent);
        Buffer.Seek(NumSent, soFromCurrent);
      end;
      // remove bytes that were sent...
      CompactBuffer(Buffer);
    end;
    
    // reads raw bytes from the specified socket ands buffers them
    procedure ReadBufferFromSocket(Socket: TCustomWinSocket; Buffer: TMemoryStream);
    var
      NumRecv: Integer;
      OldSize: Integer;
    begin
      repeat
        NumRecv := Socket.ReceiveLength;
        if NumRecv <= 0 then Exit; // error or no data available
    
        // increase the size of the buffer
        OldSize := Buffer.Size;
        Buffer.Size := Buffer.Size + NumRecv;
    
        // read bytes into the new memory space
        NumRecv := Socket.ReceiveBuf(Pointer(Longint(Buffer.Memory)+OldSize)^, NumRecv);
        if NumRecv <= 0 then
        begin
          // nothing read, free the unused memory
          Buffer.Size := OldSize;
          Exit;
        end;
      until False;
    end;
    

    Client:

    var
      Buffers: SocketBuffers = nil;
    
    procedure TLogin_Form.FormCreate(Sender: TObject);
    begin
      Buffers := SocketBuffers.Create;
    end;
    
    procedure TLogin_Form.FormDestroy(Sender: TObject);
    begin
      LoginSocket.Close;
      Buffers.Free;
    end;
    
    procedure TLogin_Form.btnLoginClick(Sender: TObject);
    begin
      if not LoginSocket.Active then
      begin
        Buffers.Inbound.Clear;
        Buffers.Outbound.Clear;
        LoginSocket.Open;
      end;
    end;
    
    procedure TLogin_Form.LoginSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
    var
      LoginQuery: TQuery;
    begin
      LoginQuery.Login := ledtName.Text;
      LoginQuery.Passwort := ledtPasswort.Text;
      LoginQuery.IP := LoginSocket.Socket.LocalAddress;
    
      // send query, buffering unsent bytes if needed...
      SendDataToSocket(Socket, @LoginQuery, SizeOf(LoginQuery), Buffers.Outbound);
    end;
    
    procedure TLogin_Form.LoginSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      Buffer: TmemoryStream;
      Available: Integer;
      Query: TQuery;
    begin
      Buffer := Buffers.Inbound;
    
      // read available bytes into the buffer...
      ReadBufferFromSocket(Socket, Buffer);
    
      // process complete queries, ignore unfinished queries until later...
      Buffer.Position := 0;
      repeat
        Available := Buffer.Size - Buffer.Position;
        if Available < SizeOf(Query) then Break;
        Buffer.ReadBuf(Query, SizeOf(Query));
        // process query as needed ...
      until False;
    
      // remove processed bytes from the buffer...
      CompactBuffer(Buffer);
    end;
    
    procedure TLogin_Form.LoginSocketWrite(Sender: TObject; Socket: TCustomWinSocket);
    begin
      // can send any buffered bytes now...
      SendBufferToSocket(Socket, Buffers.Outbound);
    end;
    

    Server:

    procedure TServer_Form.btnStartStopClick(Sender: TObject);
    begin
      if not ServerSocket.Active then
      begin
        btnStartStop.Caption := 'stop server'; 
        ServerSocket.Open;
      end
      else if ServerSocket.Socket.ActiveConnections > 0 then
      begin
        ShowMessage('Clients still logged in');
      end
      else
      begin
        ServerSocket.Close;
      end;
    end;
    
    procedure UserCheckExist(Socket: TCustomWinSocket; Login, Password: string);
    var
      LoginReply: TReply;
    begin
      ...
      LoginReply.Value := ...;
    
      // send query, buffering unsent bytes if needed...
      SendDataToSocket(Socket, @LoginReply, Sizeof(LoginReply), SocketBuffers(Socket.Data).Outbound);
      ...
    end;
    
    
    procedure TServer_Form.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket);
    begin
      Socket.Data := SocketBuffers.Create;
    end;
    
    procedure TServer_Form.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
    begin
      SocketBuffers(Socket.Data).Free;
      Socket.Data := nil;
    end;
    
    procedure TServer_Form.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      Buffer: TmemoryStream;
      Available: Integer;
      Query: TQuery;
    begin
      Buffer := SocketBuffers(Socket.Data).Inbound;
    
      // read available bytes into the buffer...
      ReadBufferFromSocket(Socket, Buffer);
    
      // process complete queries, ignore unfinished queries until later...
      Buffer.Position := 0;
      repeat
        Available := Buffer.Size - Buffer.Position;
        if Available < SizeOf(Query) then Break;
        Buffer.ReadBuf(Query, SizeOf(Query));
        // process query as needed ...
        case Query.Action of
          0: UserCheckExist(Socket, Query.Login, Query.Password);
          ...
        end;
      until False;
    
      // remove processed bytes from the buffer...
      CompactBuffer(Buffer);
    end;
    
    procedure TServer_Form.ServerSocketClientWrite(Sender: TObject; Socket: TCustomWinSocket);
    begin
      // can send any buffered bytes now...
      SendBufferToSocket(Socket, SocketBuffers(Socket.Data).Outbound);
    end;