Windows核心编程学习文档

一、CreateProcess

CreateProcess为启动进程函数:下面已启动NOTEPAD记事本进程为例阐述CreateProcess,函数的用法;

 

lpApplicationName:可执行文件的名称,(可不包含路径)。如果该参数为NULL,那必须

在参数lpCommandLine中传递文件名称。

lpCommandLine:传递给执行的文件的命令行参数。如果lpApplicationName为NULL,那必须在该参数中指定,例如:"notepad.exe readme.txt" 。

参数lpApplicationName、lpCommandLine对参数设置都可以启动.txt文件;但对lpApplicationName的设置只能完成起启动.txt文件这个事件,不能针对的启动某一个XXX.txt文件。

对参数一lpApplicationName设置

#include"stdafx.h"

#include<stdio.h>

#include<conio.h>

#include<windows.h>

 

int _tmain(intargc, _TCHAR* argv[])

{

    TCHARszPath[]=TEXT("C:\\Windows\\system32\\NOTEPAD.EXE");  //记事本安装目录

    STARTUPINFO si ={sizeof(si)}; 

    PROCESS_INFORMATIONpi; 

    BOOL bStatus = CreateProcess(szPath,NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);  

    return 0;

}

怎可以完成启动一个空的.txt文件;

对参数二lpCommandLine设置

int _tmain(intargc, _TCHAR* argv[])

{

    TCHAR lpPath[] =TEXT("notepad.exe readme.txt"); 

    STARTUPINFO si ={sizeof(si)}; 

    PROCESS_INFORMATIONpi; 

    BOOL bStatus =

CreateProcess(NULL,lpPath,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);  

    return 0;

}

则打开readme.txt文件;既能打开指定文件名的相应文件;

CreateProcess:的最后两个参数的设置

lpStartupInfo

指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。

当Wi n d o w s创建新进程时,它将使用该结构的有关成员。大多数应用程序将要求生成的应用程序仅仅使用默认值。至少应该将该结构中的所有成员初始化为零,然后将 c b成员设置为该结构的大小:

STARTUPINFO si = {sizeof(si)}; 

CreateProcess(szPath,NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi); 

如果未能将该结构的内容初始化为零,那么该结构的成员将包含调用线程的堆栈上的任何

无用信息。将该无用信息传递给CreateProcess,将意味着有时会创建新进程,有时则不能创建新进程,完全取决于该无用信息。有一点很重要,那就是将该结构的未用成员设置为零,这样,CreateProcess就能连贯一致地运行。不这样做是开发人员最常见的错误。

lpProcessInformation

指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。

创建新进程可使系统建立一个进程内核对象和一个线程内核对象。在创建进程

的时候,系统为每个对象赋予一个初始使用计数值1。然后,在c r e a t e P r o c e s s返回之前,该函数打开进程对象和线程对象,并将每个对象的与进程相关的句柄放入 P R O C E S S _ I N F O R M AT I O N结构的h Pr o c e s s和h T h r e a d成员中。当Cr e a t e P r o c e s s在内部打开这些对象时,每个对象的使用计数就变为2。

这意味着在系统能够释放进程对象前,该进程必须终止运行(将使用计数递减为 1),并且父进程必须调用C l o s e H a n d l e(再将使用计数递减1,使之变为0)。同样,若要释放线程对象,该线程必须终止运行,父进程必须关闭线程对象的句柄。

注意必须关闭子进程和它的主线程的句柄,以避免在应用程序运行时泄漏资源。当然,当进程终止运行时,系统会自动消除这些泄漏现象,但是,当进程不再需要访问子进程和它的线程时,编写得较好的软件能够显式关闭这些句柄(通过调用C l o s e H a n d l e函数来关闭)。不能关闭这些句柄是开发人员最常犯的错误之一。

#include"stdafx.h"

#include<stdio.h>

#include<conio.h>

#include<windows.h>

 

int _tmain(intargc, _TCHAR* argv[])

{

    TCHARszPath[]=TEXT("C:\\Windows\\system32\\NOTEPAD.EXE");

    TCHAR lpPath[] =TEXT("notepad.exe readme.txt"); 

    STARTUPINFO si ={sizeof(si)}; 

    PROCESS_INFORMATIONpi; 

    BOOL bStatus =CreateProcess(szPath,lpPath,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);  

    CloseHandle(pi.hProcess);

    CloseHandle(pi.hThread);

    return 0;

}

所以代码重要添加

    CloseHandle(pi.hProcess);

    CloseHandle(pi.hThread);

二、Windows线程

1 实现多线程

1 实现卖火车票的小程序,两个线程,相继使tickets不断减小,最后到0

#include"stdafx.h"

 

//这是2个线程模拟卖火车票的小程序

#include<windows.h>

#include<iostream>

usingnamespacestd;

 

DWORD WINAPI Fun1Proc(LPVOID lpParameter);//thread data

DWORD WINAPI Fun2Proc(LPVOID lpParameter);//thread data

 

int index=0;

inttickets=10;

HANDLE hMutex;

void main()

{

    HANDLE hThread1;

    HANDLE hThread2;

    //创建线程

 

    hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

    hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

    CloseHandle(hThread1);

    CloseHandle(hThread2);

 

    //创建互斥对象

    hMutex=CreateMutex(NULL,TRUE,TEXT("tickets"));

    if (hMutex)

    {

        if (ERROR_ALREADY_EXISTS==GetLastError())

        {

            cout<<"only one instance can run!"<<endl;

            return;

        }

    }

    WaitForSingleObject(hMutex,INFINITE);

    ReleaseMutex(hMutex);

    ReleaseMutex(hMutex);

 

    Sleep(4000);

}

//线程1的入口函数

DWORD WINAPI Fun1Proc(LPVOID lpParameter)//thread data

{

    while (true)

    {

        ReleaseMutex(hMutex);

        WaitForSingleObject(hMutex,INFINITE);

        if (tickets>0)

        {

            Sleep(1);

            cout<<"thread1 sell ticket :"<<tickets--<<endl;

        }

        else

            break;

        ReleaseMutex(hMutex);

    }

 

    return 0;

}

//线程2的入口函数

DWORD WINAPI Fun2Proc(LPVOID lpParameter)//thread data

{

    while (true)

    {

        ReleaseMutex(hMutex);

        WaitForSingleObject(hMutex,INFINITE);

        if (tickets>0)

        {

            Sleep(1);

            cout<<"thread2 sell ticket :"<<tickets--<<endl;

        }

        else

            break;

        ReleaseMutex(hMutex);

    }

 

    return 0;

}

函数:

CreateThreadhttp://baike.baidu.com/view/1191444.htm

 

当用_beginThread来创建,而用CloseHandle来关闭线程时,这时复制的全局结构就不会被释放了,这就有了内存的泄漏。这就是很多资料所说的内存泄漏问题的真正的原因。

其实,可以不用_beginThread和_endThread这一对函数。如果用CreateThread函数创建,用CloseHandle关闭,那么,与C有关的库就会用全局的,它们会引起冲突。

CloseHandlehttp://baike.baidu.com/view/1288756.htm

CloseHandle包括文件、文件映射、进程、线程、安全和同步对象等。涉及文件处理时,这个函数通常与vb的close命令相似。应尽可能的使用close,因为它支持vb的差错控制

CreateMutex:http://baike.baidu.com/view/1285853.htm

找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。CreateMutex()函数可用来创建一个有名或无名的互斥量对象;

fInitialOwner参数(第二个参数)用于控制互斥对象的初始状态。如果传递FALSE(这是通常情况下传递的值),那么互斥对象的ID和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号。如果为fInitialOwner参数传递TRUE,那么该对象的线程ID被设置为调用线程的ID,递归计数器被设置为1。由于ID是个非0数字,因此该互斥对象开始时不发出通知信号。

2互斥对象:

http://baike.baidu.com/view/1769610.htm

互斥对象(mutex)内核对象能够确保线程拥有对单个资源的互斥访问权。实际上互斥对

象是因此而得名的。互斥对象包含一个使用数量,一个线程ID和一个递归计数器。

  互斥对象是系统内核维护的一种数据结构,它保证了对象对单个线程的访问权

  互斥对象的结构:包含了一个使用数量,一个线程ID,一个计数器

  使用数量是指有多少个线程在调用该对象,线程ID是指互斥对象维护的线程的ID

计数器表示当前线程调用该对象的次数;

WaitForSingleObject:获得互斥对象

WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。

ReleaseMutex:释放互斥对象:如果一个线程拥有了一个互斥对象后,当该线程运行完成后就要释放该互斥对象,不然其他的线程得不到互斥对象则无法运行。

Sleephttp://baike.baidu.com/view/66232.htm

C++中头文件<windows.h>下的函数

  作用:延时,程序暂停若干时间。

  时间,就是他的参数,单位是毫秒。

  例如:

  Sleep(500) ; //注意第一个字母是大写。

就是到这里停半秒,然后继续向下执行。

代码来源:

http://127.0.0.1:47873/help/1-11712/ms.help?method=page&id=D6FB159A-EACA-4130-A51A-F95D62F71485&product=VS&productversion=100&locale=en-US&topiclocale=EN-US&topicversion=100&SQM=2

2 实现主线程调用子线程的功能;

#include"stdafx.h"

#include<clocale>

#include<cstdio>

#include<locale>

#include<process.h>

#include<windows.h>

#include<iostream>

 

#defineNUM_THREADS 2

usingnamespacestd;

 

unsigned__stdcallRunThreadA(void *params);

unsigned__stdcallRunThreadB(void *params);

 

BOOL localeSet = FALSE;

HANDLE printMutex = CreateMutex(NULL, FALSE, NULL);

 

int main()

{

    HANDLEthreads[NUM_THREADS];

 

    unsigned aID;

    threads[0] =(HANDLE)_beginthreadex(

        NULL, 0,RunThreadA, NULL, 0, &aID);

 

    unsigned bID;

    threads[1] =(HANDLE)_beginthreadex(

        NULL, 0,RunThreadB, NULL, 0, &bID);

 

    WaitForMultipleObjects(2,threads, TRUE, INFINITE);

 

    printf_s("[Thread main] Per-thread locale is NOTenabled.\n");

    printf_s("[Thread main] CRT locale is set to\"%s\"\n",

        setlocale(LC_ALL,NULL));

    printf_s("[Thread main] locale::global is set to\"%s\"\n",

        locale().name().c_str());

    system("pause");

    CloseHandle(threads[0]);

    CloseHandle(threads[1]);

    CloseHandle(printMutex);

 

    return 0;

}

 

unsigned__stdcallRunThreadA(void *params)

{

    _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);

    setlocale(LC_ALL,"french");

    localeSet =TRUE;

 

    WaitForSingleObject(printMutex,INFINITE);

    printf_s("[Thread A] Per-thread locale is enabled.\n");

    printf_s("[Thread A] CRT locale is set to\"%s\"\n",

        setlocale(LC_ALL,NULL));

    printf_s("[Thread A] locale::global is set to\"%s\"\n\n",

        locale().name().c_str());

    ReleaseMutex(printMutex);

 

    return 1;

}

 

unsigned__stdcallRunThreadB(void *params)

{

    while (!localeSet)

        Sleep(100);

 

    WaitForSingleObject(printMutex,INFINITE);

    printf_s("[Thread B] Per-thread locale is NOTenabled.\n");

    printf_s("[Thread B] CRT locale is set to\"%s\"\n",

        setlocale(LC_ALL,NULL));

    printf_s("[Thread B] locale::global is set to\"%s\"\n\n",

        locale().name().c_str());

    ReleaseMutex(printMutex);

 

    return 1;

}

(1)比较_beginthreadexCreateThread

关于_beginthreadexCreateThread的区别我就不做说明了,这个很容易找到的。我们只要知道一个问题:_beginthreadex是一个C运行时库的函数,CreateThread是一个系统API函数,_beginthreadex内部调用了CreateThread。只所以所有的书都强调内存泄漏的问题是因为_beginthreadex函数在创建线程的时候分配了一个堆结构并和线程本身关联起来,我们把这个结构叫做tiddata结构,是通过线程本地存储器TLS于线程本身关联起来。我们传入的线程入口函数就保存在这个结构中。

2)比较WaitForMultipleObjectsWaitForSingleObject

参考:http://blog.sina.com.cn/s/blog_9d83957001012ngk.html

WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是 WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给 WaitForSingleObject,那么返回值将是WAIT_FAILED(若要了解详细信息,可调用GetLastError)。

下面这个函数WaitForMultipleObjectsWaitForSingleObject函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已通知状态;

WaitForMultipleObjectshttp://baike.baidu.com/view/1424795.htm

多线程和区域设置参考:http://msdn.microsoft.com/zh-cn/library/ms235302.aspx

总结: 如果A线程通过调用 _configthreadlocale(_ENABLE_PER_THREAD_LOCALE) 启用每线程区域设置。则他不会受到其他线程的区域设置的影响,也不会影响其他线程的区域设置;调用 locale::global 将同时更改标准 C++ 库和 C 运行库的区域设置。 但是,调用 Setlocale

 仅更改 C 运行库的区域设置;标准 C++ 库不受影响。

3 VC多线程实现线程间通信

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。

进程是应用程序的执行实例;

线程是一个独立的执行流;

SuspendThread线程挂起;当线程做完任务或者现在想暂停线程运行,就需要使用SuspendThread来暂停线程的执行,当然恢复线程的执行就是使用ResumeThread函数了。

void ThreadSuspendResume(void)

{

::SuspendThread(m_hThread);

Sleep(10);

ResumeThread(m_hThread);

}

如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。

结束线程

  终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread,DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:

////////////////////////////////////////////////////////////////

//////CtestView message handlers

/////Set to True to end thread

Bool bend=FALSE;//定义的全局变量,用于控制线程的运行;

//The Thread Function

UINT ThreadFunction(LPVOID pParam)//线程函数

{

 while(!bend)

 {

Beep(100,100);

  Sleep(1000);

 }

 return 0;

}

/////////////////////////////////////////////////////////////

CwinThread *pThread;

HWND hWnd;

Void CtestView::OninitialUpdate()

{

 hWnd=GetSafeHwnd();

 pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程

 pThread->m_bAutoDelete=FALSE;//线程为手动删除

 Cview::OnInitialUpdate();

}

////////////////////////////////////////////////////////////////

Void CtestView::OnDestroy()

{

 bend=TRUE;//改变变量,线程结束

 WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束

 delete pThread;//删除线程

 Cview::OnDestroy();

Beep:http://baike.baidu.com/view/1022371.htm

AfxBeginThread:http://baike.baidu.com/view/994702.htm

 

 

CwinThread:http://baike.baidu.com/view/1856410.htm

4 线程之间的通信

  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。

5事件对象

http://baike.baidu.com/view/2309178.htm

事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。

事件对象和互斥对象(参见百度百科互斥对象http://baike.baidu.com/view/1769610.htm),一样都属于内核对象,它包含一个使用计数,一个用于标识该事件是一个自动重置还是一个人工重置的布尔值,和另一个用于指定该事件处于已通知状态还是未通知状态的布尔值。

6 线程之间的同步

前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。

7 临界区(critical section

http://baike.baidu.com/view/1237110.htm

临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:

 

#include "afxmt.h"

int array[10],destarray[10];

CCriticalSection Section;

UINT WriteThread(LPVOID param)

{

 Section.Lock();

 for(int x=0;x<10;x++)

  array[x]=x;

 Section.Unlock();

}

UINT ReadThread(LPVOID param)

{

 Section.Lock();

 For(int x=0;x<10;x++)

  Destarray[x]=array[x];

  Section.Unlock();

}

 

  上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。

8 互斥(mutexe

互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLockCMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

 

#include "afxmt.h"

int array[10],destarray[10];

CMutex Section;

 

UINT WriteThread(LPVOID param)

{

 CsingleLock singlelock;

 singlelock (&Section);

 singlelock.Lock();

 for(

int x=0;x<10;x++)

  array[x]=x;

 singlelock.Unlock();

}

 

 

UINT ReadThread(LPVOID param)

{

 CsingleLock singlelock;

 singlelock (&Section);

 singlelock.Lock();

 For(int x=0;x<10;x++)

  Destarray[x]=array[x];

  singlelock.Unlock();

}

 

9 信号量(semaphore

  信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLockCmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。

 

/////////////////////////////////////////////////////////////////////////

Csemaphore *semaphore;

Semaphore=new Csemaphore(2,2);

HWND hWnd=GetSafeHwnd();

AfxBeginThread(threadProc1,hWnd);

AfxBeginThread(threadProc2,hWnd);

AfxBeginThread(threadProc3,hWnd);

UINT ThreadProc1(LPVOID param)

{

 CsingleLock singelLock(semaphore);

 singleLock.Lock();

 Sleep(10000);

 ::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);

 return 0;

}

UINT ThreadProc2(LPVOID param)

{

 CSingleLock singelLock(semaphore);

 singleLock.Lock();

 Sleep(10000);

 ::MessageBox((HWND)param,"Thread2 hadaccess","Thread2",MB_OK);

 return 0;

}

 

UINT ThreadProc3(LPVOID param)

{

 CsingleLock singelLock(semaphore);

 singleLock.Lock();

 Sleep(10000);

 ::MessageBox((HWND)param,"Thread3 hadaccess","Thread3",MB_OK);

 return 0;

}

10 线程调度

线程调度是指按照特定机制为多个线程分配CPU的使用权.

创建一个“挂起状态”的线程,意味着你可以在线程执行之前更改该线程的环境(比如优先级)。你可以使用ResumeThread函数来使得这个线程变为可调度状态。

DWORD ResumeThread(HANDLE hThread);

  如果该函数成功,返回线程的前一个挂起计数,否则,返回-1(0xFFFFFFFF)。

  一个线程可以被挂起多次,如果一个线程被挂起3次,那么需要呼叫ResumeThread函数来唤醒它3次,ResumeThread会“递减”指定线程的“挂起计数”。

  挂起一个线程,只要呼叫SuspendThread函数,传递一个线程句柄给它就可以了。

DWORD SuspendThread(HANDLE hThread);

  一个线程可以呼叫这个函数从而挂起另一个线程。该函数也返回线程的前一个“挂起计数”。需要注意的是,SuspendThread函数对于内核模式来说是异步的,当线程挂起之后,不会发生用户模式的执行。

想要让一个线程“睡眠”一段时间,可以呼叫Sleep函数。

VOID Sleep(DWORD dwMilliseconds);

  运行该函数后,在一个指定的时间之内,线程不会再被调度。你可以传递INFINITE该这个函数,让线程无休止地休眠下去。也可以传递一个普通的正数,让线程休眠一定的时间(以毫秒为单位)。也可以传递0,这说明该线程放弃当前的时间片,强制系统调度另一个线程。但是系统可以随时调度这个线程,也就是说这个线程不休眠。

  系统提供了一个函数,允许转而调度另一个线程。该函数是:

BOOL SwitchToThread();

  呼叫该函数使得系统查看当前是否有“饥饿”的线程,如果有,该函数立即调度饥饿线程,可能这个线程具有比较低的优先级。

  当存在可以被调度的线程的时候,该函数返回TURE,否则返回FALSE。

该函数与调用Sleep(0)的区别是,该函数允许低优先级的线程被调度。

 

三、异步I/O

http://baike.baidu.com/view/1865389.htm

异步传输常用于低速设备,每次异步传输的信息都以一个起始位开头,它通知接收方数据已经到达了,这就给了接收方响应、接收和缓存数据比特的时间;

WaitForInputIdle:http://baike.baidu.com/view/4217373.htm等待新进程完成它的初始化并等待用户输入。

调用进程可以通过WaitForInputIdle函数来等待新进程完成它的初始化并等待用户输入。这对于父进程和子进程之间的同步是极其有用的,因为CreateProcess函数不会等待新进程完成它的初始化工作。举例来说,在试图与新进程关联的窗口之前,进程应该先调用WaitForInputIdle

四、内存管理

http://read.beifabook.com/H/lianzai/l_jy/10/06/SRQB07020211/201006070209414361.shtml

 

Win32内存相关API:

Win32平台下,开发人员可以通过如下5组函数来使用内存(完成申请和释放等操作)。

  (1)传统的CRT函数(malloc/free系列):因为这组函数的平台无关性,如果程序会被移植到其他非Windows平台,则这组函数是首选。也正因为这组函数非Win32专有,而且介绍这组函数的资料俯拾皆是,这里不作详细介绍。

  (2global heap/local heap函数(GlobalAlloc/LocalAlloc系列):这组函数是为了向后兼容而保留的。在Windows 3.1平台下,global heap为系统中所有进程共有的堆,这些进程包括系统进程和用户进程。它们对此global heap内存的申请会交错在一起,从而使得一个用户进程的不小心的内存使用错误会导致整个操作系统的崩溃。local heap又被称为“private heap”,与global heap相对应,local heap为每个进程私有。进程通过LocalAlloc从自己的local heap里申请内存,而不会相互干扰。除此之外,进程不能通过另外的用户自定义堆或者其他方式动态地申请内存。到了Win32平台,由于考虑到安全因素,global heap已经废弃,local heap也改名为“process heap”。为了使得以前针对Windows 3.1平台写的应用程序能够运行在新的Win32平台上,GlobalAlloc/ LocalAlloc系列函数仍然得到沿用,但是这一系列函数最后都是从process heap中分配内存。不仅如此,Win32平台还允许进程除process heap之外生成和使用新的用户自定义堆,因此在Win32平台下建议不使用GlobalAlloc/LocalAlloc系列函数进行内存操作,因此这里不详细介绍这组函数。

  (3)虚拟内存函数(VirtualAlloc/VirtualFree系列):这组函数直接通过保留(reserve)和提交(commit)虚拟内存地址空间来操作内存,因此它们为开发人员提供最大的自由度,但相应地也为开发人员内存管理工作增加了更多的负担。这组函数适合于为大型连续的数据结构数组开辟空间。

  (4)内存映射文件函数(CreateFileMapping/MapViewOfFile系列):系统使用内存映射文件函数系列来加载.exe或者.dll文件。而对开发人员而言,一方面通过这组函数可以方便地操作硬盘文件,而不用考虑那些繁琐的文件I/O操作;另一方面,运行在同一台机器上的多个进程可以通过内存映射文件函数来共享数据(这也是同一台机器上进程间进行数据共享和通信的最有效率和最方便的方法)。

5)堆内存函数(HeapCreate/HeapAlloc系列):Win32平台中的每个堆都是各进程私有的,每个进程除了默认的进程堆,还可以另外创建用户自定义堆。当程序需要动态创建多个小数据结构时,堆函数系列最为适合。一般来说CRT函数(malloc/free)就是基于堆内存函数实现的。

http://baike.baidu.com/view/4541016.htm

五、DLL

动态链接库Dynamic Link Library或者Dynamic-link library,缩写为DLL),又称为动态链接库,是微软公司在微软视窗操作系统中实现共享函数库概念的一种实作方式。

http://baike.baidu.com/view/4541016.htm

使用动态连接库是Windows的一个很重要的特点,它使得多个Windows应用程序可以共享函数代码、数据和硬件,这可以大大提高Windows内存的利用率。

  连接时隐式引入

  最常用也最简单的方法是连接时隐式引入,这种方法是在应用程序的连接命令行中列出为动态连接库创建的引入库,这样应用程序在使用DLL的引出函数时,就如同使用静态库中的函数一样了。

  连接时显式引入

  和隐式引入一样,显式引入也是在连接时进行的,它通过把所需函数列在应用程序的模块定义文件IMPORTS语句中完成。对于在模块定义文件中定义了入口序号的DLL函数,采用引入函数名、动态连接库名和入口序号的形式,如:

  IMPORTS

  DrawBox=DllDraw.2

  如果DLL的模块定义文件没有定义引出函数的入口序号,则使用如下引入语句:

  IMPORTS

  DllDraw.DrawBox

  运行时动态引入

应用程序可以在运行时动态连接DLL函数,当需要调用DLL的引出函数时,应用程序首先装入库,并直接检索所需函数地址,然后才调用该函数。

六、监控该程序的运行

必须实现一个机制来检索当前运行的所有进程以及映射到每个进程的dll

PSAPI

PSAPI 就是指 Process Status API,中文意思就是进程状态API

http://baike.baidu.com/view/4236069.htm

进程状态API函数如下:

  EmptyWorkingSet

  EnumDeviceDrivers

  EnumPageFiles

  EnumProcesses

  EnumProcessModules

  GetDeviceDriverBaseName

  GetDeviceDriverFileName

  GetMappedFileName

  GetModuleBaseName

  GetModuleFileNameEx

  GetModuleInformation

  GetPerformanceInfo

  GetProcessMemoryInfo

  GetWsChanges

  InitializeProcessForWsWatch

  QueryWorkingSet

EnumProcesses检索进程中的每一个进程标识符:

http://baike.baidu.com/view/2003505.htm

pProcessId

  接收进程标识符的数组.Pointer to an array that receives the list of process identifiers.

cb

  数组的大小.Size of the pProcessIds array, in bytes.

pBytesRrturned

数组返回的字节数.Number ofbytes returned in the pProcessIds array.

OpenProcess:用来打开一个已存在的进程对象,并返回进程的句柄;

http://baike.baidu.com/view/1280137.htm

 DWORD dwDesiredAccess, //渴望得到的访问权限(标志)

 BOOL bInheritHandle, // 是否继承句柄

 DWORD dwProcessId// 进程标示符

 

示例代码:

例如,查看列举所有进程或列举所有模块的一个过程。列举所有模块的一个过程来确定哪个进程加载一个特定的DLL,你必须列举模块为每个流程。下面的示例代码使用EnumProcessModules函数枚举当前流程的模块的系统。

  #include<windows.h>

  #include<stdio.h>

  #include"psapi.h"

  voidPrintModules( DWORD processID )

  {

  HMODULEhMods[1024];

  HANDLEhProcess;

  DWORDcbNeeded;

  unsignedint i;

  // Printthe process identifier.

  printf("\nProcess ID: %u\n", processID );

  // Get alist of all the modules in this process.

  hProcess =OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,

  FALSE,processID );

  if (NULL== hProcess)

  return;

  if(EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))

  {

  for ( i =0; i < (cbNeeded / sizeof(HMODULE)); i++ )

  {

  charszModName[MAX_PATH];

  // Get thefull path to the module's file.

  if (GetModuleFileNameEx( hProcess, hMods, szModName, sizeof(szModName)))

  {

  // Printthe module name and handle value.

  printf("\t%s(0x%08X)\n", szModName, hMods ) ;

  }

  }

  }

  CloseHandle(hProcess );

  }

  void main()

  {

  // Get thelist of process identifiers.

  DWORDaProcesses[1024], cbNeeded, cProcesses;

  unsignedint i;

  if (!EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )

  return;

  //Calculate how many process identifiers were returned.

  cProcesses= cbNeeded / sizeof(DWORD);

  // Printthe name of the modules for each process.

  for ( i =0; i < cProcesses; i++ )

  PrintModules(aProcesses);

}

代码解释

main函数中用EnumProcesses接收进程标识符的数组,对于每个进程,PrintModules函数以过程标识符为参数,将过程表符传给OpenProcess,判断OpenProcess是否能打开相应过程标识符标识的进程,如果能打开,怎通过EnumProcessModules检索一个处理在指定的进程中每个模块。然后通过调用GetModuleFileNameEx函数获得指定路径的模块的名字。

ToolHelp32

http://hi.baidu.com/szfoaaamqabavwd/item/fe43d7142f587d423a176ef6

http://hi.baidu.com/dcxepslbwablmyq/item/f36feae1d1a00ec0baf37d20

 

ToolHelp32EnumProcesses

摘要

我们在编写程序时,常常遇到的一件事情就是要准确列出系统中所有正在运行的程序或者进程。Windows 任务管理器就是这样的一个程序。它既能列出运行的桌面应用程序,又能列出系统中所有运行的进程。那么,我们在程序中如何实现这样的任务呢?本文下面将详细讨论这个问题。


枚举顶层(top-level)窗口

枚举桌面顶层窗口相对于枚举进程来说可能要容易一些。枚举桌面顶层窗口的方法是用 EnumWindows() 函数。不要用 GetWindow()来创建窗口列表,因为窗口之间复杂的父子及同胞关系(Z-Order)容易造成混乱而使得枚举结果不准确。
EnumWindows()有两个参数,一个是指向回调函数的指针,一个是用户定义的 LPARAM 值,针对每个桌面窗口(或者顶层窗口)它调用回调函数一次。然后回调函数用该窗口句柄做一些处理,比如将它添加到列表中。这个方法保证枚举结果不会被窗口复杂的层次关系搞乱,因此,一旦有了窗口句柄,我们就可以通过 GetWindowText() 得到窗口标题。


枚举进程

建立系统进程列表比枚举窗口稍微复杂一些。这主要是因为所用的 API 函数对于不同的 Win32 操作系统有依赖性。在 Windows 9xWindows MeWindows 2000 Professional 以及 Windows XP 中,我们可以用ToolHelp32 库中的 APIs 函数。但是在 Windows NT 里,我们必须用 PSAPI 库中的 APIs 函数, PSAPI 库是 SDK 的一部分。本文我们将讨论上述所有平台中的实现。附带的例子程序将对上述库中的 APIs 进行包装,以便包装后的函数能支持所有 Win32 操作系统。


使用 ToolHelp32 库枚举进程

ToolHelp32
库函数在 KERNEL32.dll 中,它们都是标准的 API 函数。但是 Windows NT 4.0 不提供这些函。
ToolHelp32 库中有各种各样的函数可以用来枚举系统中的进程、线程以及获取内存和模块信息。其中枚举进程只需用如下三个的函数:CreateToolhelp32Snapshot()Process32First()Process32Next()
使用 ToolHelp32 函数的第一步是用 CreateToolhelp32Snapshot() 函数创建系统信息快照。这个函数可以让你选择存储在快照中的信息类型。如果你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志即可。 CreateToolhelp32Snapshot() 函数返回一个 HANDLE,完成调用之后,必须将此HANDLE 传给 CloseHandle()
接下来是调用一次 Process32First 函数,从快照中获取进程列表,然后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个PROCESSENTRY32 结构。
调用完 Process32First Process32Next 之后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 可以被传给 OpenProcess() API 以获得该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile 结构成员中。在该结构中还可以找到其它一些有用的信息。
注意:在调用 Process32First() 之前,一定要记住将 PROCESSENTRY32 结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)

--

CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]

HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);

参数:
dwFlags
[
输入]指定快照中包含的系统内容,这个参数能够使用下列数值(变量)中的一个。

TH32CS_INHERIT -
声明快照句柄是可继承的。
TH32CS_SNAPALL - 在快照中包含系统中所有的进程和线程。
TH32CS_SNAPHEAPLIST - 在快照中包含在th32ProcessID中指定的进程的所有的堆。
TH32CS_SNAPMODULE - 在快照中包含在th32ProcessID中指定的进程的所有的模块。
TH32CS_SNAPPROCESS - 在快照中包含系统中所有的进程。
TH32CS_SNAPTHREAD - 在快照中包含系统中所有的线程。

th32ProcessID
[
输入]指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLISTTH32CS_SNAPMOUDLE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。

返回值:
调用成功,返回快照的句柄,调用失败,返回INVAID_HANDLE_VALUE

备注:
使用GetLastError函数查找该函数产生的错误状态码。
要删除快照,使用CloseHandle函数


使用 PSAPI 库枚举进程

Windows NT 中,创建进程列表使用 PSAPI 函数,这些函数在 PSAPI.DLL 中。这个文件是随 Platform SDK 一起分发的,最新版本的 Platform SDK 可以从这里下载

使用这个库所需的 PSAPI.h PSAPI.lib 文件也在该 Platform SDK 中。
为了使用 PSAPI 库中的函数,需将 PSAPI.lib 添加到代码项目中,同时在所有调用 PSAPI API 的模块中包含 PSAPI.h 文件。记住一定要随可执行文件一起分发 PSAPI.DLL,因为它不随 Windows NT 一起分发。你可以点击这里单独下载 PSAPI.DLL的可分发版本(不用完全下载 Platform SDK)。
ToolHelp32 一样,PSAPI 库也包含各种各样有用的函数。由于篇幅所限,本文只讨论与枚举进程有关函数:EnumProcesses()EnumProcessModules ()GetModuleFileNameEx() GetModuleBaseName()
创建进程列表的第一步是调用 EnumProcesses()。该函数的声明如下:

BOOL EnumProcesses( DWORD *lpidProcess,DWORD cb, DWORD *cbNeeded );EnumProcesses()带三个参数,DWORD 类型的数组指针lpidProcess;该数组的大小尺寸 cb;以及一个指向 DWORD 的指针 cbNeeded,它接收返回数据的长度。DWORD 数组用于保存当前运行的进程 IDscbNeeded 返回数组所用的内存大小。下面算式可以得出返回了多少进程:nReturned = cbNeeded / sizeof(DWORD)
注意:虽然文档将返回的 DWORD 命名为“cbNeeded”,实际上是没有办法知道到底要传多大的数组的。EnumProcesses()根本不会在 cbNeeded 中返回一个大于 cb 参数传递的数组值。结果,唯一确保 EnumProcesses()函数成功的方法是分配一个 DWORD 数组,并且,如果返回的 cbNeeded 等于 cb,分配一个较大的数组,并不停地尝试直到 cbNeeded 小于 cb
现在,你获得了一个数组,其元素保存着系统中每个进程的ID。如果你要想获取进程名,那么你必须首先获取一个句柄。要想从进程 ID 得到句柄,就得调用 OpenProcess()
一旦有了句柄,则需要得到该进程的第一个模块。为此调用 EnumProcessModules() APIEnumProcessModules(hProcess, &hModule, sizeof(hModule), &cbReturned );调用之后,hModule 变量中保存的将是进程中的第一个模块。记住进程其实没有名字,但进程的第一个模块既是该进程的可执行模块。现在你可以用 hModule 中返回的模块句柄调用 GetModuleFileNameEx() GetModuleBaseName()API 函数获取全路径名,或者仅仅是进程可执行模块名。两个函数均带四个参数:进程句柄,模块句柄,返回名字的缓冲指针以及缓冲大小尺寸。
EnumProcesses() API 返回的每一个进程 ID 重复这个调用过程,你便可以创建 Windows NT 的进程列表。


16位进程的处理方法

Windows 95Windows 98 Windows ME 中,ToolHelp32 对待16位程序一视同仁,它们与 Win32 程序一样有自己的进程IDs。但是在 Windows NTWindows 2000 Windows XP 中情况并不是这样。在这些操作系统中,16位程序运行在所谓的 VDM 当中(也就是DOS机)。
为了在 Windows NTWindows 2000 Windows XP 中枚举16位程序,你必须使用一个名为 VDMEnumTaskWOWEx()的函数。在源代码模块中必须包含 VDMDBG.h,并且 VDMDBG.lib 文件必须与项目链接。这两个文件都在 Platform SDK 中。该函数的声明如下:INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEXfp,LPARAM lparam );

  此处 dwProcessId NTVDM 中拟枚举的16位任务进程标示符。参数 fp 是回调枚举函数的指针。参数 lparam 是用户定义的值,它被传递到枚举函数。枚举函数应该被定义成如下这样:

BOOL WINAPI Enum16( DWORD dwThreadId,
WORD hMod16,
WORD hTask16,
PSZ pszModName,
PSZ pszFileName,
LPARAM lpUserDefined );
该函数针对每个运行在 NTVDM 进程中的16位任务调用一次,NTVDM 进程ID将被传入 VDMEnumTaskWOWEx()。如果想继续枚举则返回 FALSE,终止枚举则返回 TRUE。注意这是与 EnumWindows()相对的。


关于代码

本文附带的代码例子将 PSAPI ToolHelp32 封装到一个名为 EnumProcs() 的函数中。该函数的工作原理类似 EnumWindows(),有一个指向回调函数的指针,并要对该函数进行重复调用,针对系统中的每个进程调用一次。另一个参数是用户定义的 lParam。下面是该函数的声明:BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );

使用该函数时,要象下面这样声明回调函数:

BOOL CALLBACK Proc( DWORD dw, WORD w16,LPCSTR lpstr, LPARAM lParam );

参数 dw 包含 ID“w16”16位任务的任务号,如果为32位进程则为0(在 Windows 95中总是0),参数lpstr 指向文件名,lParam 是用户定义的,要被传入 EnumProcs()
EnumProcs() 函数通过显示链接使用 ToolHelp32 PSAPI,而非通常所用的隐式链接。之所以要这样做,主要是为了让代码能够在二进制一级兼容,从可以在所有 Win32 操作系统平台上运行。

 

http://www.cppblog.com/tx7do/articles/5632.html

HOWTO:使用Win32 API 枚举应用程序

http://www.cppblog.com/tx7do/archive/2012/07/29/5576.html#185532

 

七、一个进程中各线程的堆和栈的关系

在很多现代操作系统中,一个进程的(虚)地址空间大小为4G,分为系统空间和用户空间两部分,系统空间为所有进程共享,而用户空间是独立的,一般WINDOWS进程的用户空间为2G

一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(私有的)栈(stack)Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,Windows进程还有所谓进程默认堆,用户也可以创建自己的堆。

堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

 

栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

 

LoadLibrary:载入指定的动态链接库,并将它映射到当前进程使用的地址空间。一旦载入,即可访问库内保存的资源

http://baike.baidu.com/view/1286902.htm

  返回值

Long,成功则返回库模块的句柄,零表示失败。会设置GetLastError

GetProcAddresshttp://baike.baidu.com/view/1523523.htm

  GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。

  函数原型:

  FARPROC GetProcAddress(

  HMODULE hModule, // DLL模块句柄

  LPCSTR lpProcName // 函数名

  );

八、VC全局钩子实现程序监控

SetWindowsHookEx:http://baike.baidu.com/view/1208620.htm

钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。

  钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

CreateFileMappinghttp://baike.baidu.com/view/1288760.htm

 

 

九、HTTP服务用线程池实现

超文件传输协议(HTTPHyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。目前的应用主要除了HTML网页外还被用来传输超文本数据例如:图片、音频文件(MP3)、视频文件(rmavi)、压缩包(ziprar)……基本上只要是文件数据均可以利用HTTP进行传输。

  Web的应用层协议HTTPWeb的核心。HTTPWeb的客户程序和服务器程序中得以实现。运行在不同端系统上的客户程序和服务器程序通过交换HTTP消息彼此交流。HTTP定义这些消息的结构以及客户和服务器如何交换这些消息。在详细解释HTTP之前,我们先来回顾一些web中的术语。

  Web页面(web page,也称为文档)由多个对象构成。对象(object)仅仅是可由单个URL寻址的文件,例如HTML文件、JPG图像、GIF图像、JAVA小应用程序、语音片段等。大多数Web页面由单个基本HIML文件和若干个所引用的对象构成。例如,如果一个Web页面包含HTML文本和5JPEG图像,那么它由6个对象构成,即基本H1ML文件加5个图像。基本HTML文件使用相应的URL来引用本页面的其他对象。每个URL由存放该对象的服务器主机名和该对象的路径名两部分构成。例如,在如下的URL:

  www.zzy.cn/images/zzy-logoa.gif

  www.zzy.cn是一个主机名,/images/zzy-logoa.gif是一个路径名。浏览器是web的用户代理,它显示所请求的Web页面,并提供大量的导航与配置特性。Web浏览器还实现HTTP的客户端,因此在web上下文中,我们会从进程意义上互换使用“浏览器”和“客户”两词。流行的Web浏览器有Netscape Communicatorfirefox和微软的IE等。Web服务器存放可由URL寻址的Web对象。web服务器还实现HTTP的服务器端。流行的Web服务器有Apache、微软的IIS以及Netscape Enterprise ServerNetcraft提供了web服务器的概要剖析[Netcrft 2000]

  HTTP定义Web客户(即浏览器)如何从web服务器请求Web页面,以及服务器如何把Web页面传送给客户。下图展示了这种请求—响应行为。当用户请求一个Web页面(譬如说点击某个超链接)时,浏览器把请求该页面中各个对象的HTTP请求消息发送给服务器。服务器收到请求后,以运送含有这些对象HTTP响应消息作为响应。到1997年底,基本上所有的浏览器和Web服务器软件都实现了在RFC 1945中定义的HTTP/1.0版本。1998年初,一些Web服务器软件和浏览器软件开始实现在RFC 2616中定义的HTTP/1.1版本。HTTP/1.1HTTP/1.0后向兼容;运行1.1版本的web服务器可以与运行1.0版本的浏览器“对话”,运行1.1版本的浏览器也可以与运行1.0版本的Web服务器“对话”。

  HTTP/1.0HTTP/1.1都把TCP作为底层的传输协议。HTTP客户首先发起建立与服务器TCP连接。一旦建立连接,浏览器进程和服务器进程就可以通过各自的套接字来访问TCP。如前所述,客户端套接字是客户进程和TCP连接之间的服务器端套接字是服务器进程和同一TCP连接之间的。客户往自己的套接字发送HTTP请求消息,也从自己的套接字接收HTTP响应消息。类似地,服务器从自己的套接字接收HTTP请求消息,也往自己的套接字发送HTTP响应消息。客户或服务器一旦把某个消息送入各自的套接字,这个消息就完全落入TCP的控制之中。TCPHTTP提供一个可靠的数据传输服务;这意味着由客户发出的每个HTTP请求消息最终将无损地到达服务器,由服务器发出的每个HTTP响应消息最终也将无损地到达客户。我们可从中看到分层网络体系结构的一个明显优势——HTTP不必担心数据会丢失,也无需关心TCP如何从数据的丢失和错序中恢复出来的细节。这些是TCP和协议栈中更低协议层的任务。

  TCP还使用一个拥塞控制机制。该机制迫使每个新的TCP连接一开始以相对缓慢的速率传输数据,然而只要网络不拥塞,每个连接可以迅速上升到相对较高的速率。这个慢速传输的初始阶段称为缓启动(slow start)

  需要注意的是,在向客户发送所请求文件的同时,服务器并没有存储关于该客户的任何状态信息。即便某个客户在几秒钟内再次请求同一个对象,服务器也不会响应说:自己刚刚给它发送了这个对象。相反,服务器重新发送这个对象,因为它已经彻底忘记早先做过什么。既然HTTP服务器不维护客户的状态信息,我们于是说HTTP是一个无状态的协议(stateless protocol)

 

用线程池实现HTTP服务:

 

publicstatic void main(String[] args)

 {

//TODO Auto-generated method stub

 try{

ServerSocket server= new ServerSocket(8000);

System.out.println("Serveris listenning");

ThreadPoolManagermanager = new ThreadPoolManager(3);

int i = 0;

while(true)

{

Socket client =server.accept();

manager.add(client);

i++;

System.out.println(i);

}

}catch(IOExceptione){ e.printStackTrace(); } }

 

这是整个程序的入口,初始化该初始化的,监听该

监听的。ThreadPoolManager用于管理线程,初始化有三个线程。

accept方法会造成阻塞,知道有消息传过来。将接收到的消息传入到manager中以便于管理。

 

publicclass ThreadPoolManager

{

private intmaxThread;

publicVector<SimpleThreak> vector;

publicQueue<Socket> buffer;

public voidsetMaxThread(int maxThread) { this.maxThread = maxThread; }

public intgetMaxThread() { return maxThread; }

publicThreadPoolManager(int threadCount)

{

this.setMaxThread(threadCount);

buffer = newArrayBlockingQueue<Socket>(100);

System.out.println("Startingpool");

vector = newVector<SimpleThreak>();

for(inti=1;i<=threadCount;i++)

{

SimpleThreak st =new SimpleThreak(i,buffer);

vector.add(st);st.start();

}

}

public voidadd(Socket client)

{

synchronized(buffer)

{

buffer.offer(client);

buffer.notify();

}

}

 

线程池类,线程池在初始化之后放入一个vector之中,同时将请求放入一个缓冲区中,缓冲区用ArrayBlockingQueue来做。

BlockingQueue接口定义了一种阻塞的FIFO queue,每一个BlockingQueue都有一个容量,让容量满时往BlockingQueue中添加数据时会造成阻塞,当容量为空时取元素操作会阻塞。ArrayBlockingQueue是对BlockingQueue的一个数组实现,它使用一把全局的锁并行对queue的读写操作,同时使用两个Condition阻塞容量为空时的取操作和容量满时的写操作。

 

publicsynchronized void run()

{

Try

{

while(true)

{ synchronized(buffer)

{

 if(buffer.isEmpty()){ buffer.wait(); }

this.client =buffer.poll();

System.out.println(this.number+"------running");

}

PrintStreamoutStream;

BufferedReader in ;

try {

in = new BufferedReader(newInputStreamReader(client.getInputStream()));

outStream=new PrintStream(newBufferedOutputStream(client.getOutputStream())); this.argument=in.readLine();

if(getRequest(this.argument))

{

String fileName =getFileName(this.argument);

File file = newFile(fileName);

if(file.exists())

{sendFile(outStream,file);}

else

System.out.println("不存在图片");

}

in.close();

} catch (IOExceptione)

 {

// TODO Auto-generated catchblock

e.printStackTrace();

}

sleep(3*1000);

//System.out.println("Threadis sleeping");

}

}catch(InterruptedExceptione){ System.out.println("Interrupt"); } }

 

不断监视缓冲区中的动向,如果有了数据,就从缓冲区中取出socket,否则进入等待列,将socket封装到bufferReader中,取出请求的报文头,如果请求的文件存在,就将文件放入PrintStream中,送入客户端。为了看出效果,让线程工作完之后睡3秒钟。

 

 

privateString getFileName(String s)

{

Stringf = s.substring(s.indexOf(' ')+1);

f=f.substring(0,f.indexOf(''));

try

{

   if(f.charAt(0)=='/')

f=f.substring(1);

}

catch(StringIndexOutOfBoundsExceptione)

{e.printStackTrace(); }

if(f.equals(""))

 f = "index.html";

returnf;

}

 

 

通过截取报文头,来获取请求文件的位置。

privatevoid sendFile(PrintStream ps,File file)

{

try

{

DataInputStream ds =new DataInputStream(new FileInputStream(file));

int len = (int)file.length(); //System.out.println(len);

byte buf[]=newbyte[len];

ds.readFully(buf);

ps.write(okResponse().getBytes());

ps.write(buf,0,len);

ps.flush();

ds.close();

 }catch(Exception e){ e.printStackTrace(); } }

 

将流传输到客户端的过程,先将数据写入到一个字节流中,输入到客户端。

这样,一个简单的http服务器就完成了,服务器还有一个问题,就是请求过来之后会阻塞。从高人处得知,可以用nio这种非阻塞的io进行优化。正在研究之中。

Argshttp://baike.baidu.com/view/1333371.htm   args[] 在命令行运行时候输入的参数

ServerSocket Socket的区别

http://www.cnblogs.com/mareymarey111/archive/2011/12/08/2280253.html

ServerSocket 一般仅用于设置端口号和监听,真正进行通信的是服务器端的Socket与客户端的Socket,在ServerSocket 进行accept之后,就将主动权转让了。

1. 服务器端程序设计
在服务器端,利用ServerSocket类的构造函数ServerSocket(intport)创建一个ServerSocket类的对象,port参数传递端口,这个端口就是服务器监听连接请求的端口,如果在这时出现错误将抛出IOException异常对象,否则将创建ServerSocket对象并开始准备接收连接请求。
服务程序从调用ServerSocket的accept()方法开始,直到连接建立。在建立连接后,accept()返回一个最近创建的Socket对象,该Socket对象绑定了客户程序的IP地址或端口号。
2.客户端程序设计
当客户程序需要与服务器程序通信时,需在客户机创建一个Socket对象。Socket类有构造函数Socket(InetAddressaddr,int port)和Socket(Stringhost,intport),两个构造函数都创建了一个基于Socket的连接服务器端流套接字的流套接字。对于第一个InetAd-dress子类对象通过addr参数获得服务器主机的IP地址,对于第二个函数host参数包被分配到InetAddress对象中,如果没有IP地址与host参数相一致,那么将抛出UnknownHostException异常对象。两个函数都通过参数port获得服务器的端口号。假设已经建立连接了,网络API将在客户端基于Socket的流套接字中捆绑客户程序的IP地址和任意一个端口号,否则两个函数都会抛出一个IOException对象。
如果创建了一个Socket对象,那么它可通过get-InputStream()方法从服务程序获得输入流读传送来的信息,也可通过调用getOutputStream()方法获得输出流来发送消息。在读写活动完成之后,客户程序调用close()方法关闭流和流套接字。

 

 

 

你可能感兴趣的:(Windows核心编程学习文档)