因为家族产业的缘故,我很少吃小吃店里的包子,这一习惯维持了好多年。
公司早上提供早饭,包子+豆浆。老员工每天都是一个包子+一杯豆浆,刚来的一段时间,我每天早上都拿2个包子,不要自己掏钱的早饭,吃起来具香。差不多一个月过去了,渐渐发觉自己吃不下2个包子了,一个,或者一个半,逐步进入老员工的食量状态。
腻了,真都点腻了。
因为家族产业的缘故,我很少吃小吃店里的包子,这一习惯维持了好多年。
公司早上提供早饭,包子+豆浆。老员工每天都是一个包子+一杯豆浆,刚来的一段时间,我每天早上都拿2个包子,不要自己掏钱的早饭,吃起来具香。差不多一个月过去了,渐渐发觉自己吃不下2个包子了,一个,或者一个半,逐步进入老员工的食量状态。
腻了,真都点腻了。
拖拖拉拉整理完了vc++深入详解的前17章,收获还是不少的。开始写这些文档之前便计划着辞掉那份asp.net的工作,在写这份文档完成之前终于如愿了。从asp.net到win ce,虽然都是vs2005平台,跨度可真不小,从不想整到整不了,一时间很难适应。
《vc++深入详解》是本很不错的书,很仔细的讲解了vc开发的基本知识,本来计划着一口气把书上的例子程序都整理出来,做个笔记,可是因为换了工作,时间上很难保证。另外,书的后三章介绍的分别是active控件,dll,hook与数据库。虽然都是很有用的知识,但是离应用还远,另外,工作上是wince开发,这些知识暂时还用不到,拖拉了一个多星期,也没能把这份文档做个完结。
这两天空闲的时候在想,为了做个了解,干脆粗乱这整理些文字发上来。可是翻开书本的时候却没有多少兴致。罢了,在此做个标记,等回到MFC开发平台的时候再学习这些吧。
为了更快熟悉wince,今天在亚马逊上买了本wince开发的书,不知道每天工作加班到深夜之后,还是否有兴致来整理《win ce 学习笔记》之类的东西。如果时间上容许的话,我肯定乐而为之。好脑筋比不上烂笔头,整理vc++学习笔记过程中思考了一些问题,在面试这份工作的时候提供了不小的帮助。即使抄写比人现成的代码也能给我这只菜鸟带来不小的成就感。
当一个进程启动后,操作系统为其分配4GB的私有地址空间,位于同一个进程中的线程共享同一个地址空间。然而由于每个进程所拥有的4GB的地址空间都是私有的,一个进程不能访问另外一个进程的地址空间的数据,因此进程之间的通信相对比较困难。本章将介绍下列4中进程通信的方式:
17.1剪贴板
当我们在一个程序中复制一份数据之后,可以将数据粘贴到另外一个应用程序中,这就是2个进程利用剪贴板实现诗词数据传输。剪贴板实际上是系统维护管理的一块内存区域,当在一个进程中复制数据时,这个份数据被放到该内存区域中,当执行粘帖操作时,从该内存区域中取出数据,然后显示出来。
将数据粘帖上剪切板时,首先要打开剪贴板。
BOOL OpenClipBoard();
当打开剪贴板成功,完成操作之后,需要关闭剪贴板CloseClipBoard(),以便其他程序调用。在系统系统中之后一块剪贴板区域,如果当前拥有者不关闭剪贴板,其他进程则无法获取到。手动将编辑框內数据复制到剪贴板的示例:
void CClipDlg::OnButton1()
{
// TODO: Add your control notification handler code here
if(OpenClipboard())
{
CString str;//用来存放将要放到剪贴板上的数据
HANDLE hClip;//保存GlobalAlloc动态分配的内存对象的句柄
char *pBuf;//保存调用GlobalLock函数返回的地址
EmptyClipboard();//清空剪贴板
GetDlgItemText(IDC_EDIT1,str);//获取编辑框的内容,保存到str
hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//动态分配内存,返回对象句柄
pBuf=(char*)GlobalLock(hClip);//对内存对象加锁,返回地址
strcpy(pBuf,str);//将str对象中的数据拷贝到pBuf指向的地址空间
GlobalUnlock(hClip);//解锁
SetClipboardData(CF_TEXT,hClip);//以指定的剪贴板格式项剪贴板上存放数据
CloseClipboard();//关闭剪贴板
}
}
其中GlobalAlloc动态分配一块内存区域的参数有几种默认类型,MSDN中给出了详细的说明。
调用剪贴板上的数据,显示到一个编辑框上。
void CClipDlg::OnButton2()
{
// TODO: Add your control notification handler code here
if(OpenClipboard())
{
if(IsClipboardFormatAvailable(CF_TEXT))
{
HANDLE hClip;
char * pBuf;
hClip=GetClipboardData(CF_TEXT);
pBuf=(char*)GlobalLock(hClip);
GlobalUnlock(hClip);
SetDlgItemText(IDC_EDIT2,pBuf);
}
}
}
17.2匿名管道
匿名管道是一个未命名的、单向管道,通常用来在一个父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上的两个进程间的通信,而不能实现跨网络通信。
17.3命名管道
命名管道通过网络来实现进程间的通信,它屏蔽了底层的网络协议细节,在不了解网络协议的情况下也可以利用命名管道来实现进程间的通信。命名管道不仅可以在本机上实现2个进程之间的通信,还可以跨网络实现2个进程的通信。
17.4邮槽
邮槽式基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。邮槽是一种单向的通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户端进程写入数据。为了使邮槽在各种Windows平台下运行,传输消息的时候,应该将消息的长度限制在424字节以下。
PS:后三个例子代码写不贴了。
上一章介绍了线程同步,以及利用互斥对象实现线程同步的方法。本章继续介绍另2种线程同步的方法:事件对象和关键代码段,另外,介绍了利用异步套接字编写网络应用程序的实现。
16.1事件对象
和互斥对象一样,时间对象也属于内核对象。事件对象分为人工重置的事件对象和自动重置的事件对象。人工重置的事件对象得到通知时,等待该事件对象的所有线程都变为可调度线程。自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个变为可调度线程。
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // SD BOOL bManualReset, // reset type BOOL bInitialState, // initial state LPCTSTR lpName // object name );
根据事件对象的声明可以了解它的一些参数属性。创建一个事件对象之后,可以通过SetEvent函数和ResetEvent函数来设置事件对象的有无信号状态。通常在单CPU平台下,为了实现线程的通过,常使用自动重置的事件对象,而非人工重置的事件对象。
16.2关键代码段
关键代码段:也称为临界区,工作在用户态,它是指一个小段代码,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码称为关键代码段。利用关键代码段实现线程同步的示例代码如下:
#include <windows.h>
#include <iostream.h>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int ticket=100;
CRITICAL_SECTION g_cs;
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);
InitializeCriticalSection(&g_cs);//初始化
Sleep(4000);
DeleteCriticalSection(&g_cs);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);//进入关键代码段
Sleep(1);
if(ticket>0)
{
Sleep(1);
cout<<\"thread1 sell the ticket:\"<<ticket--<<endl;
LeaveCriticalSection(&g_cs);//离开
}
else
{
LeaveCriticalSection(&g_cs);//离开
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);//进入关键代码段
Sleep(1);
if(ticket>0)
{
Sleep(1);
cout<<\"thread2 sell the ticket:\"<<ticket--<<endl;
LeaveCriticalSection(&g_cs);//离开
}
else
{
LeaveCriticalSection(&g_cs);//离开
break;
}
}
return 0;
}
16.3互斥对象,事件对象和关键代码段的比较
本章主要介绍多线程程序的编写,并利用多线程技术创建一个网络聊天室。
15.1基本概念
程序是计算机指令的集合,它以文件形式存储在磁盘上。
进程被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。
进程的组成:操作系统用来管理进程的内核对象和地址空间。进程不执行任何东西,它只是程序的实例,线程的容器或执行环境。创建一个进程之后,操作系统会自动为这个进程创建一个主线程(执行main或者winmain函数的线程)。
线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
线程的组成:线程的内核对象(操作系统用来存放线程统计信息)和线程栈(维护线程在执行代码时候所需要的所有的函数和局部变量)。一个线程之后一个内核对象和一个栈。
15.2线程创建函数
可使用系统提供的API函数CreateThread来创建一个线程。
该函数的原形如下:
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD DWORD dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier );
根据CreateThread函数的参数说明,可以基本了解创建一个线程的规则。
15.3简单的多线程示例
#include <windows.h>
#include <iostream.h>
DWORD WINAPI Function1(LPVOID lpParamete);//申明过程函数
void main()
{
HANDLE hThreadl;
hThreadl=CreateThread(NULL,0,Function1,NULL,0,NULL);
CloseHandle(hThreadl);
cout<<\"main thread is running\"<<endl;
Sleep(10);//主线程执行完毕后,休眠10毫秒,启动子线程
}
DWORD WINAPI Function1(LPVOID lpParamete)
{
cout<<\"thread1 is running\"<<endl;
return 0;
}
在创建一个线程的时候需要指定过程函数,因此需要在main函数之前申明Function1,也可以将Function1函数的实现放到main函数之前,这样就不需要声明了。
15.4线程同步
操作系统为每一个运行线程安排一定的CPU时间—时间片。在一个多线程的程序中,系统通过一种循环的方式为线程提供时间片,线程在自己的时间片內运行。这就可以会出现一种情况:当多个线性同时对一个全局的对象进行访问时,一个线程已经进入了循环体,这时候时间片结束,该线程进入睡眠状态,系统转向执行另外一个线程。而当另外一个线程执行的时候对全局变量做了修改。当返回到第一个线程的时候,接着从上次时间片结束的地方执行,这时候会出现一些异常。
DWORD WINAPI Function1(LPVOID lpParamete)
{
while(true) {
if(count>0) {
//当线程执行到此的时候,时间片到了,线程1Sleep,开始执行线程2,当线性2完毕之后再回到线程1,接着从此处执行。这时候有可能线程count的值已经为非正常值,如:0。
cout<<\"thread1 sell the ticket:\"<<count--<<endl;
}
else
break;
}
return 0;
}
DWORD WINAPI Function2(LPVOID lpParamete)
{
while(true) {
if(count>0)
{cout<<\"thread2 sell the ticket:\"<<count--<<endl; }
else
break;
}
return 0;
}
15.4.1互斥对象实现线程同步
互斥对象mutex属于内核对象,它能够确保线程对当资源的互斥访问权。创建互斥对象的CreateMutex函数的声明如下:
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // 互斥对象安全性,默认NULL BOOL bInitialOwner, // 指定互斥对象的拥有者,默认FALSE LPCTSTR lpName // 指定互斥对象的名称,默认NULL );
网络程序的实现可以有多种方式,Windows Socket就是其中的一种比较简单的实现方式。Socket是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定操作和应用程序建立连接关系。
14.1计算机网络的基本知识
计算机网络是相互连接的独立自主的计算机的集合,最初建立的目的是实现资源的共享。其中比较重要的概念是协议,其目的是为了进行网络中的数据交换而建立的规则、标准或约定。不同的层有不同的协议。
14.1.1ISO/OSI七层参考模型
OSI(Open System Interconnection)参考模型将网络的不同功能分成了七层。由高到底的分层及其功能分别如下:
七层参考模型知识一个功能上的划分,不是物理上的划分,各层之间是严格的单向依赖,通信实体的对等层之间不容许出现直接通信,下次向上层提供服务,上层使用下层提供的服务。
14.1.2数据封装
一台计算机向另外一台计算机发送数据,必须将该数据打包,打包的过程被称为数据封装。由应用层向下,逐步为数据进行封装,在头部或尾部或同时添加本层的头部或尾部。当数据包到达目标计算机时,再从物理层开始,逐步去除头部或尾部,完成拆分。
14.1.3TCP/IP模型
OSI的七层参考模型比较复杂,通常采用TCP/IP模型,该模型有四个层次与OSI七层模型的对应关系为:
14.1.4端口
为了标识通信实体中进行通信的进程,TCP/IP提供了协议端口的概念。端口是一种抽象的软解结构,应用程序通过系统调用和某端口建立连接后,传输层传给该端口的数据都会被相应得应用程序接收,相依进程发送给传输层的数据也通过该端口输出。(端口存在与传输层?)
端口的范围是0~65355,1024以下的端口号通常都被系统应用程序占用。如http的80,ftp的21.
14.1.9套接字Socket的引入
Socket最早出现在UNIX系统中,后来被引入Windows。Windows Socket只支持一个通信区域:网际域,这个域被使用网际协议簇通信的进程使用。
14.2Windows Socket的实现
套接字分为三种类型,其中主要使用的由2种:
流式套接字SOCK_STREAM:提供面向连接,可靠的传输服务,数据无差错,无重复发送,按发送顺序接收,流式套接字是基于TCP协议实现的;
数据报式套接字SOCK_DGRAM:提供无连接服务,数据包独立发送,基于UDP协议;
14.2.1基于TCP的socket编程
基于TCP的socket编程的服务器端的流程如下:
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//bind
bind(socksrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//listen
listen(socksrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
//wait for client
SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);
char buf[100];
sprintf(buf,\"welcome %s to www.colsir.com\",inet_ntoa(addrClient.sin_addr));
//send
send(sockConn,buf,strlen(buf)+1,0);
char recvbuf[100];
recv(sockConn,recvbuf,100,0);
printf(\"%s\n\",recvbuf);
closesocket(sockConn);
}
}
基于TCP的socket编程的客户端端的流程如下:
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockclient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=inet_addr(\"127.0.0.1\");
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//connect
connect(sockclient,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//recv
char recvbuf[100];
recv(sockclient,recvbuf,100,0);
printf(\"%s\n\",recvbuf);
//send
send(sockclient,\"send message\",strlen(\"send message\")+1,0);
closesocket(sockclient);
WSACleanup();
}
14.2.2基于UDP的socket编程
基于UDP的socket编程的服务器端的流程如下:
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
//bind
bind(sockSrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
//recv
while(1)
{
SOCKADDR_IN addServer;
int len=sizeof(SOCKADDR);
char recvbuf[100];
recvfrom(sockSrv,recvbuf,100,0,(SOCKADDR*)&addServer,&len);
printf(\"%s\n\",recvbuf);
}
closesocket(sockSrv);
WSACleanup();
}
基于UDP的socket编程的客户端端的流程如下:
示例代码如下:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
//create socket
SOCKET sockclient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addsrcv;
addsrcv.sin_addr.S_un.S_addr=inet_addr(\"127.0.0.1\");
addsrcv.sin_family=AF_INET;
addsrcv.sin_port=htons(6000);
sendto(sockclient,\"hello\",strlen(\"hello\")+1,0,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
closesocket(sockclient);
WSACleanup();
}
14.3小结
看完了书本上的介绍,把代码照着书本抄了一遍,对于socket编程有了一个初步的认识。真正完成能实用的应用程序要有很远的路。过程中遇到的问题做个简单的小结: