2008年5月1日星期四

InnoDB 和 MyISAM中的Blob

   最近在用的一直是Mysql的Innodb,昨天测试了一下包含Blob字段的数据的插入,真的是很惨不忍睹。在表中记录数小于2k的时候,速度还行,超过2k速度就下降,从470/s持续的下降,在表中有1w条记录的时候速度就只有240/s了。到5w的时候,就只有178/s了。到20w的时候,则是130/s。后来换成了Myisam,基本上在200w记录的时候还能够有1000/s的速度。Myisam不支持事务,速度快,这些都是很早就知道的。只是不知道会有这么大的差距。也不清楚是不是innodb的什么设置我没有设置正确。现在从性能上考虑,要采用MyISAM了。而不能保证事务的这个现实,需要在APP上进行处理了。这个差距真的是太惊人了。
     不过如果没有Blob等字段,只有简单类型的话,InnoDB还好了。性能还说的过去。并且关键的是不会因为表中的有一些数据而导致性能急剧下降。

DBCP

DBCP是apache下面的一个开源的数据库连接池,谈谈几个经验
1 Connection出问题后的释放
       Connection建立成功后,如果在某次操作的时候,连接本身出现异常,可能需要废弃掉这个连接,创建新连接。当然对于mysql,支持autoReconnect的则不存在问题(Connection本身不需要废弃),但是如果不支持autoReconnect的driver,就存在这个问题。DBCP中使用的是PoolableConnection,关闭的时候,判断PoolableConnection是否close,因为这个PoolableConnection上还有代理,所以不会重复关闭,那么就判断底层的Connection的isClosed是否为true,如果是true,那么就会丢弃这个连接。JDK中Connection接口的isClosed只在Connection.close被调用后为true,在Driver的实现上,产生和数据库连接的异常后,Driver必须要自己调用close方法,才能保证这个地方让DBCP去丢弃连接。另外就是在close的时候,还调用了ConnectionFactory的passiveObject方法,这个地方出异常也会导致连接被丢弃掉。
2 关于Idle
      默认DBCP的minIdle和maxIdle都是-1,设置以后的话,对于maxIdle,如果maxIdle小于maxActive,那么在调用returnObject的时候,如果当前的idle已经等于maxIdle了,会释放掉这个连接。
3 evict
     如果设置了evict的time,那么会启动一个evictor的线程,这个是对idle的object进行检查的。基本上的逻辑是验证idle的object,删除idle超时的object,然后要保证idle的数量到达minIdle的值。

LoadRunner lrs_send发送数据

LoadRunner的lrs_一族的函数是socket操作的函数,可以方便的创建、释放socket,并通过创建的socket收发数据。socket发送的数据可以是固定的数据,也可以是在buffer中使用param来使得发送的内容具有动态 性。那么如果这个param是从用户自定义函数中返回的,那么存在一个没有办法释放的问题。那么有没有什么更好的办法呢。一个办法是通过加载dll,然后传入一个char[]来获取生成的信息,然后把这个信息写给一个buffer,然后发送这个buffer,还有一个方式就是直接发送char[]中的内容,第一种方法要使用lrs_save_param或者lrs_save_param_ex来保存数据到param中,但是我自己没有实验成功过。第二种可以通过lrs_set_send_buffer调用使得发送的buffer就是我们的char[].那么在这个函数调用后,lrs_send中的第二个参数,也就是buffer的名称就没有意义了。

LoadRunnder 使用外部的动态链接库

在LoadRunner中使用外部的动态链接库有两种方式,一种是通过Param,另外一个就是直接加载并且使用。开始看到网上有人说是用dll中的函数的话,返回不能是字符串,另外就是参数是字符串的也是只读的,不能更改。这样的话,调用dll就真的是限制太大了。不过后来测试发现,不存在上面两个限制的。 

通过Param来使用 外部的dll,要设置Param的类型是User Defined Function,设置dll的路径和方法名称,这个方法貌似是不能有参数的。这样就可以使用了。不过对于这样的方式,Param的值是从函数返回的,返回字符串就比较麻烦了。因为返回字符串,除非是常量字符串,否则都要new(malloc)那么没有地方去释放。 

另外一种方式就是类似于C的写法了,直接使用lr_load_dll加载动态链接库,然后直接使用动态链接库中的函数,不过这里面要注意一点,如果函数返回值不是int,要事先声明一下,就是要在代码头部写 extern char * yourFunc();就可以使用了。开始在写C的时候,忘了C必须在代码前定义变量,而不能在代码中定义,然后编译出错,看看是写在Action中,以为有什么限制,很是不解,后来是在另外一个同事那里又试的时候,想到的。唉。基础都忘了。另外就是如果需要dll中产生以下数据,传递接收的buffer给函数,在dll外部分配好空间。这很重要,因为dll内部分配的内存在外部是没有办法直接释放的,因为EXE和DLL,也包括多个dll都是由自己独立的堆的!!!

上帝会保佑我们的--来个技术无关的。

一个人溺水,等上帝来救他。来了一艘船要救他,他说不,上帝回来救我的,船走了。后来又有两艘船路过要救他,被他用同样的理由拒绝了,结果这个人被淹死了。当他碰见上帝时,他说,上帝啊,我是这么的信仰你,你为什么没有来救我呢?上帝说,我已经派了三艘船去救你,你都拒绝了,上帝也没有办法救你了! 

Eclipse下XmlBuddy和jpdl-gpd的冲突

近日想学习一下jBPM,看到有一个插件可以图形化的进行流程的编辑,就下载了一个。就是jbpm中的jpdl-gpd。上次在兄弟公司看到他们的技术人员在介绍jbpm在他们那边的应用。上周就下载了jbpm,似乎现在的版本比较新了一点儿。以前还有一个starter-kit,现在貌似没有了。自己下载好了以后,这个编辑流程的差价也装好了,可是就是工作不正常。也不知道具体是什么原因。奇怪。 今天偶然发现是xmlBuddy和这个gpd有冲突,想来真是奇怪。不过也不知道Eclipse的工作的原理。只能是先禁用XmlBuddy了。或者说是可以通过怎样的设置使得两者共存?有时间自己去研究一下吧。在网上搜了下,只是看到大家说gpd不正常,我看到他们写的自己的eclipse的配置,都是有xmlbuddy的。自己只是能告诉他们原因是冲突,但是,暂时还是没有solution。谁知道的话也告诉我一下。

VISTA下的Manifest文件,提升权限

VISTA下的exe文件如果包含了Manifest文件,并且Manifest文件中指明需要管理员权限,那么会弹出UAC的框让用户确认。下面是这个Manifest文件中关于这个部分的xml代码。 

<?xml version="1.0" encoding="utf-8"?> 
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> 
    <security> 
      <requestedPrivileges> 
        <requestedExecutionLevel   level="requireAdministrator"   uiAccess="false" /> 
      </requestedPrivileges> 
    </security> 
  </trustInfo> 
</assembly> 

编程常犯的错误

自己写程序也写了不短的日子了。这个blog就列出来自己遇到的常犯的错误吧。也算是给自己一个记录,一个提醒。 

        集合中的元素的删除。 

                for(int i=0; i < container.length; i++){ 

                        if(....){ 

                                   container.delete(i); 

                       } 

              } 

            在循环中可能删除多个复合条件的元素。这样的写法就错了。可以改写成 

                for(int i=container.length; i > 0; i--){ 

                        if(....){ 

                                   container.delete(i--); 

                       } 

              }            



             此外就是使用iterator在遍历的时候,直接用容器的delete或者remove方法删除iterator,然后没有跳出循环,继续遍历的话,也是错误的。在java中,可以使用iterator本身的remove。而c++的stl中没有对应的处理办法。如果不使用iterator的remove方法或者不能使用,那么循环中可以考虑如下的写法 

      for(Iterator iter = container.begin(); iter != container.end(); ){ 

             if(...){ 

                   Iterator tmpIter = iter; 

                    tmpIter++; 

                    container.remove(iter); 

                   iter = tmpIter; 

            } 

          else{ 

                iter++; 

         } 

      } 

         C/C++ 中分配内存 

         void AllocSpace(char *p){ 

                p = new char[1024]; 

         } 

        p只是一个局部变量,这样的分配只是导致内存泄露。 

        完成这个意图的写法可以有两种 

        char * AllocSpace(){ 

                return new char[1024]; 

        } 

      或者 

      void AllocSpace(char **p){ 

               *p = new char[1024]; 

        } 

      

    对于delete的误解 

                char *p = new char[1024]; 

               delete p; 

              if(NULL != p){ 

             } 

           delete(free) 只是释放指针指向的内存,并不会使得指针本身的值为NULL 

VISTA中的变化

无语啊无语。这变化让程序结构都要变化了。 

Vista Challenge I (session isolation) 
We spend about 4 months on R&D and final got it working with some support of the MS helpdesk. 

Vnc and all remote control softwares are having trouble withy the new Vista security model. In de old model, winlogon was always running in the same session as the services, session0 



While in the new model, the winlogon run in the same session as the desktop. 



The isolation of the session0, now only used for services, prevent winvnc in service mode to access session X and no interaction with the desktop in session X is possible. Using the service, you can't logon or capture the desktop. 

Running winvnc in application mode ( started manual in sessionX) all seems to work as long as you don't logoff or use any system application that popup the UAC. 

Solution I 
Winvnc need to split exe in a service and a application part. When we run the winvnc_service in session0 and let the service start winvnc_app in the sessionX, winvnc_app can communicate with the desktop and control the mouse and keyboard. 

An other problem is that sessionX have different desktop, let's take a closer look how desktop exist in Vista. (This model was already partly in use on XP, for the "Fast user switching", remember the black screen you had with VNC after switching user) 

- Session 0 

|  | 

|   ---- WinSta0 (interactive window station) 

|  |   | 

|   |  ---- Default (desktop) 

|  |   | 

|   |   ---- Disconnect (desktop) 

|  |   | 

|   |   ---- Winlogon (desktop) 

|   | 

|   ---- winvnc Service (non-interactive window station) 

|   |   | 

|   |   ---- Default (desktop) 

|   | 

- Session 1 

|  | 

|   ---- WinSta0 (interactive window station) 

|   |   | 

|   |   ---- Default (desktop)(1**) 

|   |   | 

|   |   ---- Disconnect (desktop) 

|   |   | 

|   |   ---- Winlogon (desktop) 

|   | 

- Session 2 

| | 

| ---- WinSta0 (interactive window station) 

| | | 

| | ---- Default (desktop) 

| 

| | ---- Disconnect (desktop) 

| |   | 

| | ---- Winlogon (desktop)  (2**) 

The service need to check the console desktop and session. 
If the console desktop is (1**) the service need to start winvnc_app in session1 on the default desktop, the default desktop is the normal desktop where your application run on. 
If the console desktop is (2**) the service need to start winvnc_app in session2 on the winlogon desktop, this is the secure desktop used to logon. 
To avoid access problems, you best start the winvnc_app with the same security context as the desktop he is started in. 
Default Desktop --> user 
Winlogon Desktop -> local system 
Createprocessasuser() allow the service to start the exe in the correct security context. 

This method would be a complete solution to support Fast user switching and access on PC's running RDP. But Vista has other nasty tricks to prevent applications to control a desktop. 

Vista Challenge II (elevation and UAC) 
Security elevation 
From previous MS OS's you know that permissions where based on users. If you logged on as administrator, you could simple click on an executable to start it. As normal user, you needed execute permission on that executable. 
In Vista applications have a "security elevation". 
Low: Iexplorere started as administrator runs in low security elevation, this block iexplorer access to many system sources and applications. 
Normal: A standard application, word pad, run in normal elevation. 
High: For system utilities, like service manager.. 
The elevation block some interaction from lower elevated application to higher. For Vnc the most important is that sendinput() is blocked. You can't control an application running in higher elevation then the elevation winvnc is running. 
If you start the service manager from within VNC, VNC mouse clicks get locked by the elevated "service manager" application. 
If the remote users minimize the "service manager" you have full access again, but remote you are blocked. 

UAC 
In older OS version you could simple start a "system application" with a double click, in Vista the UAC jumps in. The UAC popup a "OK" window in the secure desktop, he temporal switch to the winlogon desktop and ask your permission to execute that program. 
The problem for VNC is that your winvnc_app running in the default desktop has no permission to access the winlogon desktop, remote the whole desktop lock and you need to ask the remote user the press the ok button to continue. 

Solution II 
UAC problem can be solved by restarting the winvnc_app in the winlogon desktop, to press OK, and the restarting it again in the default desktop. 
Great, but now VNC lock because the "system app" has focus and sendinput() is locked because it run in "high" elevation. 
No problem, should you think, we just add a manifest to the winvnc_app and tell that it need to start in "high" elevation, then it can control all application. ( elevation high has access to all elevation >=high) 
This works, when you manual click on the winvnc_app.exe, it popup the UAC and you press OK, but when you start it from the service you get a permission denied, CreateprocessAsUser(CPAU) is not allowed to start elevated application...... 

Don't play with the manifest, the only way is to play with the token passed to CPAU and pass the full elevated token, then winvnc_app start elevated and you have access. 

Vista Challenge II ( Ctrl-alt-del) 
In previous OS's you could send a message from a service in the winlogon desktop to simulate the CAD sequence... 
PostMessage(HWND_BROADCAST,WM_HOTKEY,0,MAKELPARAM(MOD_ALT|MOD_CONTROL,VK_DELETE)); 
This does not work in Vista.... 

Solution III 
We are testing, it has to be possible, the osk (on screen keyboard) can simulate the sequence. The osk use undocumented functions, the "winlogon IPC API"... 
As workaround, you can use the on screen keyboard. When you press the left/down icon it popup the keyboard and you can that to simulate CAD... 
Problem solved , Ctrl-Alt-Del can be made with a separate exe "cad.exe" 

Windows Vista 交互式服务编程

Windows Vista 对快速用户切换,用户账户权限,以及服务程序所运行的会话空间都作了很大的改动,致使一些原本可以工作的程序不再能够正常工作了,我们不得不进行一些改进以跟上 Vista 的步伐。 
我们的软件在Windows NT/2000/XP/Vista 系统中安装了一个系统服务,这个服务负责以 SYSTEM 权限启动我们的主程序。我们的主程序启动后会在系统托盘添加一个图标,点击此图标可以弹出控制菜单,通过这个菜单也可以激活配置程序首选项的对话框。在 Windows NT/2000/XP 下我们的程序都可以正常工作。哦不,当 XP 具备了快速用户切换功能的时候我们的问题已经出现了。XP 启动后我们以用户 A 登录,我们的图标出现在系统托盘,一切工作都正常,可当我们使用快速用户切换,切换到用户B后(用户A此时也是已登录状态,并没有注销),虽然用户B已经是本地控制台会话(Session 属性为 Console)但我们的图标已经无法出现了,自然菜单和对话框更无从谈起了。我们的程序是和本机控制台桌面相关的,这种情况无疑是个缺陷。再来看一下在 Vista 平台是怎么样吧,系统启动后以用户A登录,我们的图标更本就没有出现,查看进程管理器中的进程列表发现我们的程序已经启动了,当我们从远端检查我们的服务,发现已经正常工作,尝试远程登录我们的服务,Vista 会在本机控制台弹出一个消息框,提示有交互式服务消息,是否查看这个消息,点击立刻查看发现切换到另外一个桌面去了。 
于是开始分析这种情况发生的原因。在 Windows NT/2000 中系统服务进程和本机控制台交互式登录的用户都运行于Session0 中,默认用户桌面运行于 WinSta0 窗口站,所以我们的程序由服务程序启动时依然是和本机用户处于同一个Session中,即使在某些情况下出现不能弹出对话框或者无法添加系统托盘图标的情况也只需要修改一下进程桌面到 WinSta0\Default 就可以了(可以参考 MSDN 中 OpenInputDesktop, SetThreadDesktop 等API的说明)。 
XP为我们带来了快速用户切换,也让我们所采用的软件架构问题浮现出来。当我们快速切换到用户B的时候,用户A仍然在会话中(Session0),而用户B则处于新启动的会话中(Session1或者其他),此时服务程序和本机控制台程序就不在处于同一会话了,OpenInputDesktop,SetThreadDesktop 等API的工作范围仅限于本Session,用户A没有退出,Session0也依然存在但是已经是 Disconnected 状态,当进程所处的Session是 Disconnected 状态的时候调用 OpenInputDesktop 会返回错误“无效的API”。进程及线程所属的Session 是由他们的Token 结构中的 TokenSessionId 决定的(参见MSDN中SetTokenInformation 和 TOKEN_INFORMATION_CLASS的说明),我尝试以微软提供的相关API修改运行中的进程和线程的TokenSessionId 信息从而达到修改桌面环境的目的,到目前还没有成功过(或许可以尝试参考RootKit 技术,不过即使修改成功到底能不能实现我们的需求也不确定)。我们的进程无法跨越Session的界限,自然无法与当前活动的另外一个Session中的桌面交互了,L 。 
Vista中又是如何的一番景象呢?处于安全方面及其他因素的考虑,Vista以及将所有的服务程序置于Session0中,而为本机第一个交互登录的用户创建了Session1,快速切换到用户B后则是 Session2,无论是本机登录的用户,快速切换后的用户,还是远程桌面登录的用户再也没有谁和服务进程处于同一个Session中了,我们的程序还运行在Session0中,自然我们的托盘图标是没有用户能看到了。事实上这个图标还是可以出现的。Session0因为不是一个交互式会话所以没有象其他用户环境初始化的时候一样启动Explorer程序,但是我们开始可以手工启动他,在Session0中启动 Explorer 后任务栏出现后我们还是看到了我们的图标(具体启动Explorer的方法我们不在此文中讨论),菜单、对话框也可以使用。 
既然我们的程序必须运行在Session0而我们又没有办法把我们的图标、对话框一下子就抛到隔壁Session的用户桌面上去,只能想其他的办法了。微软也不提倡我们这种服务程序直接提供GUI与用户直接交互的方式,而他们建议使用C/S架构,Client/Server之间用Socket/Pipe/RPC等方式通讯,这样我们只要把Client整个进程放到用户Session去和用户交互,然后将配置信息等内容通过上述途径传递给Server,服务端在作出相应的响应即可。 
把GUI分离出来并不是那么困难,然后在以前直接调用的地方加上一个通过Pipe通讯的接口,这样GUI(Client)的运行就可以灵活的掌握了。 
最初我想把用户界面程序放到 Startup(启动)中随用户登录自动启动。这样当用户A和B都登录后将有两个用户界面程序在运行,而我们的服务只是和当前活动的控制台登录用户交互,所以这样并不符合需求。 
接下来我们需要看看如何判定当前的活动Session是哪个,然后如何在这个活动Session中启动我们的用户界面程序了。 
微软从XP/2003开始为我们提供了一套Windows Terminal Service 的相关API,这些API都以WTS开头(请安装MSDN2005以查阅相关说明),要获得活动Session也不止一个途径,最简单的就是直接使用 
DWORD WTSGetActiveConsoleSessionId(void); 
来获得活动Session Id 。要在程序中使用这些API需要最新的Platform SDK(如果你正在使用Visual Studio 2005那么它已经具备了相关头文件和库文件可以直接使用了),如果你在使用VC++ 6.0 你也没有或者不打算安装最新的SDK那么你可以直接使用LoadLibrary() 装载wtsapi32.dll然后使用GetProcAddress()获得相关函数的地址以调用它们。我们获得了活动SessionId后就可以使用 
BOOL WTSQueryUserToken( 
ULONG SessionId, 
PHANDLE phToken 
); 
来获取当前活动Session中的用户令牌(Token),有了这个Token我们的就可以在活动Session中创建新进程了, 
BOOL CreateProcessAsUser( 
HANDLE hToken, 
LPCTSTR lpApplicationName, 
LPTSTR lpCommandLine, 
LPSECURITY_ATTRIBUTES lpProcessAttributes, 
LPSECURITY_ATTRIBUTES lpThreadAttributes, 
BOOL bInheritHandles, 
DWORD dwCreationFlags, 
LPVOID lpEnvironment, 
LPCTSTR lpCurrentDirectory, 
LPSTARTUPINFO lpStartupInfo, 
LPPROCESS_INFORMATION lpProcessInformation 
); 
将我们获得的Token作为此API的第一个参数即可,你可以先尝试一下运行一个notepad.exe看看,怎么样?你可以在控制台桌面上看到新进程了。再查看一下进程列表,该进程的用户名是当前控制台登录的用户。可是这里我们又遇到一个问题,我们需要收集当前交本机互式登录用户的一些信息,而有些操作需要很高的权限才能完成,而Vista下即使是Administraotrs用户组成员默认也是以Users权限启动进程的,所以我们创建的新进程只有Users权限,无法完成一些操作,当然我们可以使用Vista所提供的UI来询问用户以提升至管理员权限,可有些操作甚至是管理员Token也无法完成的,而且需要用户确认实在在易用性上大打折扣,所以我决定在活动Session中以SYSTEM权限启动我们的用户交互程序。显然 WTSQueryUserToken() 是不好用了。 
之前,我们提到过进程所属的Session是由进程Token中的TokenSessionId来决定的,那么我们是不是可以复制服务进程的Token然后修改其中的TokenSessionId,从而在用户桌面上创建一个具有SYSTEM权限的新进程呢?答案是肯定的。一下是实现这个操作的代码,为了缩小篇幅我删除了异常处理代码 
HANDLEhTokenThis = NULL; 
HANDLEhTokenDup = NULL; 
HANDLEhThisProcess = GetCurrentProcess(); 
OpenProcessToken(hThisProcess, TOKEN_ALL_ACCESS, &hTokenThis); 
DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED,NULL, SecurityIdentification, TokenPrimary, &hTokenDup); 
DWORDdwSessionId = WTSGetActiveConsoleSessionId(); 
SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD)); 

STARTUPINFOsi; 
PROCESS_INFORMATION pi; 
ZeroMemory(&si, sizeof(STARTUPINFO)); 
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 
si.cb = sizeof(STARTUPINFO); 
si.lpDesktop = "WinSta0\\Default"; 

LPVOIDpEnv = NULL; 
DWORDdwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE; 

CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE); 

CreateProcessAsUser( 
              hTokenDup, 
              NULL, 
              (char *)"notepad", 
              NULL, 
              NULL, 
              FALSE, 
              dwCreationFlag, 
              pEnv, 
              NULL, 
              &si, 
              &pi); 
  


到这里我们的大部分工作已经完成了,我们还需要做的就是监控活动Session的变化,就是用户的登录、注销、快速切换。WTS系列API以及为我们提供了具备这些能力的API了,大致可以用一下几种方法实现: 
1.              设置一个定时器,使用WTSGetActiveConsoleSessionId()轮询活动桌面id,当检测到变化的时候让用户交互程序的前一个实例退出,在新活动Session中创建新进程。 
2.              使用WTSRegisterSessionNotification()函数注册一个窗口来接收WTSSESSION_NOTIFICATION消息,来判断Session变化。 
3.              使用 WTSEnumerateSessions枚举所有Session然后根据返回的WTS_SESSION_INFO结构中的State成员来判断Session状态,找到处于 Active状态的Session. 
结合你的其他需求选择其中之一,然后作出响应就可以了。