困扰已久的网络通信(IOCP:完成端口),今天终于揭开她的神秘面纱了,之前百度N久还是未能理解IOCP,网络上好多博文都没有贴出源码,初学者很难正在理解IOCP并自己写出通信例子 ,经过努力,今天自己终于做出了简单的测试程序,下面贴出源码,水平有限,难免有错,希望不要误人子弟。
1、Svr主窗体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
unit Umain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, UIOCPSvr;
type
TForm1 = class (TForm)
Button1: TButton;
mmoRev: TMemo;
procedure Button1Click(Sender: TObject);
private
IOCPSvr: TIOCPSvr;
{ Private declarations }
public
{ Public declarations }
end ;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1 . Button1Click(Sender: TObject);
begin
IOCPSvr := TIOCPSvr . Create(Self);
IOCPSvr . Host := '192.168.1.86' ;
IOCPSvr . Port := 8988 ;
IOCPSvr . open;
end ;
end .
|
2、IOCP 服务端实现代码
1 unit UIOCPSvr; 2 3 interface 4 5 uses 6 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7 Dialogs, StdCtrls, JwaWinsock2; 8 9 const 10 DATA_BUFSIZE = 1024; 11 12 type 13 LPVOID = Pointer; 14 {* 完成端口操作定义 *} 15 TIocpOperate = (ioNone, ioCon, ioRead, ioWrite, ioStream, ioExit); 16 PIocpRecord = ^TIocpRecord; 17 TIocpRecord = record 18 Overlapped: TOverlapped; //完成端口重叠结构 19 WsaBuf: TWsaBuf; //完成端口的缓冲区定义 20 IocpOperate: TIOCPOperate; //当前操作类型 21 end; 22 23 type 24 TThreadRev = class(TThread) 25 private 26 pData: Pointer; 27 protected 28 procedure Execute; override; 29 public 30 constructor Create(CreateSuspended: Boolean; adata: Pointer); 31 destructor Destroy; override; 32 end; 33 34 35 TThreadCon = class(TThread) 36 private 37 PSocket: TSocket; 38 lvIOPort: THandle; 39 protected 40 procedure Execute; override; 41 public 42 constructor Create(CreateSuspended: Boolean; var aSocket: TSocket; var aIOport: THandle); 43 destructor Destroy; override; 44 end; 45 46 47 TIOCPSvr = class(TComponent) 48 private 49 FHost: string; 50 FPort: Integer; 51 ThreadCon: TThreadCon; 52 ThreadRev: TThreadRev; 53 protected 54 public 55 constructor Create(AOwner: TComponent); override; 56 destructor Destroy; override; 57 procedure open; 58 published 59 property Port: Integer read FPort write FPort; 60 property Host: string read FHost write FHost; 61 end; 62 63 64 procedure SendData(astr: string; FSocket: TSocket); //发生数据 65 function PIocpAllocate(ALen: Cardinal): PIocpRecord; //分配内存 66 procedure PIocpRelease(var AValue: PIocpRecord); //释放内存 67 68 implementation 69 70 uses Umain; 71 72 function PIocpAllocate(ALen: Cardinal): PIocpRecord; 73 begin 74 New(Result); 75 Result.Overlapped.Internal := 0; 76 Result.Overlapped.InternalHigh := 0; 77 Result.Overlapped.Offset := 0; 78 Result.Overlapped.OffsetHigh := 0; 79 Result.Overlapped.hEvent := 0; 80 Result.IocpOperate := ioNone; 81 Result.WsaBuf.buf := GetMemory(ALen); 82 Result.WsaBuf.len := ALen; 83 end; 84 85 86 procedure PIocpRelease(var AValue: PIocpRecord); 87 begin 88 FreeMemory(AValue.WsaBuf.buf); 89 AValue.WsaBuf.buf := nil; 90 Dispose(AValue); 91 end; 92 93 94 procedure SendData(astr: string; FSocket: TSocket); 95 var 96 IocpRec: PIocpRecord; 97 iErrCode: Integer; 98 dSend, dFlag: DWORD; 99 FOutputBuf: TMemoryStream; 100 begin 101 102 FOutputBuf := TMemoryStream.Create; 103 FOutputBuf.WriteBuffer(astr[1], Length(astr)); 104 105 New(IocpRec); 106 IocpRec.Overlapped.Internal := 0; 107 IocpRec.Overlapped.InternalHigh := 0; 108 IocpRec.Overlapped.Offset := 0; 109 IocpRec.Overlapped.OffsetHigh := 0; 110 IocpRec.Overlapped.hEvent := 0; 111 IocpRec.WsaBuf.buf := GetMemory(Length(astr)); 112 IocpRec.WsaBuf.len := Length(astr); 113 114 IocpRec.IocpOperate := ioWrite; 115 System.Move(PAnsiChar(FOutputBuf.Memory)[0], IocpRec.WsaBuf.buf^, FOutputBuf.Size); 116 dFlag := 0; 117 if WSASend(FSocket, @IocpRec.WsaBuf, 1, dSend, dFlag, @IocpRec.Overlapped, nil) = SOCKET_ERROR then 118 begin 119 iErrCode := WSAGetLastError; 120 if iErrCode <> ERROR_IO_PENDING then 121 begin 122 // FIocpServer.DoError('WSASend', GetLastWsaErrorStr); 123 //ProcessNetError(iErrCode); 124 end; 125 end; 126 FreeAndNil(FOutputBuf); 127 end; 128 129 130 { TIOCPSvr } 131 132 constructor TIOCPSvr.Create(AOwner: TComponent); 133 begin 134 inherited; 135 136 end; 137 138 destructor TIOCPSvr.Destroy; 139 begin 140 ThreadCon.Terminate; 141 if ThreadCon.Suspended then 142 ThreadCon.Resume; 143 144 FreeAndNil(ThreadCon); 145 inherited; 146 end; 147 148 procedure TIOCPSvr.open; 149 var 150 WSData: TWSAData; 151 lvIOPort: THandle; 152 lvAddr: TSockAddr; 153 sSocket: TSocket; 154 begin 155 156 //加载初始化SOCKET。使用的是2.2版为了后面方便加入心跳。 157 WSAStartup($0202, WSData); 158 159 // 创建一个完成端口(内核对象),新建一个IOCP 160 lvIOPort := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); 161 162 //创建一个工作线程,调试用 163 ThreadRev := TThreadRev.Create(False, Pointer(lvIOPort)); 164 165 //创建一个套接字,将此套接字和一个端口绑定并监听此端口。 166 sSocket := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); 167 if sSocket = SOCKET_ERROR then 168 begin 169 closesocket(sSocket); 170 WSACleanup(); 171 end; 172 lvAddr.sin_family := AF_INET; 173 lvAddr.sin_port := htons(Port); 174 lvAddr.sin_addr.s_addr := htonl(INADDR_ANY); 175 if bind(sSocket, @lvAddr, sizeof(lvAddr)) = SOCKET_ERROR then 176 begin 177 closesocket(sSocket); 178 end; 179 listen(sSocket, 20); 180 181 //连接线程,当有客户端请求建立连接在该现场中处理 182 ThreadCon := TThreadCon.Create(False, sSocket, lvIOPort); 183 184 //下面循环进行循环获取客户端的请求。这注释部分放到 ThreadCon线程中处理了 185 // while (TRUE) do 186 // begin 187 // //当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字cSocket。这个套接字就是和客户端通信的时候使用的套接字。 188 // cSocket := WSAAccept(sSocket, nil, nil, nil, 0); 189 // 190 // //判断cSocket套接字创建是否成功,如果不成功则退出。 191 // if (cSocket = SOCKET_ERROR) then 192 // begin 193 // closesocket(sSocket); 194 // exit; 195 // end; 196 // 197 // //将套接字、完成端口绑定在一起。 198 // lvPerIOPort := CreateIoCompletionPort(cSocket, lvIOPort, cSocket, 0); 199 // if (lvPerIOPort = 0) then 200 // begin 201 // Exit; 202 // end; 203 // 204 // //初始化数据包 205 // PerIoData := PIocpAllocate(DATA_BUFSIZE); 206 // PerIoData.IocpOperate := ioCon; 207 // //通知工作线程,有新的套接字连接<第三个参数> 208 // PostQueuedCompletionStatus(lvIOPort, 0, cSocket, POverlapped(PerIOData)); 209 // end; 210 211 end; 212 213 214 215 { TThreadCon } 216 217 constructor TThreadCon.Create(CreateSuspended: Boolean; var aSocket: TSocket; var aIOport: THandle); 218 begin 219 inherited create(CreateSuspended); 220 PSocket := aSocket; 221 lvIOPort := aIOport; 222 end; 223 224 destructor TThreadCon.Destroy; 225 begin 226 227 inherited; 228 end; 229 230 procedure TThreadCon.Execute; 231 var 232 cSocket: TSocket; 233 lvPerIOPort: Integer; 234 PerIoData: PIocpRecord; 235 begin 236 inherited; 237 while not Terminated do 238 begin 239 240 //当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字cSocket。这个套接字就是和客户端通信的时候使用的套接字。 241 cSocket := WSAAccept(PSocket, nil, nil, nil, 0); 242 243 //判断cSocket套接字创建是否成功,如果不成功则退出。 244 if (cSocket = SOCKET_ERROR) then 245 begin 246 closesocket(PSocket); 247 exit; 248 end; 249 250 //将套接字、完成端口绑定在一起。 251 lvPerIOPort := CreateIoCompletionPort(cSocket, lvIOPort, cSocket, 0); 252 if (lvPerIOPort = 0) then 253 begin 254 Exit; 255 end; 256 257 //初始化数据包 258 PerIoData := PIocpAllocate(DATA_BUFSIZE); 259 PerIoData.IocpOperate := ioCon; 260 //通知工作线程,有新的套接字连接<第三个参数> 261 PostQueuedCompletionStatus(lvIOPort, 0, cSocket, POverlapped(PerIOData)); 262 end; 263 264 end; 265 266 { TThreadRev } 267 268 constructor TThreadRev.Create(CreateSuspended: Boolean; adata: Pointer); 269 begin 270 inherited Create(CreateSuspended); 271 pData := adata; 272 end; 273 274 destructor TThreadRev.Destroy; 275 begin 276 277 inherited; 278 end; 279 280 procedure TThreadRev.Execute; 281 var 282 CompletionPort: THANDLE; 283 BytesTransferred: Cardinal; 284 PerIoData: PIocpRecord; 285 cSocket: TSocket; 286 Flags: Cardinal; 287 lvResultStatus: BOOL; 288 temp: string; 289 begin 290 inherited; 291 CompletionPort := THandle(pData); 292 293 //得到创建线程是传递过来的IOCP 294 while not Terminated do 295 begin 296 //工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止 297 lvResultStatus := GetQueuedCompletionStatus(CompletionPort, BytesTransferred, cSocket, POverlapped(PerIoData), INFINITE); 298 299 {//CompletionPort:新建IOCP CreateIoCompletionPort()函数返回的端口 // BytesTransferred 收到数据的长度 300 // cSocket 个人理解就是通信sock句柄 //PerIoData 数据结构 301 //INFINITE 超时时间,这里是一直等待的意思,GetQueuedCompletionStatus 可以参考百度百科} 302 303 if (lvResultStatus = False) then 304 begin 305 //当客户端连接断开或者客户端调用closesocket函数的时候,函数GetQueuedCompletionStatus会返回错误。如果我们加入心跳后,在这里就可以来判断套接字是否依然在连接。 306 if cSocket <> 0 then 307 begin 308 closesocket(cSocket); 309 end; 310 if PerIoData <> nil then 311 begin 312 PIocpRelease(PerIoData); 313 end; 314 continue; 315 end; 316 317 if PerIoData = nil then 318 begin 319 closesocket(cSocket); 320 Break; 321 end 322 else if (PerIoData <> nil) then 323 begin 324 325 if PerIoData.IocpOperate = ioCon then //连接请求 326 begin 327 328 PIocpRelease(PerIoData); 329 end 330 else if PerIoData.IocpOperate = ioRead then 331 begin 332 ////可以在这里处理数据…… 333 temp:= Copy(string(PerIoData.WsaBuf.buf),1,BytesTransferred); //获取接收到的数据 这里只处理了字符串 334 Form1.mmoRev.Lines.Add(format('收到客户端:%d 消息:%s',[cSocket,temp])); 335 // temp := 'hello world !' + #13#10; //indy TCP 需要#13#10 才能收到信息 336 SendData(temp, cSocket); //接受什么数据原样返回 337 PIocpRelease(PerIoData);//释放内存 338 end; 339 Flags := 0; 340 /////进入投递收取动作 341 PerIoData := PIocpAllocate(DATA_BUFSIZE); 342 PerIoData.IocpOperate := ioRead; 343 344 /////异步收取数据 345 WSARecv(cSocket, @PerIoData.WsaBuf, 1, PerIoData.WsaBuf.len, Flags, @PerIoData.Overlapped, nil); 346 if (WSAGetLastError() <> ERROR_IO_PENDING) then 347 begin 348 closesocket(cSocket); 349 if PerIoData <> nil then 350 begin 351 PIocpRelease(PerIoData); 352 end; 353 Continue; 354 end; 355 end; 356 end; 357 358 end; 359 360 end.
3、indy TCP 客户端
1 unit Unit1; 2 3 interface 4 5 uses 6 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7 Dialogs, IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection, 8 IdTCPClient, StdCtrls, Sockets; 9 10 type 11 TForm1 = class(TForm) 12 IdTCPClient1: TIdTCPClient; 13 btnCon: TButton; 14 mmo1: TMemo; 15 btnSend: TButton; 16 btnRev: TButton; 17 edtSend: TEdit; 18 edtHost: TEdit; 19 edtPort: TEdit; 20 procedure IdTCPClient1Connected(Sender: TObject); 21 procedure btnConClick(Sender: TObject); 22 procedure btnSendClick(Sender: TObject); 23 procedure btnRevClick(Sender: TObject); 24 private 25 { Private declarations } 26 public 27 { Public declarations } 28 end; 29 30 var 31 Form1: TForm1; 32 33 implementation 34 35 {$R *.dfm} 36 37 procedure TForm1.IdTCPClient1Connected(Sender: TObject); 38 begin 39 mmo1.Lines.Add('用户连接上'); 40 end; 41 42 procedure TForm1.btnConClick(Sender: TObject); 43 begin 44 45 IdTCPClient1.Host:=edtHost.Text; 46 IdTCPClient1.Port:=StrToInt(edtPort.Text) ; 47 IdTCPClient1.Connect(); 48 btnCon.Enabled:=False; 49 btnSend.Enabled:=True; 50 end; 51 52 procedure TForm1.btnSendClick(Sender: TObject); 53 begin 54 IdTCPClient1.WriteLn(edtSend.Text); 55 btnSend.Enabled:=False; 56 btnRev.Enabled:=True; 57 end; 58 59 procedure TForm1.btnRevClick(Sender: TObject); 60 begin 61 mmo1.Lines.Add( IdTCPClient1.ReadLn(#13#10,-1,-1)); 62 btnRev.Enabled:=False; 63 btnSend.Enabled:=True; 64 end; 65 66 end.
Q群 Delphi Home 235236282,欢迎delphi 爱好者加入,一起学习、进步。