A
asksomeone
Unregistered / Unconfirmed
GUEST, unregistred user!
unit Mains;<br><br>interface<br><br>uses Windows, WinSock2, WinSock, Sysutils;<br><br>const<br>PORT = 5150;<br>DATA_BUFSIZE = 8192;<br><br><br>type<br> LPVOID = Pointer; //定义一个指针<br> LPPER_IO_OPERATION_DATA = ^ PER_IO_OPERATION_DATA ;//一个指向数据集地址的指针吧?<br> PER_IO_OPERATION_DATA = packed record //开始定义数据集<br> Overlapped: OVERLAPPED; //重叠操作吧??<br> DataBuf: TWSABUF; //和上面的那个重叠,是固定的结构类型<br> Buffer: array [0..DATA_BUFSIZE] of CHAR; //定义一个字符数组 是用来保存接受数据的缓存<br> BytesSEND: DWORD; // 用来标志发送数据的长度。<br> BytesRECV: DWORD; // 用来标志接收数据的长度<br> end;<br> {上面的结构中Overlapped: OVERLAPPED;和DataBuf: TWSABUF;是固定的结构类型。<br> Buffer: array [0..1024] of CHAR;是用来保存接受数据的缓存。<br> BytesSEND: DWORD;用来标志发送数据的长度。<br> BytesRECV: DWORD;用来标志接受数据的长度。<br> 因为完成端口的工作者线程可以接受到来自客户端的数据,<br> 同时还可以接受到自己发送给客户端的数据,所以我们使用BytesSEND,<br> BytesRECV变量来说是用来区分这次的数据是来自客户端的数据还是自己发送出去的数据。}<br><br> {单句柄数据结构}<br> LPPER_HANDLE_DATA = ^ PER_HANDLE_DATA;<br> PER_HANDLE_DATA = packed record<br> Socket: TSocket;<br> end;<br> <br> procedure main;<br><br>implementation<br><br>function ServerWorkerThread(CompletionPortID: LPVOID): DWORD; stdcall; forward;<br><br>procedure printf(Fmt: string; num: Integer);<br>begin<br> WriteLn(Format(Fmt, [num]));<br>end;<br><br>procedure main;<br>var<br> InternetAddr: SOCKADDR_IN;<br> Listen: TSOCKET;<br> Accept: TSOCKET;<br> CompletionPort: THANDLE ;<br> SystemInfo: SYSTEM_INFO ;<br> PerHandleData: LPPER_HANDLE_DATA ;<br> PerIoData: LPPER_IO_OPERATION_DATA ;<br> i: Integer;<br> RecvBytes: DWORD;<br> Flags: DWORD;<br> ThreadID: DWORD ;<br> wsaData: TWSADATA ;<br> Ret: DWORD ;<br><br> ThreadHandle: THANDLE;<br>begin<br> Ret := WSAStartup($0202, wsaData);<br> {应用程序或DLL只能在一次成功的WSAStartup()<br> 调用之后才能调用进一步的Windows Sockets API函数<br> WSAStartup()的另一个作用是获得Windows Sockets API的版本号。<br> $0202应该是表明现在用的是版本2.2的winsock<br> http://www.delphibbs.com/keylife/iblog_show.asp?xid=16839}<br> if (Ret <> 0) then<br> begin<br> printf('WSAStartup failed with error %d', Ret);<br> Exit;<br> end;<br><br> // Setup an I/O completion port.<br> {创建一个完成端口 CreateIoCompletionPort(FileHandle, ExistingCompletionPort: THandle;<br> CompletionKey, NumberOfConcurrentThreads: DWORD): THandle;}<br> CompletionPort := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);<br> {第一个是windows常量吧INVALID_HANDLE_VALUE}<br> if (CompletionPort = 0) then<br> begin<br> printf( 'CreateIoCompletionPort failed with error: %d', GetLastError());<br> Exit;<br> end;<br> {如果得到的句柄是0,那就表示没有创建成功!返回错误}<br><br> // Determine how many processors are on the system.<br> {获取当前系统里有几颗处理器}<br> GetSystemInfo(SystemInfo);<br><br> // Create worker threads based on the number of processors available on the<br> // system. Create two worker threads for each processor.<br>{ 根据CPU的数量创建CPU*2数量的工作者线程。}<br> for i:= 0 to SystemInfo.dwNumberOfProcessors * 2 - 1 do<br> begin<br><br> // Create a server worker thread and pass the completion port to the thread.<br> {建立一个服务器工作线程并传递完成端口号给线程,当然要捕捉错误}<br> ThreadHandle := CreateThread(nil, 0, @ServerWorkerThread, Pointer(CompletionPort),<br> 0, ThreadID);<br> {function CreateThread(lpThreadAttributes: Pointer;<br> dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine;<br> lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall;}<br> if (ThreadHandle = 0) then<br> begin<br> printf('CreateThread() failed with error %d', GetLastError());<br> Exit;<br> end;<br><br> // Close the thread handle<br> {关闭线程句柄}<br> CloseHandle(ThreadHandle);<br> end;<br><br> // Create a listening socket<br> {function WSASocket( af: u_int; atype: u_int; protocol: u_int;<br> lpProtocolInfo: PWSAPROTOCOL_INFOA; g: TGroup; dwFlags: Dword)<br> : TSocket; stdcall;}<br> Listen := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED);<br> {创建一个与指定传送服务提供者捆绑的套接口,可选地创建和/或加入一个套接口组。<br> af:地址族描述。目前仅支持PF_INET格式,亦即ARPA Internet地址格式。<br> type:新套接口的类型描述。<br> protocol:套接口使用的特定协议,如果调用者不愿指定协议则定为0。<br> lpProtocolInfo:一个指向PROTOCOL_INFO结构的指针,<br> 该结构定义所创建套接口的特性。如果本参数非零,<br> 则前三个参数(af, type,protocol)被忽略。<br> g:套接口组的描述字。<br> iFlags:套接口属性描述。<br><br> 返回值:<br> 若无错误发生,WSASocket()返回新套接口的描述字。<br> 否则的话,返回 INVALID_SOCKET,应用程序可定调<br> 用WSAGetLastError()来获取相应的错误代码。<br><br> 错误代码:<br> WSANOTINITIALISED 在调用本API之前应成功调用WSAStartup()。<br> WSAENETDOWN 网络子系统失效。<br> WSAEAFNOSUPPORT 不支持指定的地址族。<br> WSAEINPROGRESS 一个阻塞的WinSock调用正在进行中,或者服务提供者仍在处理一个回调函数<br> WSAEMFILE 无可用的套接口描述字。<br> WSAENOBUFS 无可用的缓冲区空间。套接口无法创建。<br> WSAEPROTONOSUPPORT 不支持指定的协议。<br> WSAEPROTOTYPE 指定的协议对于本套接口类型错误。<br> WSAESOCKTNOSUPPORT 本地址族不支持指定的套接口类型。<br> WSAEINVAL g参数非法。<br> }<br> if (Listen = INVALID_SOCKET) then<br> begin<br> printf('WSASocket() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br><br> InternetAddr.sin_family := AF_INET; {SOCKET.AF_INET:0...127}<br> InternetAddr.sin_addr.s_addr := htonl(INADDR_ANY);{ SOCKET.htonl():integer;}<br> InternetAddr.sin_port := htons(PORT);<br><br> {sin _family为网络地址类型,必须设定为AF_INET。<br> sin_port为服务端口,注意不要使用已固定的服务端口,如HTTP的端口80等。<br> 如果端口设置为0,则系统会自动分配一个唯一端口。<br> sin_addr为一个unsigned long的IP地址。<br> sin_zero为填充字段,纯粹用来保证结构的大小。<br> ◆ 将常用的用点分开的IP地址转换为unsigned long类型的IP地址的函数:<br> unsigned long inet_addr(const char FAR * cp <br> 用法:<br> unsigned long addr=inet_addr("192.1.8.84"<br><br> ◆ 如果将sin_addr设置为INADDR_ANY,则表示所有的IP地址,也即所有的计算机。<br> #define INADDR_ANY (u_long)0x00000000<br><br> }<br> {绑定端口}<br> if (bind(Listen, InternetAddr, sizeof(InternetAddr)) = SOCKET_ERROR) then<br> begin<br> printf('bind() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br> // Prepare socket for listening<br><br><br> {第二个表示可接受的最大等待数<br> int PASCAL FAR listen( SOCKET s, int backlog);<br><br> S:用于标识一个已捆绑未连接套接口的描述字。<br> backlog:等待连接队列的最大长度。}<br> if (Winsock.listen(Listen, 5) = SOCKET_ERROR) then<br> begin<br> printf('listen() failed with error %d', WSAGetLastError());<br> exit;<br> end<br> else<br> begin<br> printf('Server listen on port = %d ...', PORT);<br> end;<br><br> {创建一个套接字,将此套接字和一个端口绑定并监听此端口。}<br> // Accept connections and assign to the completion port.<br> while(TRUE) do<br> begin<br> {当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字Acceptsc。<br> 这个套接字就是和客户端通信的时候使用的套接字。}<br> Accept := WSAAccept(Listen, nil, nil, nil, 0);<br>{ 根据条件函数的返回值有条件地接受连接,<br> 同时(可选地)创建和/或加入一个套接口组。<br><br> SOCKET WSAAPI WSAAccept ( SOCKET s, struct<br> sockaddr FAR * addr, int FAR * addrlen,<br> LPCONDITIONPROC lpfnCondition, DWORD<br> dwCallbackData <br><br> s:标识一个套接口的描述字,该套接口在listen()后监听连接。<br> addr:(可选)指针,指向存放通讯层所知的连接实体地址的缓冲区。<br> addr参数的具体格式由套接口创建时产生的地址族决定。<br> addrlen:(可选)指针,指向存放addr地址长度的整形数。<br> lpfnCondition:(可选的)用户提供的条件函数的进程实例地址。<br> 该函数根据参数传入的调用者信息作出接受或拒绝的决定,<br> 并通过给结果参数赋予特定的值来(可选地)创建和/或加<br> 入一个套接口组。<br> dwCallbackData:作为条件函数参数返回给应用程序的回调数据。<br> WinSock不分析该参数。<br><br> 返回值:<br> 若无错误发生,WSAAccept()函数返回所接受套接口的描述字。<br> 否则的话,将返回INVALID_SOCKET错误,应用程序可通过<br> WSAGetLastError()来获取相应的错误代码。<br> addrlen参数引用的整形数初始时包含了addr参数所指向的空间数,<br> 在调用返回时包含了返回地址的实际长度。<br><br> 错误代码:<br> WSANOTINITIALISED 在调用本API之前应成功调用WSAStartup()。<br> WSAECONNREFUSED 根据条件函数的返回值(CF_REJECT)强制拒绝连接请求。<br> WSAENETDOWN 网络子系统失效。<br> WSAEFAULT addrlen参数太小(小于sockaddr结构的大小),或者lpfnCondition并不是用户空间的一部分。<br> WSAEINTR 通过WSACancelBlockingCall()函数取消(阻塞)调用。<br> WSAEINPROGRESS 一个阻塞WinSock调用正在进行。<br> WSAEINVAL WSAAccept()调用前未执行listen()调用;条件函数中的g参数非法;条件函数的返回值非法;套接口处于非法状态。<br> WSAEMFILE WSAAccept()调用时排队队列非空,且无可用套接口描述字。<br> WSAENOBUFS 无可用缓冲区空间。<br> WSAENOTSOCK 描述字不是一个套接口。<br> WSAEOPNOTSUPP 所引用的套接口不是支持面向连接服务类型的。<br> WSATRY_AGAIN 根据条件函数的返回值(CF_DEFER) ,连接请求被推迟。<br> WSAEWOULDBLOCK 套接口标志为非阻塞,无连接请求供接受。<br> WSAEACCES 被推迟的连接请求超时或撤销。<br>}<br> if (Accept = SOCKET_ERROR) then<br> begin<br> printf('WSAAccept() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br> {判断Acceptsc套接字创建是否成功,如果不成功则退出。}<br> // Create a socket information structure to associate with the socket<br> {创建一个“单句柄数据结构”将Acceptsc套接字绑定。}<br> PerHandleData := LPPER_HANDLE_DATA (GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)));<br> if (PerHandleData = nil) then<br> begin<br> printf('GlobalAlloc() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br> // Associate the accepted socket with the original completion port.<br> <br> printf('Socket number %d connected', Accept);<br> PerHandleData.Socket := Accept;<br> {将套接字、完成端口和“单句柄数据结构”三者绑定在一起。}<br> if (CreateIoCompletionPort(Accept, CompletionPort, DWORD(PerHandleData), 0) = 0) then<br> begin<br> printf('CreateIoCompletionPort() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br> // Create per I/O socket information structure to associate with the <br> // WSARecv call below.<br> {将套接字、完成端口和“单句柄数据结构”三者绑定在一起。}<br> PerIoData := LPPER_IO_OPERATION_DATA(GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA)));<br> if (PerIoData = nil) then<br> begin<br> printf('GlobalAlloc() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br> {创建一个“单IO数据结构”其中将PerIoData.BytesSEND 和PerIoData.BytesRECV 均设置成0。<br> 说明此“单IO数据结构”是用来接受的。}<br> ZeroMemory( @PerIoData.Overlapped, sizeof(OVERLAPPED));<br> PerIoData.BytesSEND := 0;<br> PerIoData.BytesRECV := 0;<br> PerIoData.DataBuf.len := DATA_BUFSIZE;<br> PerIoData.DataBuf.buf := @PerIoData.Buffer;<br><br><br> {<br> 在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,<br> 因为要用刀重叠结构嘛,它是这样定义的:<br><br> int WSARecv(<br> SOCKET s, // 当然是投递这个操作的套接字<br> LPWSABUF lpBuffers, // 接收缓冲区,与Recv函数不同<br><br> // 这里需要一个由WSABUF结构构成的数组<br> DWORD dwBufferCount, // 数组中WSABUF结构的数量<br> LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,<br> 这里会返回函数调用所接收到的字节数<br> LPDWORD lpFlags, // 说来话长了,我们这里设置为0 即可<br> LPWSAOVERLAPPED lpOverlapped, // “绑定”的重叠结构<br> LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine<br> // 完成例程中将会用到的参数,我们这里设置为 NULL<br> <br><br>返回值:<br> WSA_IO_PENDING : 最常见的返回值,这是说明我们的WSARecv操作成功了,<br> 但是I/O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成<br> }<br> Flags := 0;<br> if (WSARecv(Accept, @(PerIoData.DataBuf), 1, @RecvBytes, @Flags,<br> @(PerIoData.Overlapped), nil) = SOCKET_ERROR) then<br> begin<br> if (WSAGetLastError() <> ERROR_IO_PENDING) then<br> begin<br> printf('WSARecv() failed with error %d', WSAGetLastError());<br> exit;<br> end<br> end; <br><br> end;<br>end;<br> {上面参照http://blog.csdn.net/jedli/archive/2007/06/29/1671969.aspx}<br><br><br>{下面的参照}<br>function ServerWorkerThread(CompletionPortID: LPVOID): DWORD; stdcall;<br>var<br> CompletionPort: THANDLE;<br> BytesTransferred: DWORD ;<br>// Overlapped: POVERLAPPED;<br> PerHandleData: LPPER_HANDLE_DATA ;<br> PerIoData: LPPER_IO_OPERATION_DATA ;<br> SendBytes, RecvBytes: DWORD;<br> Flags: DWORD ;<br>begin<br> CompletionPort := THANDLE( CompletionPortID);<br> {得到创建线程是传递过来的IOCP}<br> Result:= 0;<br><br> while(TRUE) do<br> begin<br> {工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止}<br> if (GetQueuedCompletionStatus(CompletionPort, BytesTransferred,<br> DWORD(PerHandleData), POverlapped(PerIoData), INFINITE) = False) then<br> begin<br> printf('GetQueuedCompletionStatus failed with error %d', GetLastError());<br> exit;<br> end;<br> {当客户端连接断开或者客户端调用closesocket函数的时候,<br> 函数GetQueuedCompletionStatus会返回错误。<br> 如果我们加入心跳后,在这里就可以来判断套接字是否依然在连接。}<br> // First check to see if an error has occured on the socket and if so<br> // then close the socket and cleanup the SOCKET_INFORMATION structure<br> // associated with the socket.<br> {当客户端连接断开或者客户端调用closesocket函数的时候,<br> 函数GetQueuedCompletionStatus会返回错误。如果我们加入心跳后,<br> 在这里就可以来判断套接字是否依然在连接。}<br><br> <br> if (BytesTransferred = 0) then<br> begin<br> {当客户端调用shutdown函数来从容断开的时候,<br> 我们可以在这里进行处理。}<br> printf('Closing socket %d/', PerHandleData.Socket);<br><br> if (closesocket(PerHandleData.Socket) = SOCKET_ERROR) then<br> begin<br> printf('closesocket() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br><br> GlobalFree(DWORD(PerHandleData));<br> GlobalFree(DWORD(PerIoData));<br> continue;<br> end;<br><br> // Check to see if the BytesRECV field equals zero. If this is so, then<br> // this means a WSARecv call just completed so update the BytesRECV field<br> // with the BytesTransferred value from the completed WSARecv() call.<br> {我们来判断数据的来自方向。因为我们发送出去数据的时候我们设置了<br> 结构成员BytesSEND。所以如果BytesRECV=0同时BytesSEND=0<br> 那么此数据就是我们接受到的客户端数据。<br> (这种区分方法不是唯一的,个人可以有自己的定义方法。<br> 只要可以区分开数据来源就可以。}<br> if (PerIoData.BytesRECV = 0) then<br> begin<br> PerIoData.BytesRECV := BytesTransferred;<br> PerIoData.BytesSEND := 0;<br> end<br> else<br> begin<br> PerIoData.BytesSEND := PerIoData.BytesSEND + BytesTransferred;<br> end;<br> {当是接受来自客户端的数据是,我们进行数据的处理。}<br> if (PerIoData.BytesRECV > PerIoData.BytesSEND) then<br> begin<br><br> // Post another WSASend() request.<br> // Since WSASend() is not gauranteed to send all of the bytes requested,<br> // continue posting WSASend() calls until all received bytes are sent.<br><br> ZeroMemory(@(PerIoData.Overlapped), sizeof(OVERLAPPED));<br> {这时变量PerIoData.Buffer就是接受到的客户端数据。<br> 数据的长度是PerIoData.DataBuf.len 你可以对数据进行相关的处理了}<br> PerIoData.DataBuf.buf := PerIoData.Buffer + PerIoData.BytesSEND;<br> PerIoData.DataBuf.len := PerIoData.BytesRECV - PerIoData.BytesSEND;<br><br> if (WSASend(PerHandleData.Socket, @(PerIoData.DataBuf), 1, @SendBytes, 0,<br> @(PerIoData.Overlapped), nil) = SOCKET_ERROR) then<br> begin<br> if (WSAGetLastError() <> ERROR_IO_PENDING) then<br> begin<br> printf('WSASend() failed with error %d', WSAGetLastError());<br> Exit;<br> end;<br> end;<br> end<br> else<br> begin<br> PerIoData.BytesRECV := 0;<br><br> // Now that there are no more bytes to send post another WSARecv() request.<br><br> Flags := 0;<br> ZeroMemory(@(PerIoData.Overlapped), sizeof(OVERLAPPED));<br><br> PerIoData.DataBuf.len := DATA_BUFSIZE;<br> PerIoData.DataBuf.buf := @PerIoData.Buffer;<br><br> if (WSARecv(PerHandleData.Socket, @(PerIoData.DataBuf), 1, @RecvBytes, @Flags,<br> @(PerIoData.Overlapped), nil) = SOCKET_ERROR) then<br> begin<br> if (WSAGetLastError() <> ERROR_IO_PENDING) then<br> begin<br> printf('WSARecv() failed with error %d', WSAGetLastError());<br> exit;<br> end;<br> end;<br> end;<br> end;<br>end;<br><br><br>end.<br>小弟不是学网络编程的,所以对socket函数也了解不深,如果问的问题让大家见笑了,笑过之后请明示一下改怎么做,谢谢<br>1,我想知道,这是一个单句柄的完成端口,是什么意思,是不是实际应用当中不是这样子的<br>2,我想知道,它的客户端怎么写<br>3,我想知道,完成端口怎么和数据库通讯,进行增删改查的工作<br>希望大家畅所欲言,小弟无以为报,就以300分,献给教我学习的大富翁们~