zoukankan      html  css  js  c++  java
  • winsocket练习一 阻塞与select模型

    简单的封装了一下,有很多不完善的地方,很多细节未考虑进去。

    client代码

    unit ClientMainFormUnit;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Win.ScktComp, Winapi.Winsock,
      Vcl.StdCtrls;
    
    type
      TMyClientSocket = class
      private
        FActive: Boolean;
        FConnected: Boolean;
        FSocketHandle: TSocket;
        FSocketAddr: TSockAddr;
        FSocketRead: TNotifyEvent;
        FSocketWrite: TNotifyEvent;
        procedure SetSocketRead(const Value: TNotifyEvent);
        procedure SetSocketWrite(const Value: TNotifyEvent);
      public
        constructor Create;
        destructor Destroy; override;
        procedure Open(IP: string; Port: Word);
        procedure Close;
        function SendBuf(var Buf; BufSize: Integer): Integer;
        function ReceiveBuf(var Buf; BufSize: Integer): Integer;
        property Active: Boolean read FActive write FActive;
        property SockctRead: TNotifyEvent read FSocketRead write SetSocketRead;
        property SocketWrite: TNotifyEvent read FSocketWrite write SetSocketWrite;
      end;
    
      TForm2 = class(TForm)
        ClientSocket1: TClientSocket;
        Memo1: TMemo;
        Button1: TButton;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
      private
        FCSocket: TMyClientSocket;
        procedure ReadHandle(Sender: TObject);
        procedure WriteHandle(Sender: TObject);
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
    
    
    var
      Form2: TForm2;
      WSAData: TWsaData;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm2.Button1Click(Sender: TObject);
    begin
      FCSocket.Open('192.168.1.105', 6000);
    end;
    
    constructor TForm2.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      if WSAStartup($0101, WSAData) <> 0 then
        raise Exception.Create('WSAStartup Error');
      FCSocket := TMyClientSocket.Create;
      FCSocket.SockctRead := ReadHandle;
      FCSocket.SocketWrite := WriteHandle;
    end;
    
    destructor TForm2.Destroy;
    begin
      FCSocket.Free;
      WSACleanup;
      inherited;
    end;
    
    procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      FCSocket.Close;
    end;
    
    procedure TForm2.ReadHandle(Sender: TObject);
    var
      ret: Integer;
      buf: array[0..9] of AnsiChar;
    begin
      ret := FCSocket.ReceiveBuf(buf[0], 10);
      if ret > 0 then
        Memo1.Lines.Add(String(AnsiString(buf)));
      if ret = 0 then
        FCSocket.FActive := False;
    end;
    
    procedure TForm2.WriteHandle(Sender: TObject);
    var
      buf: TBytes;
    begin
      buf := BytesOf(DateTimeToStr(Now));
      FCSocket.SendBuf(buf, Length(buf));
    end;
    
    { TClientSkt }
    
    constructor TMyClientSocket.Create;
    begin
      inherited Create;
      FActive := False;
      FConnected := False;
      FSocketHandle := INVALID_SOCKET;
    end;
    
    destructor TMyClientSocket.Destroy;
    begin
      if FConnected then
        Close;
      inherited;
    end;
    
    procedure TMyClientSocket.Open(IP: string; Port: Word);
    var
      c, ErrorCode: Integer;
      FDSet: TFDSet;
      TimeVal: TTimeVal;
    begin
      FSocketAddr.sin_family := PF_INET;
      FSocketAddr.sin_port := htons(Port);
      FSocketAddr.sin_addr.S_addr := inet_addr(PAnsiChar(AnsiString(IP)));
      FSocketHandle := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      if FSocketHandle = INVALID_SOCKET then
        raise Exception.Create('Cannot Create Socket');
      if connect(FSocketHandle, FSocketAddr, SizeOf(FSocketAddr)) = SOCKET_ERROR then
        raise Exception.Create(SysErrorMessage(WSAGetLastError));
    
      FConnected := FSocketHandle <> INVALID_SOCKET;
    
      TimeVal.tv_sec := 0;
      TimeVal.tv_usec := 500;
      FActive := True;
      while FActive do
      begin
        FD_ZERO(FDSet);
        FD_SET(FSocketHandle, FDSet);
        c := select(0, @FDSet, nil, nil, @TimeVal);
        case c of
          SOCKET_ERROR:
            raise Exception.Create('Select Error');
          0:
            Break; //超时,一般是服务端关闭了 。
          else
            if FD_ISSET(FSocketHandle, FDSet) and Assigned(FSocketRead) then
              FSocketRead(Self);
        end;
    
        Application.ProcessMessages;
    
        FD_ZERO(FDSet);
        FD_SET(FSocketHandle, FDSet);
        c := select(0, nil, @FDSet, nil, @TimeVal);
        case c of
          SOCKET_ERROR:
            raise Exception.Create(SysErrorMessage(WSAGetLastError)+ ' code: '+WSAGetLastError.ToString);
          0:
            Break;
          else
            if FD_ISSET(FSocketHandle, FDSet) then
            begin
              if Assigned(FSocketWrite) then
                FSocketWrite(Self);
            end;
        end;
    
    {
      在《windows网络编程》一书中说,可以用 getsockopt, SO_ERROR 获取错误信息
      但 winsock 对 SO_ERROR 支持的可能并不一致。书中提到还可以用 recv , recvfrom
      确定是否失败,然后用 WSAGetLastError 获取错误信息。 但这种方法 也不是万无一失的。
    }
    
    //    FD_ZERO(FDSet);
    //    FD_SET(FSocketHandle, FDSet);
    //    c := select(0, nil, nil, @FDSet, @TimeVal);
    //    case c of
    //      SOCKET_ERROR:
    //        raise Exception.Create('Select Error');
    //      0:
    //        Break;
    //      else
    //        if FD_ISSET(FSocketHandle, FDSet) then
    //          raise Exception.Create('Error Message');
    //    end;
    
        Application.ProcessMessages;
      end;
    end;
    
    function TMyClientSocket.ReceiveBuf(var Buf; BufSize: Integer): Integer;
    begin
      Result := recv(FSocketHandle, buf, BufSize, 0);
    end;
    
    function TMyClientSocket.SendBuf(var Buf; BufSize: Integer): Integer;
    begin
      Result := send(FSocketHandle, buf, BufSize, 0);
    end;
    
    procedure TMyClientSocket.SetSocketRead(const Value: TNotifyEvent);
    begin
      FSocketRead := Value;
    end;
    
    procedure TMyClientSocket.SetSocketWrite(const Value: TNotifyEvent);
    begin
      FSocketWrite := Value;
    end;
    
    procedure TMyClientSocket.Close;
    begin
      FActive := False;
    
      if FConnected then
      begin
    //    shutdown(FSocketHandle, 2);
        if closesocket(FSocketHandle) = SOCKET_ERROR then
          raise Exception.Create(SysErrorMessage(WSAGetLastError));
        FConnected := False;
      end;
    end;
    
    
    end.
    View Code

    Server代码

    unit ServerMainFormUnit;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Win.ScktComp, Winapi.WinSock;
    
    type
      TMyServerSocket = class
      private
        FPort: Word;
        FActive: Boolean;
        FSocketHandle: TSocket;
        FSocketAddr: TSockAddr;
      public
        constructor Create;
        destructor Destroy; override;
        procedure Open;
        procedure Close;
        property Port: Word read FPort write FPort;
      end;
    
      TForm2 = class(TForm)
        ServerSocket1: TServerSocket;
        Memo1: TMemo;
        Button1: TButton;
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
      private
        FSSocket: TMyServerSocket;
      public
        { Public declarations }
      end;
    
    var
      Form2: TForm2;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm2.Button1Click(Sender: TObject);
    begin
      FSSocket.Open;
    end;
    
    procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      FSSocket.Close;
    end;
    
    procedure TForm2.FormCreate(Sender: TObject);
    var
      wsData: TWSAData;
    begin
      WSAStartup($0101, wsData);
      FSSocket := TMyServerSocket.Create;
      FSSocket.Port := 6000;
    end;
    
    
    { TClientSkt }
    
    constructor TMyServerSocket.Create;
    begin
      inherited Create;
      FActive := False;
      FSocketHandle := INVALID_SOCKET;
    end;
    
    destructor TMyServerSocket.Destroy;
    begin
      Close;
      inherited;
    end;
    
    procedure TMyServerSocket.Open;
    var
      c: Integer;
      FDSet: TFDSet;
      ClientAddr: TSockAddr;
      ClientAddrSize: Integer;
      ClientSocket: TSocket;
      TimeVal: TTimeVal;
      buf: TBytes;
    begin
      FSocketAddr.sin_family := PF_INET;
      FSocketAddr.sin_port := htons(Port);
      FSocketAddr.sin_addr.S_addr := INADDR_ANY;
      FSocketHandle := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      if FSocketHandle = INVALID_SOCKET then
        raise Exception.Create('Cannot Create Socket');
    
      bind(FSocketHandle, FSocketAddr, SizeOf(FSocketAddr));
      {
        listen(socket s, int backlog)
        backlog 参数指定搁置连接的最大数, 如果为2, 那么当有三个连接请求时,
        前两个连接请求会被 ”挂起 " ,第三个连接请求会失败,返回 WSAECONNREFUSED 错误。
      }
      FActive := listen(FSocketHandle, 2) = 0;
    
      if not FActive then
        raise Exception.Create(SysErrorMessage(WSAGetLastError));
    
      TimeVal.tv_sec := 0;
      TimeVal.tv_usec := 100;
      FActive := True;
      while FActive do
      begin
        FD_ZERO(FDSet);
        FD_SET(FSocketHandle, FDSet);
        c := select(0, @FDSet, nil, nil, @TimeVal);
        case c of
          SOCKET_ERROR:
            ;
          0:
            Application.ProcessMessages;
          else
            if FD_ISSET(FSocketHandle, FDSet) then
            begin
              ClientSocket := accept(FSocketHandle, @ClientAddr, @ClientAddrSize);
              if (ClientSocket <> INVALID_SOCKET) then
              begin
                buf := BytesOf('hello');
                send(ClientSocket, buf[0], Length(buf), 0);
                Application.ProcessMessages;
                shutdown(ClientSocket, 1);
                closesocket(ClientSocket);
              end;
            end;
        end;
    
        Application.ProcessMessages;
      end;
    end;
    
    procedure TMyServerSocket.Close;
    begin
      if FActive then
      begin
        shutdown(FSocketHandle, 1);
        closesocket(FSocketHandle);
        FActive := False;
      end;
    end;
    
    procedure TForm2.FormDestroy(Sender: TObject);
    begin
      FSSocket.Close;
      FSSocket.Free;
      WSACleanup;
    end;
    
    end.
    View Code

    如何优雅的关闭socket,还需要很多测试。

    目前看,send和recv 后立即断开连接时,在closesocket前,需要立即 shutdown 或 setsockopt  SO_LINGER 相对稳定。

    此外,send 或 recv 后返回 0,基本上表示socket 被断开了。 

  • 相关阅读:
    王建军_百度百科
    腾讯研究院关于研究院
    创业公司3Gear Systems利用Kinect打造未来人机交互体验 | 36氪
    KVM切换器_互动百科
    保荐人考试
    Engadget 中文版征人启事 《 they're hiring
    吸血僵尸惊情四百年
    小霸王手机
    ARM、高通、德州仪器这三家芯片企业该怎么区分和评价?
    《美丽心灵》兼谈纳什均衡理论
  • 原文地址:https://www.cnblogs.com/Jiaojiawang/p/14315353.html
Copyright © 2011-2022 走看看