简单的封装了一下,有很多不完善的地方,很多细节未考虑进去。
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.
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.
如何优雅的关闭socket,还需要很多测试。
目前看,send和recv 后立即断开连接时,在closesocket前,需要立即 shutdown 或 setsockopt SO_LINGER 相对稳定。
此外,send 或 recv 后返回 0,基本上表示socket 被断开了。