搜档网
当前位置:搜档网 › IOCP 原理 代码

IOCP 原理 代码

IOCP 原理 代码
IOCP 原理 代码

Windows I/O完成端口

2009-10-30 10:51

WINDOWS完成端口编程

1、基本概念

2、WINDOWS完成端口的特点

3、完成端口(Completion Ports )相关数据结构和创建

4、完成端口线程的工作原理

5、Windows完成端口的实例代码

WINDOWS完成端口编程

摘要:开发网络程序从来都不是一件容易的事情,尽管只需要遵守很少的一些规则:创建socket,发起连接,接受连接,发送和接收数据,等等。真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windows完成端口进行重叠I/O的技术,可以很方便地在Windows平台上开发出支持大量连接的网络服务程序。本文介绍在Windows平台上使用完成端口模型开发的基本原理,同时给出实际的例子。本文主要关注C/S结构的服务器端程序,因为一般来说,开发一个大容量、具有可扩展性的winsock程序就是指服务程序。

1、基本概念

设备---指windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile 等,所以我们不能看到**File函数就只想到文件设备。

与设备通信有两种方式,同步方式和异步方式:同步方式下,当调用ReadFile这类函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。

重叠I/O----顾名思义,就是当你调用了某个函数(比如ReadFile)就立刻返回接着做自己的其他动作的时候,系统同时也在对I/0设备进行你所请求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是在异步方式下使用I/O设备的。重叠I/O需要使用的一个非常重要的数据结构:OVERLAPPED。

2、WINDOWS完成端口的特点

Win32重叠I/O(Overlapped I/O)机制允许发起一个操作,并在操作完成之后接收信息。对于那种需要很长时间才能完成的操作来说,重叠IO机制尤其有用,因为发起重叠操作的线程在重叠请求发出后就可以自由地做别的事情了。在WinNT和Win2000上,提供的真正可扩展的I/O模型就是使用完成端口(Completion Port)的重叠I/O。完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非得使用完成端口不可,同样设备内核对象、事件对象、告警I/0等也可使用。但是完成端口内部提

供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活地决定线程个数,而且可以减少线程调度的次数从而提高性能。其实类似于WSAAsyncSelect和select函数的机制更容易兼容Unix,但是难以实现我们想要的“扩展性”。而且windows完成端口机制在操作系统的内部已经作了优化,从而具备了更高的效率。所以,我们选择完成端口开始我们的服务器程序开发。 1)发起操作不一定完成:系统会在完成的时候通知你,通过用户在完成端口上的等待,处理操作的结果。所以要有检查完成端口和取操作结果的线程。在完成端口上守候的线程系统有优化,除非在执行的线程发生阻塞,不会有新的线程被激活,以此来减少线程切换造成的性能代价。所以如果程序中没有太多的阻塞操作,就没有必要启动太多的线程,使用CPU数量的两倍,一般这么多线程就够了。

2)操作与相关数据的绑定方式:在提交数据的时候用户对数据打上相应的标记,记录操作的类型,在用户处理操作结果的时候,通过检查自己打的标记和系统的操作结果进行相应的处理。

3)操作返回的方式:一般操作完成后要通知程序进行后续处理。但写操作可以不通知用户,此时如果用户写操作不能马上完成,写操作的相关数据会被暂存到非交换缓冲区中,在操作完成的时候,系统会自动释放缓冲区,此时发起完写操作,使用的内存就可以释放了。但如果占用非交换缓冲太多会使系统停止响应。

3、完成端口(Completion Ports )相关数据结构和创建

其实可以把完成端口看成系统维护的一个队列,操作系统把重叠IO操作完成的事件通知放到该队列里,由于是暴露“操作完成”的事件通知,所以命名为“完成端口”(Completion Ports)。一个socket被创建后,就可以在任何时刻和一个完成端口联系起来。

OVERLAPPED数据结构

typedef struct _OVERLAPPED {

ULONG_PTR Internal; //被系统内部赋值,用来表示系统状态

ULONG_PTR InternalHigh; //被系统内部赋值,表示传输的字节数

union {

struct {

DWORD Offset; //与OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始操作

DWORD OffsetHigh; //如果不是对文件I/O来操作,则Offset必须设定为0

};

PVOID Pointer;

};

HANDLE hEvent; //如果不使用,就务必设为0;否则请赋一个有效的Event 句柄

} OVERLAPPED, *LPOVERLAPPED;

下面是异步方式使用ReadFile的一个例子

OVERLAPPED Overlapped;

Overlapped.Offset=345;

Overlapped.OffsetHigh=0;

Overlapped.hEvent=0;

//假定其他参数都已经被初始化

ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped); 这样就完成了异步方式读文件的操作,然后ReadFile函数返回,由操作系统做自己的事情。

下面介绍几个与OVERLAPPED结构相关的函数。

等待重叠I/0操作完成的函数

BOOL GetOverlappedResult (

HANDLE hFile,

LPOVERLAPPED lpOverlapped, //接受返回的重叠I/0结构

LPDWORD lpcbTransfer, //成功传输了多少字节数

BOOL fWait //TRUE只有当操作完成才返回,FALSE直接返回,如果操作没有完成,

//通过用GetLastError( )函数会返回

ERROR_IO_INCOMPLETE

);

而宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成,该宏对OVERLAPPED结构的Internal成员进行了测试,查看是否等于

STATUS_PENDING值。

一般来说,一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。工作线程的数量依赖于程序的具体需要。但是在理想的情况下,应该对应一个CPU 创建一个线程。因为在完成端口理想模型中,每个线程都可以从系统获得一个“原子”性的时间片,轮番运行并检查完成端口,线程的切换是额外的开销。但在实际开发的时候,还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作,系统则将其挂起,让别的线程获得运行时间。因此,如果有这样的情况,可以多创建几个线程来尽量利用时间。

创建完成端口的函数

完成端口是一个内核对象,使用时它总是要和至少一个有效的设备句柄相关联,完成端口是一个复杂的内核对象,创建它的函数是:

HANDLE CreateIoCompletionPort(

IN HANDLE FileHandle,

IN HANDLE ExistingCompletionPort,

IN ULONG_PTR CompletionKey,

IN DWORD NumberOfConcurrentThreads

);

通常创建工作分两步:

第一步,创建一个新的完成端口内核对象,可以使用下面的函数:

HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)

{

return

CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThrea ds);

};

第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:

bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)

{

HANDLE

h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);

return h==hCompPort;

};

说明如下:

1)CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄。

2)CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。

3)NumberOfConcurrentThreads用来指定要允许同时运行的的线程的最大个数,通常我们指定为0,这样系统会根据CPU的个数来自动确定。

4)创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中该纪录会被自动删除。

4、完成端口线程的工作原理

1)完成端口管理线程池

完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们自己使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:

BOOL GetQueuedCompletionStatus(

IN HANDLE CompletionPort,

OUT LPDWORD lpNumberOfBytesTransferred,

OUT PULONG_PTR lpCompletionKey,

OUT LPOVERLAPPED *lpOverlapped,

IN DWORD dwMilliseconds

);

这个函数试图从指定的完成端口的I/0完成队列中提取纪录。只有当重叠I/O 动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将会被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维

护这个线程。完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成一个重叠I/0操作。

当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。所以我们的线程要想成为完成端口管理的线程,就必须要调用GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。

2)线程间数据传递

完成端口线程间传递数据最常用的办法是在_beginthreadex函数中将参数

传递给线程函数,或者使用全局变量。但完成端口也有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。

CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将CompletionKey 表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。

OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile这样的函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。

3)线程的安全退出

很多线程为了不止一次地执行异步数据处理,需要使用如下语句

while (true)

{

......

GetQueuedCompletionStatus(...);

......

}

那么线程如何退出呢,答案就在于上面曾提到过的PostQueudCompletionStatus 函数,我们可以向它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。

5、Windows完成端口的实例代码

DWORD WINAPI WorkerThread(LPVOID lpParam)

{

ULONG_PTR *PerHandleKey;

OVERLAPPED *Overlap;

OVERLAPPEDPLUS *OverlapPlus;

OVERLAPPEDPLUS *newolp;

DWORD dwBytesXfered;

while (1)

{

ret = GetQueuedCompletionStatus(hIocp, &dwBytesXfered,

(PULONG_PTR)&PerHandleKey, &Overlap, INFINITE);

if (ret == 0)

{

// Operation failed

continue;

}

OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);

switch (OverlapPlus->OpCode)

{

case OP_ACCEPT:

// Client socket is contained in OverlapPlus.sclient

// Add client to completion port

CreateIoCompletionPort((HANDLE)OverlapPlus->sclient, hIocp,

(ULONG_PTR)0, 0);

// Need a new OVERLAPPEDPLUS structure

// for the newly accepted socket. Perhaps

// keep a look aside list of free structures.

newolp = AllocateOverlappedPlus();

if (!newolp)

{

// Error

}

newolp->s = OverlapPlus->sclient;

newolp->OpCode = OP_READ;

// This function divpares the data to be sent

PrepareSendBuffer(&newolp->wbuf);

ret = WSASend(newolp->s, &newolp->wbuf, 1, &newolp->dwBytes, 0,

&newolp.ol, NULL);

if (ret == SOCKET_ERROR)

{

if (WSAGetLastError() != WSA_IO_PENDING)

{

// Error

}

}

// Put structure in look aside list for later use FreeOverlappedPlus(OverlapPlus);

// Signal accept thread to issue another AcceptEx

SetEvent(hAcceptThread);

break;

case OP_READ:

// Process the data read

// Repost the read if necessary, reusing the same

// receive buffer as before

memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));

ret = WSARecv(OverlapPlus->s, &OverlapPlus->wbuf, 1,

&OverlapPlus->dwBytes, &OverlapPlus->dwFlags, &OverlapPlus->ol, NULL); if (ret == SOCKET_ERROR)

{

if (WSAGetLastError() != WSA_IO_PENDING)

{

// Error

}

}

break;

case OP_WRITE:

// Process the data sent, etc.

break;

} // switch

} // while

} // WorkerThread

查看以上代码,注意如果Overlapped操作立刻失败(比如,返回SOCKET_ERROR 或其他非WSA_IO_PENDING的错误),则没有任何完成通知时间会被放到完成端口队列里。反之,则一定有相应的通知时间被放到完成端口队列。更完善的关于Winsock的完成端口机制,可以参考 MSDN的Microsoft PlatForm SDK,那里有完成端口的例子。

完成端口例子(转)

2009-11-04 10:55

这个例子未运行成功,但原理表述的很透彻。

--------------------------------------------------------------------------

#include

#include

#include

#define PORT 5150

#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef enum

{

RECV_POSTED

}OPERATION_TYPE; //枚举,表示状态

typedef struct

{

WSAOVERLAPPED overlap;

WSABUF Buffer;

char szMessage[MSGSIZE];

DWORD NumberOfBytesRecvd;

DWORD Flags;

OPERATION_TYPE OperationType;

}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA; //定义一个结构体保存IO数据

DWORD WINAPI WorkerThread(LPVOID);

int main()

{

WSADATA wsaData;

SOCKET sListen, sClient;

SOCKADDR_IN local, client;

DWORD i, dwThreadId;

int iaddrSize = sizeof(SOCKADDR_IN);

HANDLE CompletionPort = INVALID_HANDLE_VALUE; SYSTEM_INFO systeminfo;

LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

//初始化Socket

WSAStartup(0x0202, &wsaData);

// 初始化完成端口

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

// 有几个CPU就创建几个工作者线程

GetSystemInfo(&systeminfo);

for(i = 0; i < systeminfo.dwNumberOfProcessors; i++)

{

CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId); }

// 创建套接字

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 绑定套接字

local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

local.sin_family = AF_INET;

local.sin_port = htons(PORT);

bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

// 开始监听!

listen(sListen, 3);

//主进程的这个循环中循环等待客户端连接,若有连接,则将该客户套接字于完成端口绑定到一起

//然后开始异步等待接收客户传来的数据。

while (TRUE)

{

// 如果接到客户请求连接,则继续,否则等待。

sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize); //client中保存用户信息。

printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

//将这个最新到来的客户套接字和完成端口绑定到一起。

//第三个参数表示传递的参数,这里就传递的客户套接字地址。

//最后一个参数为0 表示有和CPU一样的进程数。即1个CPU一个线程

CreateIoCompletionPort((HANDLE)sClient, CompletionPort,

( ULONG_PTR)sClient, 0);

// 初始化结构体使用堆内存分配

lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_OPERATION_DATA));

lpPerIOData->Buffer.len = MSGSIZE; // len=1024

lpPerIOData->Buffer.buf = lpPerIOData->szMessage;

lpPerIOData->OperationType = RECV_POSTED; //操作类型

WSARecv(sClient, //异步接收消息,立刻返回。

&lpPerIOData->Buffer, //获得接收的数据

1, //The number of WSABUF structures in the lpBuffers array. &lpPerIOData->NumberOfBytesRecvd, //接收到的字节数,如果错误返回0

&lpPerIOData->Flags, //参数,先不管

&lpPerIOData->overlap, //输入这个结构体咯。

NULL);

}

//向每个工作者线程都发送—个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”.

PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL); CloseHandle(CompletionPort);

closesocket(sListen);

WSACleanup();

return 0;

}

//工作者线程有一个参数,是指向完成端口的句柄

DWORD WINAPI WorkerThread(LPVOID CompletionPortID)

{

HANDLE CompletionPort=(HANDLE)CompletionPortID; DWORD dwBytesTransferred;

SOCKET sClient;

LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

while (TRUE)

{

GetQueuedCompletionStatus( //遇到可以接收数据则返回,否则等待

CompletionPort,

&dwBytesTransferred, //返回的字数

(PULONG_PTR&)sClient, //是响应的哪个客户套接字?

(LPOVERLAPPED *)&lpPerIOData, //得到该套接字保存的IO信息

INFINITE); //无限等待咯。不超时的那种。

if (dwBytesTransferred == 0xFFFFFFFF)

{

return 0;

}

if(lpPerIOData->OperationType == RECV_POSTED) //如果收到数据

{

if (dwBytesTransferred == 0)

{

//失去客户端连接

closesocket(sClient);

HeapFree(GetProcessHeap(), 0, lpPerIOData); //释放结构体 }

else

{

lpPerIOData->szMessage[dwBytesTransferred] = '\0';

send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0); //将接收到的消息返回

// Launch another asynchronous operation for sClient

memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));

lpPerIOData->Buffer.len = MSGSIZE;

lpPerIOData->Buffer.buf = lpPerIOData->szMessage;

lpPerIOData->OperationType = RECV_POSTED;

WSARecv(sClient, //循环接收

&lpPerIOData->Buffer,

1,

&lpPerIOData->NumberOfBytesRecvd,

&lpPerIOData->Flags,

&lpPerIOData->overlap,

NULL);

}

}

}

return 0;

}

/*

首先,说说主线程:

1.创建完成端口对象

2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能)

3.创建监听套接字,绑定,监听,然后程序进入循环

4.在循环中,我做了以下几件事情:

(1).接受一个客户端连接

(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),

注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,

一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,

由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递;

(3).触发一个WSARecv异步调用,用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,

此外,还有操作类型等重要信息。

在工作者线程的循环中,我们

1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等)

2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端

3.再次触发一个WSARecv异步操作

*/

查看文章

完成端口例子2(转)

2009-11-04 10:56

这个例子可以正常运行

---------------------------------------------------------

#include

#include

#include

#define PORT 5150

#define DATA_BUFSIZE 8192

#pragma comment(lib, "Ws2_32")

typedef struct //这个玩意就是灌数据,取数据的一个自定义数据结构

//和那个wm_data差不了多少,不过就是老要塞一个OverLapped结构,

{

OVERLAPPED Overlapped;

WSABUF DataBuf;

CHAR Buffer[DATA_BUFSIZE];

DWORD BytesSEND; //发送字节数

DWORD BytesRECV;

} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;

typedef struct

{

SOCKET Socket;

} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);

void main(void)

{

SOCKADDR_IN InternetAddr;

SOCKET Listen;

SOCKET Accept;

HANDLE CompletionPort;

SYSTEM_INFO SystemInfo;

LPPER_HANDLE_DATA PerHandleData;

LPPER_IO_OPERATION_DATA PerIoData;

int i;

DWORD RecvBytes;

DWORD Flags;

DWORD ThreadID;

WSADATA wsaData;

DWORD Ret;

if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)

{

printf("WSAStartup failed with error %d\n", Ret);

return;

}

//

//完成端口的建立得搞2次,这是第一次调用,至于为什么?我问问你

//

if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)

{

printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());

return;

}

//老套子api,不谈也罢

GetSystemInfo(&SystemInfo);

//发现2个CPU,那就开个双倍的线程跑吧

for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)

{

HANDLE ThreadHandle;

//

//完成端口挂到线程上面来了,就像管子把灌数据的和读数据的两头都连上了

//

if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort, 0, &ThreadID)) == NULL)

{

printf("CreateThread() failed with error %d\n",

GetLastError());

return;

}

CloseHandle(ThreadHandle);

}

//

//启动一个监听socket ,以下都是长长的交代

//

if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,

WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)

{

printf("WSASocket() failed with error %d\n", WSAGetLastError()); return;

}

InternetAddr.sin_family = AF_INET;

InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);

InternetAddr.sin_port = htons(PORT);

if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)

{

printf("bind() failed with error %d\n", WSAGetLastError());

return;

}

if (listen(Listen, 5) == SOCKET_ERROR)

{

printf("listen() failed with error %d\n", WSAGetLastError()); return;

}

//

// 监听端口打开,就开始在这里循环,一有socket连上,WSAAccept就创建一个socket,

// 这个socket 又和完成端口联上,

//

// 嘿嘿,完成端口第二次调用那个createxxx函数,为什么,留给人思考思考可能更深刻,

// 反正这套路得来2次,

// 完成端口completionport和accept socket挂起来了,

//

while(TRUE)

{

//主线程跑到这里就等啊等啊,但是线程却开工了,

if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)

{

printf("WSAAccept() failed with error %d\n", WSAGetLastError());

return;

}

//该函数从堆中分配一定数目的字节数.Win32内存管理器并不提供相互分

开的局部和全局堆.提供这个函数只是为了与16位的Windows相兼容

//

if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)

{

printf("GlobalAlloc() failed with error %d\n",

GetLastError());

return;

}

PerHandleData->Socket = Accept;

//

//把这头和完成端口completionPort连起来

//就像你把漏斗接到管子口上,开始要灌数据了

//

if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData, 0) == NULL)

{

printf("CreateIoCompletionPort failed with error %d\n", GetLastError());

return;

}

//

//清管子的数据结构,准备往里面灌数据

//

if ((PerIoData = (LPPER_IO_OPERATION_DATA)

GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)

{

printf("GlobalAlloc() failed with error %d\n",

GetLastError());

return;

}

//用0填充一块内存区域

ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

PerIoData->BytesSEND = 0;

PerIoData->BytesRECV = 0;

PerIoData->DataBuf.len = DATA_BUFSIZE;

PerIoData->DataBuf.buf = PerIoData->Buffer;

Flags = 0;

//

// accept接到了数据,就放到PerIoData中,而perIoData又通过线程中的函数取出,

//

if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)

{

if (WSAGetLastError() != ERROR_IO_PENDING)

{

printf("WSARecv() failed with error %d\n", WSAGetLastError());

return;

}

}

}

}

//

//线程一但调用,就老在里面循环,

// 注意,传入的可是完成端口啊,就是靠它去取出管子中的数据

//

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)

{

HANDLE CompletionPort = (HANDLE) CompletionPortID;

DWORD BytesTransferred;

LPOVERLAPPED Overlapped;

LPPER_HANDLE_DATA PerHandleData;

LPPER_IO_OPERATION_DATA PerIoData;

DWORD SendBytes, RecvBytes;

DWORD Flags;

while(TRUE)

{

//

//在这里检查完成端口部分的数据buf区,数据来了吗?

// 这个函数参数要看说明,

// PerIoData 就是从管子流出来的数据,

//PerHandleData 也是从管子里取出的,是何时塞进来的,

//就是在建立第2次createIocompletionPort时

//

if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0) {

printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());

return 0;

}

// 检查数据传送完了吗

if (BytesTransferred == 0)

{

printf("Closing socket %d\n", PerHandleData->Socket);

if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)

{

printf("closesocket() failed with error %d\n", WSAGetLastError());

return 0;

}

GlobalFree(PerHandleData);

GlobalFree(PerIoData);

continue;

}

//

//看看管子里面有数据来了吗?=0,那是刚收到数据

//

if (PerIoData->BytesRECV == 0)

{

PerIoData->BytesRECV = BytesTransferred;

PerIoData->BytesSEND = 0;

}

else //来了,

{

PerIoData->BytesSEND += BytesTransferred;

}

printf("print: %d\n", PerIoData->BytesRECV);

//

// 数据没发完?继续send出去

//

if (PerIoData->BytesRECV > PerIoData->BytesSEND)

{

ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); //清0为发送准备

PerIoData->DataBuf.buf = PerIoData->Buffer +

PerIoData->BytesSEND;

PerIoData->DataBuf.len = PerIoData->BytesRECV -

PerIoData->BytesSEND;

//1个字节一个字节发送发送数据出去

if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1,

&SendBytes, 0,

&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)

{

if (WSAGetLastError() != ERROR_IO_PENDING)

{

printf("WSASend() failed with error %d\n", WSAGetLastError());

return 0;

}

}

}

else

{

PerIoData->BytesRECV = 0;

Flags = 0;

ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

PerIoData->DataBuf.len = DATA_BUFSIZE;

PerIoData->DataBuf.buf = PerIoData->Buffer;

if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,

&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)

{

if (WSAGetLastError() != ERROR_IO_PENDING)

{

printf("WSARecv() failed with error %d\n", WSAGetLastError());

return 0;

}

}

}

}

}

译]IOCP服务器/客户端实现 (转)

2009-09-22 16:46

——A simple IOCP Server/Client Class By spinoza

——译: Ocean Email: Ocean2oo6@https://www.sodocs.net/doc/6e1879140.html,

原文选自CodeProject

源代码:

https://www.sodocs.net/doc/6e1879140.html,/KB/IP/iocp_server_client/IOCP-Demo.zip https://www.sodocs.net/doc/6e1879140.html,/KB/IP/iocp_server_client/IOCP-SRC.zip

纯属学习交流用途,转载请声明。

This source code uses the advanced IOCP technology which can efficiently serve multiple clients. It also presents some solutions to practical problems that arise with the IOCP programming API, and provides a simple echo client/server with file transfer.

1.1要求

本文希望读者对C++,TCP/IP ,Socket编程,MFC以及多线程比较熟悉

源代码使用Winsock2.0 以及IOCP技术,因此需要:

o Windows NT/2000 or later: Requires Windows NT 3.5 or later.

o Windows 95/98/ME: Not supported.

o Visual C++ .NET, or a fully updated Visual C++ 6.0.

o

1.2 摘要

当你开发不同类型的软件时,你总会需要进行C/S的开发。完成一个完善的C/S 代码对于编码人员来说是一件困难的事情。本文给出了一个简单的但是却是却

十分强大的C/S源代码,他可以扩展成任何类型的C/S程序。源代码使用了IOCP 技术,该技术可以有效地处理多客户端。 IOCP 对于“一个客户端一个线程”所有面临的瓶颈(或者其他)问题提出了一种有效的解决方案,他只使用少量的执行线程以及异步的输入输出、接受发送。IOCP计数被广泛的用于各种高性能的

编译原理实验指导

编译原理实验指导 实验安排: 上机实践按小组完成实验任务。每小组三人,分别完成TEST语言的词法分析、语法分析、语义分析和中间代码生成三个题目,语法分析部分可任意选择一种语法分析方法。先各自调试运行,然后每小组将程序连接在一起调试,构成一个相对完整的编译器。 实验报告: 上机结束后提交实验报告,报告内容: 1.小组成员; 2.个人完成的任务; 3.分析及设计的过程; 4.程序的连接; 5.设计中遇到的问题及解决方案; 6.总结。

实验一词法分析 一、实验目的 通过设计编制调试TEST语言的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。 二、实验预习提示 1.词法分析器的功能和输出格式 词法分析器的功能是输入源程序,输出单词符号。词法分析器的单词符号常常表示 成以下的二元式(单词种别码,单词符号的属性值)。 2.TEST语言的词法规则 |ID|ID |NUM →a|b|…|z|A|B|…|Z →1|2|…|9|0 →+|-|*|/|=|(|)|{|}|:|,|;|<|>|! →>=|<=|!=|== →/* →*/ 三、实验过程和指导 1.阅读课本有关章节,明确语言的语法,画出状态图和词法分析算法流程图。 2.编制好程序。 3.准备好多组测试数据。 4.程序要求 程序输入/输出示例:

编译原理复习题(经典)

编译原理复习题 一、是非题 1.计算机高级语言翻译成低级语言只有解释一种方式。(×) 3.每个文法都能改写为 LL(1) 文法。 (×) 4.算符优先关系表不一定存在对应的优先函数。 (√) 5.LR分析方法是自顶向下语法分析方法。 (×) 6.“用高级语言书写的源程序都必须通过编译,产生目标代码后才能投入运行”这种说法。(× ) 7.一个句型的句柄一定是文法某产生式的右部。 (√) 8.仅考虑一个基本块,不能确定一个赋值是否真是无用的。 (√ ) 9.在中间代码优化中循环上的优化主要有不变表达式外提和削减运算强度。 (× ) 10.对于数据空间的存贮分配,FORTRAN采用动态贮存分配策略。(×) 11.甲机上的某编译程序在乙机上能直接使用的必要条件是甲机和乙机的操作系统功能完全相同。(× ) 12.递归下降分析法是自顶向下分析方法。(√ ) 13.产生式是用于定义词法成分的一种书写规则。 (×) 14.在 SLR(1)分析法的名称中,S的含义是简单的。(√) 15.综合属性是用于“自上而下”传递信息。(× ) 16.符号表中的信息栏中登记了每个名字的属性和特征等有关信息,如类型、种属、所占单元大小、地址等等。(×) 17.程序语言的语言处理程序是一种应用软件。 (×) 18.解释程序适用于 COBOL 和 FORTRAN 语言。 (×) 19.一个 LL(l)文法一定是无二义的。 (√) 20.正规文法产生的语言都可以用上下文无关文法来描述。 (√) 21.一张转换图只包含有限个状态,其中有一个被认为是初态,最多只有一个终态。 (×) 22.目标代码生成时,应考虑如何充分利用计算机的寄存器的问题。 (√) 22.逆波兰法表示的表达式亦称后缀式。 (√ ) 23.如果一个文法存在某个句子对应两棵不同的语法树,则称这个文法是二义的。 (√ ) 24.数组元素的地址计算与数组的存储方式有关。(√) 25.算符优先关系表不一定存在对应的优先函数。 (×) 26.编译程序是对高级语言程序的解释执行。(× ) 27.一个有限状态自动机中,有且仅有一个唯一的终态。(×) 28.一个算符优先文法可能不存在算符优先函数与之对应。 (√ ) 29.语法分析时必须先消除文法中的左递归。 (×) 30.LR分析法在自左至右扫描输入串时就能发现错误,但不能准确地指出出错地点。 (√) 31.逆波兰表示法表示表达式时无须使用括号。 (√ ) 32.静态数组的存储空间可以在编译时确定。 (√) 33.进行代码优化时应着重考虑循环的代码优化,这对提高目标代码的效率将起更大作用。 (√) 34.两个正规集相等的必要条件是他们对应的正规式等价。 (√) 35.一个语义子程序描述了一个文法所对应的翻译工作。 (×) 36.设r和s分别是正规式,则有L(r|s)=L(r)L(s)。(×) 37.确定的自动机以及不确定的自动机都能正确地识别正规集。(√) 38.词法分析作为单独的一遍来处理较好。 (× ) 39.构造LR分析器的任务就是产生LR分析表。 (√) 40.规范归约和规范推导是互逆的两个过程。 (√) 41.同心集的合并有可能产生新的“移进”/“归约”冲突。 (× ) 42.LR分析技术无法适用二义文法。 (× )

IOCP完成端口详解(10年吐血大总结)

IOCP完成端口超级详解 目录: 1.完成端口的优点 2.完成端口程序的运行演示 3.完成端口的相关概念 4.完成端口的基本流程 5.完成端口的使用详解 6.实际应用中应该要注意的地方 一.完成端口的优点 1. 我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S 通信模式中性能最好的网络通信模型,没有之一;甚至连和它性能接近的通信模型都没有。 2. 完成端口和其他网络通信方式最大的区别在哪里呢? (1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。 (2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不? (3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,这种神奇的效果具体是如何实现的请看下文。

编译原理知识点汇总

编译原理的复习提纲 1.编译原理=形式语言+编译技术 2.汇编程序: 把汇编语言程序翻译成等价的机器语言程序 3.编译程序: 把高级语言程序翻译成等价的低级语言程序 4.解释执行方式: 解释程序,逐个语句地模拟执行 翻译执行方式: 翻译程序,把程序设计语言程序翻译成等价的目标程序 5.计算机程序的编译过程类似,一般分为五个阶段: 词法分析、语法分析、语义分析及中间代码生成、代码优化、目标代码生成 词法分析的任务: 扫描源程序的字符串,识别出的最小的语法单位(标识符或无正负号数等) 语法分析是: 在词法分析的基础上的,语法分析不考虑语义。语法分析读入词法分析程序识别出的符号,根据给定的语法规则,识别出各个语法结构。 语义分析的任务是检查程序语义的正确性,解释程序结构的含义,语义分析包括检查变量是否有定义,变量在使用前是否具有值,数值是否溢出等。

语法分析完成之后,编译程序通常就依据语言的语义规则,利用语法制导技术把源程序翻译成某种中间代码。所谓中间代码是一种定义明确、便于处理、独立于计算机硬件的记号系统,可以认为是一种抽象机的程序 代码优化的主要任务是对前一阶段产生的中间代码进行等价变换,以便产生速度快、空间小的目标代码 编译的最后一个阶段是目标代码生成,其主要任务是把中间代码翻译成特定的机器指令或汇编程序 编译程序结构包括五个基本功能模块和两个辅助模块 6.编译划分成前端和后端。 编译前端的工作包括词法分析、语法分析、语义分析。编译前端只依赖于源程序,独立于目标计算机。前端进行分析 编译后端的工作主要是目标代码的生成和优化后端进行综合。独立于源程序,完全依赖于目标机器和中间代码。 把编译程序分为前端和后端的优点是: 可以优化配置不同的编译程序组合,实现编译重用,保持语言与机器的独立性。 7.汇编器把汇编语言代码翻译成一个特定的机器指令序列 第二章 1.符号,字母表,符号串,符号串的长度计算P18,子符号串的含义,符号串的简单运算XY,Xn, 2.符号串集合的概念,符号串集合的乘积运算,方幂运算,闭包与正闭包的概念P19,P20A0 ={ε} 3.重写规则,简称规则。非xx(V

IOCP完成端口原理-详解

本文主要探讨一下windows平台上的完成端口开发及其与之相关的几个重要的技术概念,这些概念都是与基于IOCP的开发密切相关的,对开发人员来讲,又不得不给予足够重视的几个概念: 1) 基于IOCP实现的服务吞吐量 2)IOCP模式下的线程切换 3)基于IOCP实现的消息的乱序问题。 一、IOCP简介 提到IOCP,大家都非常熟悉,其基本的编程模式,我就不在这里展开了。在这里我主要是把IOCP中所提及的概念做一个基本性的总结。IOCP的基本架构图如下: 如图所示:在IOCP中,主要有以下的参与者: --》完成端口:是一个FIFO队列,操作系统的IO子系统在IO操作完成后,会把相应的IO packet放入该队列。 --》等待者线程队列:通过调用GetQueuedCompletionStatus API,在完成端口上等待取下一个IO packet。 --》执行者线程组:已经从完成端口上获得IO packet,在占用CPU进行处理。除了以上三种类型的参与者。我们还应该注意两个关联关系,即: --》IO Handle与完成端口相关联:任何期望使用IOCP的方式来处理IO请求的,必须将相应的IO Handle与该完成端口相关联。需要指出的时,这里的IO Handle,可以是File的Handle,或者是Socket的Handle。 --》线程与完成端口相关联:任何调用GetQueuedCompletionStatus API的线程,都将与该完成端口相关联。在任何给定的时候,该线程只能与一个完成端口相关联,与最后一次调用的GetQueuedCompletionStatus为准。 二、高并发的服务器(基于socket)实现方法

(完整版)编译原理课后习题答案

第一章 1.典型的编译程序在逻辑功能上由哪几部分组成? 答:编译程序主要由以下几个部分组成:词法分析、语法分析、语义分析、中间代码生成、中间代码优化、目标代码生成、错误处理、表格管理。 2. 实现编译程序的主要方法有哪些? 答:主要有:转换法、移植法、自展法、自动生成法。 3. 将用户使用高级语言编写的程序翻译为可直接执行的机器语言程序有哪几种主要的方式? 答:编译法、解释法。 4. 编译方式和解释方式的根本区别是什么? 答:编译方式:是将源程序经编译得到可执行文件后,就可脱离源程序和编译程序单独执行,所以编译方式的效率高,执行速度快; 解释方式:在执行时,必须源程序和解释程序同时参与才能运行,其不产生可执行程序文件,效率低,执行速度慢。

第二章 1.乔姆斯基文法体系中将文法分为哪几类?文法的分类同程序设计语言的设计与实现关 系如何? 答:1)0型文法、1型文法、2型文法、3型文法。 2) 2. 写一个文法,使其语言是偶整数的集合,每个偶整数不以0为前导。 答: Z→SME | B S→1|2|3|4|5|6|7|8|9 M→ε | D | MD D→0|S B→2|4|6|8 E→0|B 3. 设文法G为: N→ D|ND D→ 0|1|2|3|4|5|6|7|8|9 请给出句子123、301和75431的最右推导和最左推导。 答:N?ND?N3?ND3?N23?D23?123 N?ND?NDD?DDD?1DD?12D?123 N?ND?N1?ND1?N01?D01?301 N?ND?NDD?DDD?3DD?30D?301 N?ND?N1?ND1?N31?ND31?N431?ND431?N5431?D5431?75431 N?ND?NDD?NDDD?NDDDD?DDDDD?7DDDD?75DDD?754DD?7543D?75431 4. 证明文法S→iSeS|iS| i是二义性文法。 答:对于句型iiSeS存在两个不同的最左推导: S?iSeS?iiSes S?iS?iiSeS 所以该文法是二义性文法。 5. 给出描述下面语言的上下文无关文法。 (1)L1={a n b n c i |n>=1,i>=0 } (2)L2={a i b j|j>=i>=1} (3)L3={a n b m c m d n |m,n>=0} 答: (1)S→AB A→aAb | ab B→cB | ε (2)S→ASb |ab

编译原理实验代码

[实验任务] 完成以下正则文法所描述的Pascal语言子集单词符号的词法分析程序。 <标识符>→字母︱<标识符>字母︱<标识符>数字 <无符号整数>→数字︱<无符号整数>数字 <单字符分界符> →+ ︱-︱* ︱; ︱(︱) <双字符分界符>→<大于>=︱<小于>=︱<小于>>︱<冒号>=︱<斜竖>* <小于>→< <等于>→= <大于>→> <冒号> →: <斜竖> →/ 该语言的保留字:begin end if then else for do while and or not 说明:1 该语言大小写不敏感。 2 字母为a-z A-Z,数字为0-9。 3可以对上述文法进行扩充和改造。 4 ‘/*……*/’为程序的注释部分。 [设计要求] 1、给出各单词符号的类别编码。 2、词法分析程序应能发现输入串中的错误。 3、词法分析作为单独一遍编写,词法分析结果为二元式序列组成的中间文件。 4、设计两个测试用例(尽可能完备),并给出测试结果。 demo.cpp #include #include #include #include "demo.h" char token[20]; int lookup(char *token) { for (int i = 0; i < 11; i++) { if (strcmp(token, KEY_WORDS[i]) == 0) { return i+1; } } return 0; } char getletter(FILE *fp) { return tolower(fgetc(fp)); } void out(FILE *fp, int c, char *value) {

编译原理(PL0编译程序源代码)

/*PL/0编译程序(C语言版) *编译和运行环境: *Visual C++6.0 *WinXP/7 *使用方法: *运行后输入PL/0源程序文件名 *回答是否将虚拟机代码写入文件 *回答是否将符号表写入文件 *执行成功会产生四个文件(词法分析结果.txt符号表.txt虚拟代码.txt源程序和地址.txt) */ #include #include"pl0.h" #include"string" #define stacksize 500//解释执行时使用的栈 int main(){ bool nxtlev[symnum]; printf("请输入源程序文件名:"); scanf("%s",fname); fin=fopen(fname,"r");//以只读方式打开pl0源程序文件 cifa=fopen("词法分析结果.txt","w"); fa1=fopen("源程序和地址.txt","w");//输出源文件及各行对应的首地址 fprintf(fa1,"输入pl0源程序文件名:"); fprintf(fa1,"%s\n",fname); if(fin){ printf("是否将虚拟机代码写入文件?(Y/N)");//是否输出虚拟机代码 scanf("%s",fname); listswitch=(fname[0]=='y'||fname[0]=='Y'); printf("是否将符号表写入文件?(Y/N)");//是否输出符号表scanf("%s",fname); tableswitch=(fname[0]=='y'||fname[0]=='Y'); init();//初始化 err=0; cc=cx=ll=0; ch=' '; if(-1!=getsym()){ fa=fopen("虚拟代码.txt","w"); fas=fopen("符号表.txt","w"); addset(nxtlev,declbegsys,statbegsys,symnum); nxtlev[period]=true; if(-1==block(0,0,nxtlev)){//调用编译程序 fclose(fa); fclose(fa1); fclose(fas); fclose(fin); return 0; } if(sym!=period){ error(9);//结尾丢失了句号 }

交换机端口镜像配置

CISCO、3COM、华为等主流交换机端口镜像配置 各型号交换机端口镜像配置方法和命令 "Port Mirror"即端口镜像,端口镜像为网络传输提供了备份通道。此外,还可以用于进行数据流量监测。可以这样理解:在端口A和端口B之间建立镜像关系,这样,通过端口A传输的数据将同时通过端口B传输,即使端口A处因传输线路等问题造成数据错误,还有端口B处的数据是可用的。 Cisco交换机端口镜像配置,cisco交换机最多支持2组镜像,支持所有端口镜像。默认密码cisco Cisco catylist2820: 有2个菜单选项 先进入menu选项,enable port monitor 进入cli模式, en conf term interface fast0/x 镜像口 port monitor fast0/x 被镜像口 exit wr Cisco catylist2924、2948 Cisco catylist 3524、3548 Switch>En Switch#Conf term Switch(config)#Interface fast mod/port Switch(config-if)#Port monitor mod/port Switch(config-if)#Exit Switch(config)#Wr Cisco catylist 2550 Cisco catylist 3550: 支持2组monitor session en password config term Switch(config)#monitor session 1 destination interface fast0/4(1为session id,id范围为1-2)Switch(config)#monitor session 1 source interface fast0/1 , fast0/2 , fast0/3 (空格,逗号,空格) Switch(config)#exit Switch#copy running-conf startup-conf Switch#show port-monitor Cisco catylist 4000/5000系列Cisco catylist 6000 系列: 支持2组镜像 En Show module (确认端口所在的模块) Set span source(mod/port) destination(mod/port) in|out|both inpkts enable Write tern all Show span 注:多个source:mod/port,mod/port-mod/port 连续端口用横杆“-”,非连续端口用逗号“,”

编译原理实验题目及报告要求

编译原理上机实验试题 一、实验目的 通过本实验使学生进一步熟悉和掌握程序设计语言的词法分析程序的设计原理及相关的设计技术, 如何针对确定的有限状态自动机进行编程序;熟悉和 掌握程序设计语言的语法分析程序的设计原理、熟悉 和掌握算符优先分析方法。 二、实验要求 本实验要求:①要求能熟练使用程序设计语言编程;②在上机之前要有详细的设计报告(预习报告); ③要编写出完成相应任务的程序并在计算机上准确 地运行;④实验结束后要写出上机实验报告。 三、实验题目 针对下面文法G(S): S→v = E E→E+E│E-E│E*E│E/E│(E)│v │i 其中,v为标识符,i为整型或实型数。要求完成 ①使用自动机技术实现一个词法分析程序; ②使用算符优先分析方法实现其语法分析程序,在 语法分析过程中同时完成常量表达式的计算。

1、题目(见“编译原理---实验题目.doc,“实验题目”中的第一项) 2、目的与要求(见“编译原理---实验题目.doc”) 3、设计原理: (1)单词分类:标识符,保留字,常数,运算符,分隔符等等 (2)单词类型编码 (3)自动机 4、程序流程框图 5、函数原型(参数,返回值) 6、关键代码(可打印,只打印关键代码) 7、调试: (1)调试过程中遇到的错误,如何改进的; (2)需要准备测试用例(至少3个,包含输入和输出)——(可打印) 8、思考: (1)你编写的程序有哪些要求是没有完成的,你觉得该采用什么方法去完成; (2)或者是你觉得程序有哪些地方可以进一步完善,简述你的完善方案。

1、题目(见“编译原理---实验题目.doc,“实验题目”中的第二项) 2、目的与要求(见“编译原理---实验题目.doc”) 3、设计原理:构造出算法优先关系表 4、程序流程框图 5、函数原型(参数,返回值) 6、关键代码(可打印,只打印关键代码) 7、调试: (1)调试过程中遇到的错误,如何改进的; (2)需要准备测试用例(至少3个,包含输入和输出)——(可打印) 8、思考: (1)你编写的程序有哪些要求是没有完成的,你觉得该采用什么方法去完成; (2)或者是你觉得程序有哪些地方可以进一步完善,简述你的完善方案。

编译原理第三版附带的实验源码

Scanner: #include #include #include #define _KEY_WORD_END "waiting for your expanding" typedef struct { int typenum; char * word; } WORD; char input[255]; char token[255]=""; int p_input; int p_token; char ch; char* KEY_WORDS[]={"main","int","char","if","else","for","while",_KEY_WORD_END}; WORD* scaner(); void main() { int over=1; WORD* oneword=new WORD; printf("Enter Your words(end with $):"); scanf("%[^$]s",input); p_input=0; printf("Your words:\n%s\n",input); while(over<1000&&over!=-1){ oneword=scaner(); if(oneword->typenum<1000) printf("(%d,%s)",oneword->typenum,oneword->word); over=oneword->typenum; } printf("\npress # to exit:"); scanf("%[^#]s",input); } char m_getch(){ ch=input[p_input]; p_input=p_input+1; return (ch); } void getbc(){

编译原理实验:目标代码的生成

5. 目标代码生成 本章实验为实验四,是最后一次实验,其任务是在词法分析、语法分析、语义分析和中间代码生成程序的基础上,将C 源代码翻译为MIPS32指令序列(可以包含伪指令),并在SPIM Simulator上运行。当你完成实验四之后,你就拥有了一个自己独立编写、可以实际运行的编译器。 选择MIPS作为目标体系结构是因为它属于RISC范畴,与x86等体系结构相比形式简单便于我们处理。如果你对于MIPS体系结构或汇编语言不熟悉并不要紧,我们会提供详细的参考资料。 需要注意的是,由于本次实验的代码会与之前实验中你已经写好的代码进行对接,因此保持一个良好的代码风格、系统地设计代码结构和各模块之间的接口对于整个实验来讲相当重要。 5.1 实验内容 5.1.1 实验要求 为了完成实验四,我们建议你首先下载并安装SPIM Simulator用于对生成的目标代码进行检查和调试,SPIM Simulator的官方下载地址为:https://www.sodocs.net/doc/6e1879140.html,/~larus/spim.html。这是由原Wisconsin-Madison的Jame Larus教授(现在在微软)领导编写的一个功能强大的MIPS32汇编语言的汇编器和模拟器,其最新的图形界面版本QtSPIM由于使用了Qt组件因而可以在各大操作系统平台如Windows、Linux、Mac等上运行,推荐安装。我们会在后面介绍有关SPIM Simulator的使用方法。 你需要做的就是将实验三中得到的中间代码经过与具体体系结构相关的指令选择、寄存器选择以及栈管理之后,转换为MIPS32汇编代码。我们要求你的程序能输出正确的汇编代码。“正确”是指该汇编代码在SPIM Simulator(命令行或Qt版本均可)上运行结果正确。因此,以下几个方面不属于检查范围: 1)寄存器的使用与指派可以不必遵循MIPS32的约定。只要不影响在SPIM Simulator中的 正常运行,你可以随意分配MIPS体系结构中的32个通用寄存器,而不必在意哪些寄存器应该存放参数、哪些存放返回值、哪些由调用者负责保存、哪些由被调用者负责保存,等等。 2)栈的管理(包括栈帧中的内容及存放顺序)也不必遵循MIPS32的约定。你甚至可以使 用栈以外的方式对过程调用间各种数据的传递进行管理,前提是你输出的目标代码(即MIPS32汇编代码)能运行正确。

编译原理习题答案

《编译原理》习题答案: 第一次: P14 2、何谓源程序、目标程序、翻译程序、汇编程序、编译程序和解释程序?它们之间可能有何种关系? 答:被翻译的程序称为源程序; 翻译出来的程序称为目标程序或目标代码; 将汇编语言和高级语言编写的程序翻译成等价的机器语言,实现此功能的程序称为翻译程序; 把汇编语言写的源程序翻译成机器语言的目标程序称为汇编程序; 解释程序不是直接将高级语言的源程序翻译成目标程序后再执行,而是一个个语句读入源程序,即边解释边执行; 编译程序是将高级语言写的源程序翻译成目标语言的程序。 关系:汇编程序、解释程序和编译程序都是翻译程序,具体见P4 图 1.3。 P14 3、编译程序是由哪些部分组成?试述各部分的功能? 答:编译程序主要由8个部分组成:(1)词法分析程序;(2)语法分析程序;(3)语义分析程序;(4)中间代码生成;(5)代码优化程序;(6)目标代码生成程序;(7)错误检查和处理程序;(8)信息表管理程序。具体功能见P7-9。 P14 4、语法分析和语义分析有什么不同?试举例说明。 答:语法分析是将单词流分析如何组成句子而句子又如何组成程序,看句子乃至程序是否符合语法规则,例如:对变量 x:= y 符合语法规则就通过。语义分析是对语句意义进行检查,如赋值语句中x与y类型要一致,否则语法分析正确,语义分析则错误。 P15 5、编译程序分遍由哪些因素决定? 答:计算机存储容量大小;编译程序功能强弱;源语言繁简;目标程序优化程度;设计和实现编译程序时使用工具的先进程度以及参加人员多少和素质等等。 补充: 1、为什么要对单词进行内部编码?其原则是什么?对标识符是如何进行内部编码的? 答:内部编码从“源字符串”中识别单词并确定单词的类型和值;原则:长度统一,即刻画了单词本身,也刻画了它所具有的属性,以供其它部分分析使用。对于标识符编码,先判断出该单词是标识符,然后在类别编码中写入相关信息,以表示为标识符,再根据具体标识符的含义编码该单词的值。 补充: 2、赋值语句: A:= 5 * C的语法和语义指的是什么? 答:语法分析将检查该语句是否符合赋值语句规则,语义是指将 5 * C 的结果赋值为 A 。

端口镜像典型配置举例

端口镜像典型配置举例 1.5.1 本地端口镜像配置举例 1. 组网需求 某公司内部通过交换机实现各部门之间的互连,网络环境描述如下: ●研发部通过端口GigabitEthernet 1/0/1接入Switch C; ●市场部通过端口GigabitEthernet 1/0/2接入Switch C; ●数据监测设备连接在Switch C的GigabitEthernet 1/0/3端口上。 网络管理员希望通过数据监测设备对研发部和市场部收发的报文进行监控。 使用本地端口镜像功能实现该需求,在Switch C上进行如下配置: ●端口GigabitEthernet 1/0/1和GigabitEthernet 1/0/2为镜像源端口; ●连接数据监测设备的端口GigabitEthernet 1/0/3为镜像目的端口。 2. 组网图 图1-3 配置本地端口镜像组网图 3. 配置步骤 配置Switch C: # 创建本地镜像组。

system-view [SwitchC] mirroring-group 1 local # 为本地镜像组配置源端口和目的端口。 [SwitchC] mirroring-group 1 mirroring-port GigabitEthernet 1/0/1 GigabitEthernet 1/0/2 both [SwitchC] mirroring-group 1 monitor-port GigabitEthernet 1/0/3 # 显示所有镜像组的配置信息。 [SwitchC] display mirroring-group all mirroring-group 1: type: local status: active mirroring port: GigabitEthernet1/0/1 both GigabitEthernet1/0/2 both monitor port: GigabitEthernet1/0/3 配置完成后,用户就可以在数据监测设备上监控研发部和市场部收发的所有报文。

编译原理实验 中间代码生成

实验四中间代码生成 一.实验目的: 掌握中间代码的四种形式(逆波兰式、语法树、三元式、四元式)。 二.实验内容: 1、逆波兰式定义:将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表 达式也称做后缀式。 2、抽象(语法)树:运算对象作为叶子结点,运算符作为内部结点。 3、三元式:形式序号:(op,arg1,arg2) 4、四元式:形式(op,arg1,arg2,result) 三、以逆波兰式为例的实验设计思想及算法 (1)首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。 (2)读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”。 (3)从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。 (4)如果不是数字,该字符则是运算符,此时需比较优先关系。 做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。倘若不是的话,则将此运算符栈顶的运算符从栈中弹出,将该字符入栈。 (5)重复上述操作(1)-(2)直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。 四、程序代码: //这是一个由中缀式生成后缀式的程序 #include<> #include<> #include<> #include<> #define maxbuffer 64 void main() { char display_out(char out_ch[maxbuffer], char ch[32]); //int caculate_array(char out_ch[32]); static int i=0; static int j=0; char ch[maxbuffer],s[maxbuffer],out[maxbuffer]; cout<<"请输入中缀表达式: ";

(编译原理)逆波兰式算法的源代码

一.实验目的 1.深入理解算符优先分析法 2.掌握FirstVt和LastVt集合的求法有算符优先关系表的求法 3.掌握利用算符优先分析法完成中缀表达式到逆波兰式的转化 二.实验内容及要求 将非后缀式用来表示的算术表达式转换为用逆波兰式来表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值。 程序输入/输出示例: 输出的格式如下: (1) (2)输入一以#结束的中缀表达式(包括+—*/()数字#) (3) (4)逆波兰式 备注:(1)在生成的逆波兰式中如果两个数相连则用&分隔,如28和68,中间用&分隔; 注意:1.表达式中允许使用运算符(+-*/)、分割符(括号)、数字,结束符#; 2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好); 3.对学有余力的同学,测试用的表达式事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照; 三.实验过程 1、逆波兰式定义 将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表达式也称做后缀式。逆波兰式的特点在于运算对象顺序不变,运算符号位置反映运算顺序。采用逆波兰式可以很好的表示简单算术表达式,其优点在于易于计算机处理表达式。 2、产生逆波兰式的前提 中缀算术表达式 3、逆波兰式生成的实验设计思想及算法

(1)首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。 (2)读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”。 (3)从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。 (4)如果不是数字,该字符则是运算符,此时需比较优先关系。 做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。倘若不是的话,则将此运算符栈顶的运算

编译原理课后习题答案+清华大学出版社第二版

第 1 章引论 第1 题 解释下列术语: (1)编译程序 (2)源程序 (3)目标程序 (4)编译程序的前端 (5)后端 (6)遍 答案: (1)编译程序:如果源语言为高级语言,目标语言为某台计算机上的汇编语言或机器语言,则此翻译程序称为编译程序。 (2)源程序:源语言编写的程序称为源程序。 (3)目标程序:目标语言书写的程序称为目标程序。 (4)编译程序的前端:它由这样一些阶段组成:这些阶段的工作主要依赖于源语言而与目标机无关。通常前端包括词法分析、语法分析、语义分析和中间代码生成这些阶 段,某些优化工作也可在前端做,也包括与前端每个阶段相关的出错处理工作和符 号表管理等工作。 (5)后端:指那些依赖于目标机而一般不依赖源语言,只与中间代码有关的那些阶段,即目标代码生成,以及相关出错处理和符号表操作。 (6)遍:是对源程序或其等价的中间语言程序从头到尾扫视并完成规定任务的过程。 第2 题 一个典型的编译程序通常由哪些部分组成?各部分的主要功能是什么?并画出编译程序的总体结构图。 答案: 一个典型的编译程序通常包含8个组成部分,它们是词法分析程序、语法分析程序、语义分析程序、中间代码生成程序、中间代码优化程序、目标代码生成程序、表格管理程序和错误处理程序。其各部分的主要功能简述如下。 词法分析程序:输人源程序,拼单词、检查单词和分析单词,输出单词的机内表达形式。 语法分析程序:检查源程序中存在的形式语法错误,输出错误处理信息。 语义分析程序:进行语义检查和分析语义信息,并把分析的结果保存到各类语义信息表中。 中间代码生成程序:按照语义规则,将语法分析程序分析出的语法单位转换成一定形式的中间语言代码,如三元式或四元式。 中间代码优化程序:为了产生高质量的目标代码,对中间代码进行等价变换处理。目标代码生成程序:将优化后的中间代码程序转换成目标代码程序。

端口镜像配置

的需要,也迫切需要

例如,模块1中端口1和端口2同属VLAN1,端口3在VLAN2,端口4和5在VLAN2,端口2监听端口1和3、4、5, set span 1/1,1/3-5 1/2 2950/3550/3750 格式如下: #monitor session number source interface mod_number/port_number both #monitor session number destination interface mod_mnumber/port_number //rx-->指明是进端口得流量,tx-->出端口得流量 both 进出得流量 for example: 第一条镜像,将第一模块中的源端口为1-10的镜像到端口12上面; #monitor session 1 source interface 1/1-10 both #monitor session 1 destination interface 1/12 第二条镜像,将第二模块中的源端口为13-20的镜像到端口24上面; #monitor session 2 source interface 2/13-20 both #monitor session 2 destination interface 2/24 当有多条镜像、多个模块时改变其中的参数即可。 Catalyst 2950 3550不支持port monitor C2950#configure terminal C2950(config)# C2950(config)#monitor session 1 source interface fastEthernet 0/2 !--- Interface fa 0/2 is configured as source port. C2950(config)#monitor session 1 destination interface fastEthernet 0/3 !--- Interface fa0/3 is configured as destination port. 4配置命令 1. 指定分析口 feature rovingAnalysis add,或缩写 f r a, 例如: Select menu option: feature rovingAn alysis add Select analysis slot: 1?& nbsp; Select analysis port: 2 2. 指定监听口并启动端口监听 feature rovingAnalysis start,或缩写 f r sta, 例如: Select menu option: feature rovingAn alysis start Select slot to monitor ?(1-12): 1 Select port to monitor&nb sp;?(1-8): 3

编译原理实验 (词法语法分析报告 附源代码

编译原理实验报告 ******************************************************************************* ******************************************************************************* PL0语言功能简单、结构清晰、可读性强,而又具备了一般高级程序设计语言的必须部分,因而PL0语言的编译程序能充分体现一个高级语言编译程序实现的基本方法和技术。PL/0语言文法的EBNF表示如下: <程序>::=<分程序>. <分程序> ::=[<常量说明>][<变量说明>][<过程说明>]<语句> <常量说明> ::=CONST<常量定义>{,<常量定义>}; <常量定义> ::=<标识符>=<无符号整数> <无符号整数> ::= <数字>{<数字>} <变量说明> ::=VAR <标识符>{, <标识符>}; <标识符> ::=<字母>{<字母>|<数字>} <过程说明> ::=<过程首部><分程序>{; <过程说明> }; <过程首部> ::=PROCEDURE <标识符>; <语句> ::=<赋值语句>|<条件语句>|<当循环语句>|<过程调用语句> |<复合语句>|<读语句><写语句>|<空> <赋值语句> ::=<标识符>:=<表达式> <复合语句> ::=BEGIN <语句> {;<语句> }END <条件语句> ::= <表达式> <关系运算符> <表达式> |ODD<表达式> <表达式> ::= [+|-]<项>{<加法运算符> <项>} <项> ::= <因子>{<乘法运算符> <因子>} <因子> ::= <标识符>|<无符号整数>| ‘(’<表达式>‘)’ <加法运算符> ::= +|- <乘法运算符> ::= *|/ <关系运算符> ::= =|#|<|<=|>|>= <条件语句> ::= IF <条件> THEN <语句> <过程调用语句> ::= CALL 标识符 <当循环语句> ::= WHILE <条件> DO <语句> <读语句> ::= READ‘(’<标识符>{,<标识符>}‘)’ <写语句> ::= WRITE‘(’<表达式>{,<表达式>}‘)’ <字母> ::= a|b|…|X|Y|Z <数字> ::= 0|1|…|8|9 【预处理】 对于一个pl0文法首先应该进行一定的预处理,提取左公因式,消除左递归(直接或间接),接着就可以根据所得的文法进行编写代码。 【实验一】词法分析 【实验目的】给出PL/0文法规,要求编写PL/0语言的词法分析程序。 【实验容】已给PL/0语言文法,输出单词(关键字、专用符号以及其它标记)。

相关主题