孙鑫VC视频教程笔记之第十六课(下)“异步套接字编程”
C++线程同步与异步套接字编程_孙鑫
相关函数说明
int WSASendTo( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, lpBuffers, dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr FAR lpNumberOfBytesSent, dwFlags, lpTo, iToLen, lpOverlapped, *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); s,标识一个套接字(可能已连接)的描述符。 标识一个套接字(可能已连接) lpBuffers,一个指向WSABUF结构体的指针。每一个WSABUF结构体包含一个 一个指向WSABUF结构体的指针。每一个WSABUF结构体包含一个 缓冲区的指针和缓冲区的长度。 dwBufferCount, lpBuffers数组中WSABUF结构体的数目。 lpBuffers数组中WSABUF结构体的数目。 lpNumberOfBytesSent,[out],如果发送操作立即完成,则为一个指向本次调用所 发送的字节数的指针。 dwFlags,指示影响操作行为的标志位。 lpTo,可选指针,指向目标套接字的地址。 iToLen,lpTo中地址的长度。 lpTo中地址的长度。 lpOverlapped,一个指向WSAOVERLAPPED结构的指针(对于非重叠套接字则忽 一个指向WSAOVERLAPPED结构的指针( 略)。 lpCompletionRoutine,一个指向接收操作完成时调用的完成例程的指针(对于非重 一个指向接收操作完成时调用的完成例程的指针( 叠套接字则忽略) 叠套接字则忽略)。
孙鑫视频VC++深入详解学习笔记
VC++深入详解学习笔记Lesson1: Windows程序运行原理及程序编写流程窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与Lessonecl调用规范的比较,初学者常犯错误及注意事项。
1.Windows API与Win32 SDK操作系统提供了各种方便开发Windows应用程序的编程接口,所的函数都在Window s。
h头文件中声明。
Win32 SDK(Software Development Kit): 即Windows 32位平台下的软件开发包,包括API函数,帮助文档,微软提供的一些辅助开发工具。
2.窗口与句柄窗口是是屏幕上一块矩形区域,是Windows应用程序与用户进行交互的接口。
窗口分为客户区和非客户区。
在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的,要对某个窗口进行操作,首先就要得到这个窗口的句柄。
其它各种资源(窗口,图标,光标等),系统在创建这些资源时会为它们分配内在,并返回标识这些资源的标识号,即句柄。
-->光标句柄(HCURSOR),图标句柄(HICON)。
3.消息与消息队列Windows程序设计是一种基于消息的事件驱动方式的程序设计模式。
消息:在Windows中由结构体MSG来表示,typedef struct tagMSG{HWND hwnd;//消息所属的窗口句柄UINT message;//消息本身标识符,由一数值表示,系统对消息定//义为WM_XXX宏(WM为Windows Message缩写)WPARAM wParam; //随消息的不同附加信息也不同LPARAM lParam; //消息的附加参数DWORD time; //消息投递的时间POINT pt; //鼠标当前位置}消息队列:每当一个Windows应用程序创建后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序一的窗口的消息,消息产生后被投递到消息队列中,应用程序通过一个消息循环不断的消息队列中取出消息进行响应。
孙鑫老师的VC视频笔记
申明本人什么功劳都没有,以上所有都是参考“虎非龙“所写的视频笔记。
望大家不要误会VC视频教程笔记目录第1课Windows程序运行原理及程序编写流程 (3)第2课类的编写与应用 (5)第3课讲述MFC AppWizard的原理与MFC程序框架的剖析 (6)第4课第4课MFC消息映射机制的剖析讲述如何运用ClassWizard (8)第五课文本编程 (11)第6课菜单编程 (15)第7课对话框用户界面程序的编写 (21)第8课逃跑按钮的巧妙实现 (23)第9课如何修改MFC AppWizard向导生成的框架程序的外观和大小 (24)第10课图形的绘制,如何使用自定义画笔 (27)第11课如何让CDC上输出的文字、图形具有保持功能 (29)第12课文件操作 (31)第13课使用CArchive类对文件进行操作 (33)第14课网络编程 (35)第15课多线程与网络编程 (40)第16课事件内核对象、关键代码段(临界区)的讲解 (44)第17课进程间通信 (47)第18课ActiveX编程 (55)第19课DLL编程 (57)第20课钩子与数据库编程 (60)第1课Windows程序运行原理及程序编写流程1.MFC生成的C++源文件中都有StdAfx.h,此文件包含了常用的AFX函数的声明,其中有afxwin.h,此文件包含了CRECT,CPoint,CWnd等许多类及其方法的声明。
2.Project->Setting->Debug可以加入命令行参数。
3.在SDK中要加入"windows.h"和stdio.h。
因为LoadCursor,MessageBox等函数的声明在这个文件中。
4.创建一个完整的窗口的四个步骤SDK,1设计窗口类,2注册窗口类,3创建窗口,4显示窗口5.函数名可以代表函数代码的首地址,即可作为函数指针。
6.要查看VC数据类型,可以在MSDN中输入“BOOL”然后选择“DATA TYPE”。
孙鑫老师笔记16课
Lesson 16 线程同步与异步套接字1.事件对象事件对象同上一课中的互斥对象一样属于内核对象,它包含三个成员:使用读数,用于指明该事件是一个自动重置的还是人工重置的事件的布尔值,用于指明该事件处于已通知状态还是未通知状态的布尔值.当人工重置的事件对象得到通知时,等待该事件对象的所有纯种无变为可高度线程,而一个自动重置的事件对象得到通知时,等待该事件对象的线程中人有一个变为可高度线程.所以一般使用线程同步时使用自动重置.创建事件对象:HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全选项,默认为NULLBOOL bManualReset, // reset type,TRUE(人工),FALSE(自动)BOOL bInitialState, // initial state,TRUE(有信号状态)LPCTSTR lpName // object name.事件对象名);BOOL SetEvent(HANDLE hEvent);把指定的事件对象设置为有信号状态BOOL ReSetEvent(HANDLE hEvent);把指定的事件对象设置为无信号状态BOOL CloseHandle( HANDLE hObject ); // handle to object关闭事件对象DWORD WaitForSingleObject(//请求内核对象,一旦得到事件对象,就进入代码中HANDLE hHandle, // handle to objectDWORD dwMilliseconds // time-out interval);以下是一个模拟火车站售票的多线程程序(使用事件对象实现线程同步)#include <windows.h>//加入头文件,Window API库#include <iostream.h>//C++标准输入输出库int tickets = 100;//共享的资源,火车票HANDLE g_hEvent;//全局的事件对象句柄//线程处理函数原型声明DWORD WINAPI Thread1Proc(LPVOID lpParameter // thread data);DWORD WINAPI Thread2Proc(LPVOID lpParameter // thread data);void main(){// g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建一个人工重置的匿名事件对象,当调用SetEvent时所有的线程都可以执行,不能实现同步// SetEvent(g_hEvent);//将事件对象设置为有信号状态g_hEvent = CreateEvent(NULL, FALSE, FALSE, "tickets");//创建一个自动重置的有名事件对象,当调用SetEvent时只有一个线程可以执行SetEvent(g_hEvent);//可以通过创建有名的事件对象来实现只有一个程序实例运行if (g_hEvent)//有值{if (ERROR_ALREADY_EXISTS == GetLastError())//以事件对象存在为条件实现只有一个实例运行限制,因为事件对象是内核对象,由操作系统管理,因此可以在多个线程间访问{cout << "only one instance can run!" << endl;return;}}HANDLE hThread1;HANDLE hThread2;hThread1 = CreateThread(NULL, 0, Thread1Proc, NULL, 0, NULL);hThread2 = CreateThread(NULL, 0, Thread2Proc, NULL, 0, NULL);CloseHandle(hThread1);//释放线程句柄CloseHandle(hThread2);Sleep(4000);CloseHandle(g_hEvent);//注意最后释放事件对象句柄,在MFC中在类的析构函数中完成}DWORD WINAPI Thread1Proc(LPVOID lpParameter // thread data){//其中的SetEvent函数应该在两个判断中都调用,以防止因条件不满足而造成对象不能被设置为有信息状态while(TRUE){WaitForSingleObject(g_hEvent, INFINITE);//无限期等待事件对象为有信号状态if (tickets > 0)//进入保护代码{cout << "Thread1 is selling tickets : " << tickets-- << endl;SetEvent(g_hEvent);}Else//如果票已经售完,退出循环{break;SetEvent(g_hEvent);}}return 0;}DWORD WINAPI Thread2Proc(LPVOID lpParameter // thread data){while(TRUE){WaitForSingleObject(g_hEvent, INFINITE);//等待事件对象,如果对象为有信号状态,可以请求该对象资源,并将其设置为综上:为实现线程间的同步,不应该使用人工重置的事件对象,而应该使用自动重置的事件对象2.关键代码段(临界区)工作在用户方式下,它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权,通常把多线程访问同一种资源的那部分代码当作关键代码段.VOID InitializeCriticalSection(//初始化代码段LPCRITICAL_SECTION lpCriticalSection //[out] critical section,使用之前要构造);VOID EnterCriticalSection(//进入关键代码段(临界区)LPCRITICAL_SECTION lpCriticalSection // critical section);VOID LeaveCriticalSection(//离开关键代码段(临界区)LPCRITICAL_SECTION lpCriticalSection // critical section);VOID DeleteCriticalSection(//删除关键代码段(临界区)LPCRITICAL_SECTION lpCriticalSection // critical section);种方法比较简单!但缺点是如果使用了多少关键代码码,容易赞成线程的死锁(使用两个或以上的临界区对象或互斥对象,造成线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程2拥有了临界区对象B,等待临界区对象A 的拥有权,形成死锁,程序无法执行下去!3.互斥对象,事件对象,关键代码段的比较⏹互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,较慢,但利用互斥对象和事件对象这俗人内核对象,可以在多个进程中的各个纯种间进行同步⏹关键代码段工作在用户方式下,同步速度快,但很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值4.基于消息的异步套接字编程Windows套接字在两种模式下执行I/O操作:阻塞模式和非阻塞模式.在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回(也就是不地将控制权交还给程序),例如,程序中调用了recvfrom函数后,如果这时网络上没有数据传送过来,该函数就会阻塞程序的执行,从而导致调用线程暂停运行,但不会阻塞主线程运行.在非阻塞模式下,Winsock函数无论如何都会立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知信息可以判断该操作是否正常完成.Windows Sockets采用了基于消息的异步存取策略以支持Windows的消息驱动机制,Windows Sockets的异步选择函数WSAAsyncSelect提供了消息机制的网络事件选择,当使用它登录的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,指示发生的网络事件,以及与该事件相关的一些信息.因此可针对不同的网络事件进行登录,一旦有数据到来,就会触发这个事件,操作系统就会通过一个消息来通知调用线程,后者就可以在相应的消息响应函数中接收这个数据.因为是在该数据到来之后,操作系统发出的通知,所以这时肯定能够接收这个数据.异步套接字能够有效的提高应用程序的性能.一些主要函数<1>//为指定的套接字请求基于Windows消息的网络事件通知.自动设置为非阻塞模式int WSAAsyncSelect(SOCKET s, //标识请求网络事件通知的套接字描述符HWND hWnd, //标识一个网络事件发生时接收消息的窗口的句柄unsigned int wMsg, //指定网络事件发生时窗口将接收到的消息,(自定义消息)long lEvent //指定网络事件类型,可以位或操作组合使用);<2> 获得系统中安装的网络协议的相关信息int WSAEnumProtocols(LPINT lpiProtocols,//[in]以NULL结尾的协议标识号数组.如果为NULL,返回可用信息LPWSAPROTOCOL_INFO lpProtocolBuffer,//[out]存放指定的完整信息ILPDWORD lpdwBufferLength//[in,out]输入时传递缓冲区长度,输出最小缓冲区长度);<3>初始化进程使用的WS2_32.DLLint WSAStartup(WORD wVersionRequested,//高位字节指定Winsock库的副版本,低位字节是主版本号LPWSADATA lpWSAData//[out]用来接收Windows Sockets实现细节);<4> 终止对套字库WS2_32.DLL的使用int WSACleanup (void);<5> Winsock库中的扩展函数WSASocket将创建套接字SOCKET WSASocket(int af,//地址簇标识int type,//socket类型SOCK_DGRAM为UDPint protocol,//协议簇LPWSAPROTOCOL_INFO lpProtocolInfo,//定义创建套接字的特性,如果为NULL,则//WinSock2.Dll使用前三个参数决定使用哪个服务提供者GROUP g,//保留DWORD dwFlags//指定套接字属性的描述,如果为WSA_FAG_OVERLAPPED则为一个重叠套接字,与文件中相似,);然后在套接字上调用WSASend, WSARecv,WSASendTo,WSARecvFrom,SWAIoctl 这些函数都会立即返回,这些操作完成后,操作系统会通过某种方式来通知调用线程,后者就可以根据通知信息判断操作是否完成<6> WSARecvFrom接收数据报类型的数据,并保存数据发送方的地址int WSARecvFrom(SOCKET s,//套接字描述符LPWSABUF lpBuffers,//指向WSABUF数据指针,一个成员缓冲区指针buf,另个长度DWORD dwBufferCount,//lpBuffers数组中WSABUF结构体的数上,一般为1LPDWORD lpNumberOfBytesRecvd,//[out]接收完成后数据字节数指针LPDWORD lpFlags,//[in/out]标志会影响函数行为,设置为0即可struct sockaddr FAR *lpFrom,//[out]可选,指向重叠操作完成后存放源地址的缓冲区LPINT lpFromlen,//[in/out]指定lpFrom缓冲区大小的指针LPWSAOVERLAPPED lpOverlapped,//指向重叠套接字指针,非重叠忽略LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//一个指定接收完成时调用的完成全程指针(非重叠套接字的忽略0);如果创建是重叠套接字,最后两个参数值要设置,因为这时将会采用重叠I/O,函数会返回,当接收数据这一操作完成后,操作系统会调用lpCompletionRoutine参数指定的例程来通知调用线程,这个例程就是一个回调函数.<7>WSASendTo发送数据报类型的数据int WSASendTo(SOCKET s,//套接字描述符LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesSent,DWORD dwFlags,//0即可const struct sockaddr FAR *lpTo,//可选指针,指向目标套接字的地址int iToLen,//lpTo中地址长度LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);5.一个网络聊天室程序的实现新建工程基于对话框,工程名为Chat,并添加一些控件主要两个编辑,IP控件和发送按钮[1]加载套接字库需要加载套接字库并进行版本协商,AfxSocketInit只能加载1.1版本的套接字库,本例使用WSAStartup加载系统安装可用版本,在CChatApp的initInstance函数加入并在stdafx.h文件中加入头文件#include <winsock2.h>[2]创建并初始化套接字在CChatDlg类增加一个SOCKET类型的成员变量,m_socket,高为私有,再添加一个BOOL类型的成员函数:InitSocket,初始化该类的套接字成员BOOL CChatDlg::InitSocket(){//使用扩展函数创建套接字m_socket = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, NULL, 0);if (INVALID_SOCKET == m_socket){MessageBox("创建套接字失败!");return FALSE;}//要绑定套按字的本地址和协议簇,端口号SOCKADDR_IN addrSock;addrSock.sin_addr.S_un.S_addr = htonl(ADDR_ANY);addrSock.sin_family = AF_INET;addrSock.sin_port = htons(6000);//绑定套接字到本地套按地址上if(SOCKET_ERROR == bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR))){ MessageBox("绑定失败!");return FALSE;}//调用WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)为网络事件定义消息! //此时如果发生FD_READ网络事件,系统会发送UM_SOCK(自定义)消息给应用程序! //使用相应的消息响应函数来处理,程序并不会阻塞在这儿了!CChatDlg类的OnInitDialog函数中调用这个函数,完成套接字的初始化工作[3]实现接收端的功能在CChatDlg头文件中定义自定义的消息:UM_SOCK#define UM_SOCK WM_USER + 1在CChatDlg头文件中添加UM_SOCK响应函数原型声明protected:HICON m_hIcon;// Generated message map functions//{{AFX_MSG(CChatDlg)virtual BOOL OnInitDialog();afx_msg void OnSysCommand(UINT nID, LPARAM lParam);afx_msg void OnPaint();afx_msg HCURSOR OnQueryDragIcon();afx_msg void OnBtnSend();//}}AFX_MSG//定义的消息要带参数,LPARAM中的低字节是保存网络事件(如FD_READ),//高字节保存错误信息,WPARAM保存是发生网络事件的SOCKET标识afx_msg void OnSock(WPARAM, LPARAM);//自定义消息的响应函数原型DECLARE_MESSAGE_MAP()在CChatDlg类的源文件中添加UM_SOCK消息映射BEGIN_MESSAGE_MAP(CChatDlg, CDialog)//{{AFX_MSG_MAP(CChatDlg)ON_WM_SYSCOMMAND()ON_WM_PAINT()ON_WM_QUERYDRAGICON()ON_BN_CLICKED(IDC_BTN_SEND, OnBtnSend)//}}AFX_MSG_MAPON_MESSAGE(UM_SOCK, OnSock)//消息与其响应函数的映射END_MESSAGE_MAP()消息响应函数的实现,因为同时可以请求多个网络事件如FD_READ或RDWRITE最好对所接受的消息进行判断后处理,本例中只有FD_READ,但仍判断处理,要注意是消息接收两个参数,低字节是保存网络事件(如FD_READ),高字节保存错误信息,WPARAM保存是发生网络事件的SOCKET标识.switch (LOBYTE(lParam)){case FD_READ://发生是网络读取事件WSABUF wsaBuf;char recvBuf[200];wsaBuf.buf = recvBuf;wsaBuf.len = 200;DWORD dwRead;DWORD dwFlag = 0;SOCKADDR_IN addrFrom;int len = sizeof(SOCKADDR);if(SOCKET_ERROR == WSARecvFrom(m_socket, &wsaBuf, 1, &dwRead, &dwFlag, (SOCKADDR*)&addrFrom, &len, NULL, NULL)){MessageBox("接收网络数据失败!");return;}CString strRecv;CString strTemp;strRecv.Format("%s 说: %s", inet_ntoa(addrFrom.sin_addr), recvBuf);GetDlgItemText(IDC_EDIT_RECV, strTemp);strRecv += "\r\n";strRecv += strTemp;[4]发送端按钮的实现[5]终止套接字库的使用为CChatApp类增加一个析构函数,主要是在此函数中调用WSACleanup函数,终止对套接字库的使用[6]在CChatDlg类中关闭套接字,添加一个析构函数,首先判断是否该套接字库有值,如果有的话关闭套接字4. 利用主机名实现网络访问struct hostent FAR *gethostbyname(const char FAR *name //从主机名中获取IP地址);Hostent结构体:struct hostent {char FAR * h_name;char FAR * FAR * h_aliases;short h_addrtype;short h_length;char FAR * FAR * h_addr_list;//空中止的IP地址列表,是一个char*字符数组,因为一个//主机可能有多个IP,选择第一个即可};由主机IP转换成主机名struct HOSTENT FAR * gethostbyaddr(const char FAR *addr,//指向网络字节序表示的IP地址指针int len,//地址长度,对于AF_INET必须为4int type//类型AF_INET);接收方部分代码可改为;。
套接字编程基本原理讲解
套接字编程原理一、客户机/服务器模式在网络中最常用的通信模式是客户机/服务器模式(Client/Server模式或C/S模式)。
服务器方要先启动,并监听指定端口,等待客户端的请求,根据客户端的请求提供相应服务。
二、基本套接字一般来说,要进行网络通信,必须要在网络的每一端都要建立一个套接字,两个套接字之间是可以建立连接的,也是可以无连接的,并通过对套接字的“读”、“写”操作实现网络通信功能。
类似于文件的打开、读、写、关闭的方式。
套接字有三种类型:数据流套接字(SOCK_STREAM):对应TCP协议。
数据报套接字(SOCK_DGRAM):对应UDP协议。
原始套接字(SOCK_RAW)。
通过使用原始套接字,可以将网卡设为混杂模式。
并且可以捕获到的数据包不仅仅是单纯的数据信息,而是包含有IP头、TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌,通过对这些在低层传输的原始信息的分析可以得到更多网络的信息。
一个完整的网间通信需要一个五元组来标识:(协议,本地地址,本地端口号,远地地址,远地端口号)三、基本套接字系统调用为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明。
1.创建套接字──socket()应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:SOCKET PASCAL FAR socket(int af, int type, int protocol);参数af:指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是互连网区域。
参数type:描述要建立的套接字的类型。
参数protocol:说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的协议。
根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。
孙鑫老师VC++教学视频学习另一篇笔记
孙鑫老师VC++教学视频学习另一篇笔记<< 一:掌握C++>>1:struct 或者 class的定义完成后面一定不要忘记分号!2: 可以这么写:cout<class test{public:test(){cout<<"aaa"<using namespace std;class test{public:test(){cout<<"aaa"<>1:_tWinMain实际上是一个宏,其等于WinMain!2:_tWinMain()的定义位于文件appmodul.cpp中,而CWinapp的定义位于appcore.cpp中,AfxWinMain()在winmain.cpp当中.而负责注册窗口类的函数AfxEndDeferRegisterClass()位于wincore.cpp当中.至于窗口类的定义,在MFC 中,微软为我们定义了好几个默认的窗口类.3:所谓CWnd对象,只是在CWnd类中封装了创建窗口的过程然后保存了生成的窗口句柄而已,然后通过构造一个它的对象来启动那些过程,并不是说CWnd对象与窗口有什么固定的关系.又因为CMainFrame与CView都是从CWnd派生而来的,所以CMainFrame 与CView对象之于窗口的关系也差不多.也就是说,一个窗口关闭了,并不代表那个对象一定也销毁了.还可以从那个对象出发,再调用一遍其中的函数生成窗口,但是若是对象都销毁了,则窗口一定不会存在!CMainFrame对应窗口的标题栏及菜单栏为非客户区,工具栏及以下为客户区.而CView对象对应窗口的客户区为工具栏以下区域.4:具体追踪过程见TrackMfc 工程!5:为了修改程序的背景色,我在View类的PreCreateWindow()中新定义了一个窗口类如下:WNDCLASS wndcls;wndcls.cbClsExtra=0;wndcls.cbWndExtra=0;wndcls.hbrBackground=(HBRUSH)::CreateSolidBrush(RGB(2 08,221,238));wndcls.hCursor=::LoadCursor(NULL, IDC_ARROW);wndcls.hIcon=LoadIcon(::AfxGetInstanceHandle(),MAKEINT RESOURCE(IDI_MY ICON));wndcls.hInstance=AfxGetInstanceHandle();wndcls.lpfnWndProc=(WNDPROC)::DefWindowProc;wndcls.lpszClassName="wndcls";wndcls.lpszMenuName=NULL;wndcls.style=CS_HREDRAW | CS_VREDRAW;cs.lpszClass="wndcls";却发现一启动程序就弹出一个消息框说"建立空文档失败!".几番查找,才发现还要将自定义的窗口类进行注册才可以,在最后一句前面加上一句::RegisterClass(&wndcls);问题就解决了,背景色也变了,可是窗口图标还是没有变成我自画的IDI_MYICON,怎么回事呢?原来改变图标要在MainFrame类的PreCreateWindow()中进行,可是只改变个图标定义一个窗口类太过麻烦了,可以用下面的方法:cs.lpszClass=AfxRegisterWndClass(NULL,NULL,NULL,AfxGetApp()->LoadIcon (IDI_MYICON));使用了一个AfxRegisterWndClass()的函数.当然在View类中做一个大大的窗口类也是相当麻烦的,所以也可以使用AfxRegisterWndClass()函数!只用一句:cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW |CS_VREDRAW,::LoadCursor(NULL,IDC_CROSS,(HBRUSH)::CreateSolidBrush(RGB(208,221,238)), 0);即可!做完上面这些,我通过import重新载入了一个ICON,在ResourceView窗口中直接删除了原本自己画的IDI_MYICON,然后将新载入的ICON改ID 为IDI_MYICON,然后重新编译,却得不到想要的效果,然后我在Build菜单下点击了Rebuild All,编译完成后新图标才显示出来,具体原因估计跟预编译有关!6:还有就是即使如上所示改变了窗口左上角的那个图标,可应用程序自身的图标却还是那个MFC标准图标,这时,你只需将你想要的图标放到工程的res文件夹下,交将之改名为标准MFC图标的名称,再做个Rebuild All就可以将之更改!之于上面的窗口类定义中菜单怎么可以定义为NULL,是因为由MFC产生的代码,菜单的创建是在App类的InitInstance()中在构造单文档模板的时候将菜单的资源标识传递过来,并进行相关的创建工作的!所以上面传递NULL并不会影响菜单的创建的. (相关更多知识见第八课的笔记)<< 三: GDI编程 >>1:如果你想画一个虚线线条,也就是说线形为PS_DASH,那么线宽必须是1才行.具有同样要求的还有PS_DOT...2:如何创建一个透明画刷CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NU LL_BRUSH));这就可以了,然后选入DC使用即可!3:关于画点,我们一般用SetPixel()函数,可是你会发现不管你把画笔的线宽设为几,画出来的点都是一个像素大小,但通常我们希望我们指定4的大小时可以画一个比原先大小大相应程度的点,这时,我想到的办法就是画点部分用画圆函数来实现,只要把填充画刷设置合理即可.不知还有没别的办法.<< 四:文本编程 >>1:创建普通插入符用CreateSolidCaret(),而创建位图插入符用CreateCaret()函数.2:路径层(Path)的概念!3:我试图写一个MyNotePad的小应用程序,已完成的任务有:设置自定义的图标,窗口背景,光标.在窗口显示插入符,并让插入符随鼠标的点击而显示在相应的位置,用TextOut完成串的输入,显示,并保存于一个CString对象中,可是我发现显示文本的背景色(默认是白色)与自定义的窗口背景色不一致,我先是用如下的代码:hdc.SetBkColor(hdc.GetBkColor));来设置文本背景色,可是背景色仍然是默认的白色,我不停地想是不是SetBkcolor()不可以用,但当时真是笨,明明用GetBkColor()取到的就是文本的背景色,你再设置回去,那不就相当于什么都没做嘛!当时脑子中充斥的想法是以为GetBkColor()取到的是窗口的背景色呢!呵呵,笨!另外,hdc.SetTextColor()可以设置文本的颜色.但还有一个问题:下面是我处理退格键的代码:void CMyNotePadView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {// TODO: Add your message handler code here and/or call defaultif(0x0d==nChar){// m_ptOrigin}else if(0x08==nChar){CClientDC hdc(this);hdc.SetBkColor(RGB(208,221,238));hdc.SetTextColor(RGB(208,221,238));hdc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);m_strLine=m_strLine.Left(m_strLine.GetLength()-1);}else{m_strLine+=nChar;}::InvalidateRect(m_hWnd,NULL,FALSE);CView::OnChar(nChar, nRepCnt, nFlags);}为了应付窗口切换时的重绘,我把输出工作放在了OnDraw()函数中了,别的都是正常的,可当退格的时候后面的那个残留的插入符没有消失,仍然保留着,我找了半天找不到原因,最后才发现原来调用::InvalidateRect()来启动OnDraw()时,第三个参数如果传FALSE,会导致原来无效区域的背景不刷新,然后我将之换成TRUE就OK了!4:至于类似卡拉OK字幕的平滑变色效果,可以用DrawT ext()来实现.<< 五:菜单编程 >>1:消息的分类及路由形态:(1)标准消息:除WM_COMMAND之外,所有以WM_开关的消息都是标准消息.(2)命令消息:来自菜单,加速键或工具栏按钮的消息.这类消息都是以WM_COMMAND呈现.在MFC 中,通过菜单项的标识ID来区分不同的命令消息,在SDK中,则是以wParam参数识别.(3)通告消息:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生.这类消息也是以WM_COMMAND形式呈现.关于消息的接收:只要是从CWnd派生的类,都既可以接收标准消息,又可以接收命令消息以及通告消息.而其它从CCmdTarget派生的类,就只能接收命令消息与通告消息,而不能接收到标准消息.另外,从视频教材的第一个菜单响应例子的实验证明,一个WM_COMMAND消息,是首先被CMainFrame类接收到,但它先交由它的子窗口View类处理,如果View类没有相应的处理函数,就由View 类将此消息交由Doc类处理,如果Doc类也没有相应的处理函数,那么Doc类会将消息返还给View类,而View类再将消息返还给MainFrame类,这时才检查MainFrame类中有没有相应的消息处理函数,如果仍然没有的话,它就将消息上还给了App类处理(如果在这里仍没有处理函数,则回到CCmdTarget类,然后由FrameWork会将消息传递给::DefWindowProc()函数,注意这儿绕过了CWinThread,因为CWinThread并不参与消息路由!)并且一个消息一旦在某个类中被响应过,则不再接着传递!(关于这个看<<深入浅出MFC>>第9章的图9-4会很清楚!)而对于一个标准WM_消息,则由CMyView类先处理,如果没有处理函数,则上溯到CView,如果仍没有处理函数,则上溯到CWnd类,如果仍没有处理函数,则上溯到CCmdTarget了.这就是消息的路由过程!2:如何在菜单项上加上对号标记:在MainFrame类的OnCreate()中最后加入GetMenu()->GetSubMenu(3)->CheckMenuItem(0,MF_BYPO SITION | MF_CHECKED);另外,在响应刷新消息的函数中由MFC传入的那个CCmdUI*指针也可以用于此!3:如何在菜单项中设置默认菜单项:同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->SetDefaultItem(0,TRUE);即可.要注意,一个子菜单中只可以有一个默认(缺省)菜单项.以最后一个设置为默认的菜单项为准.4:如何设计图形标记菜单:同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->SetMenuItemBitmap(,,,);要注意,一个菜单的图形标记只能是13*13大小的位图,这个数据可以通过GetSystemMetrics(SH_CXMENUCHECK);或者GetSystemMetrics(SH_CYMENUCHECK);来获取!5:如何Enable或者Disable一个菜单项同样在MainFrame类的OnCreate()的最后加入GetMenu()->GetSubMenu(3)->EnableMenuItem(,);要注意,菜单项的Enable或者Disable功能的正常完成需要在MainFrame类的构造函数中将一个叫m_bAutoMenuEnable的成员变量设置为FALSE.但是如果做了m_bAutoMenuEnable=FALSE;后,会导致CMainFrame so no ON_UPDATE_COMMAND_UI or ON_COMMAND handlers are needed!6:如何让一个菜单显示或不显示:要让菜单不显示,在MainFrame类的OnCreate()的最后加入SetMenu(NULL);即可!要让菜单显示,可以先构造一个CMenu对象,再利用该对象的LoadMenu(ID of menu)函数将之加载,然后再用SetMenu(该menu 对象的指针)即可!要注意,你的CMenu对象必须是一个定义在CMainFrame类中的成员变量,不能是一个在OnCreate()函数中临时定义的一个变量,因为该函数结束时其中的成员变量要发生析构,那么你的菜单就要出问题.或者你也可以定义临时函数内的局部变量,然后在SetMenu()完了后用一个menu.Detach()成员函数将菜单与菜单对象的关系切断,这样也是可以的.7:MFC之于菜单项的命令更新机制:菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象.我们可以通过手工或者利用ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息.在后台所做的工作是:操作系统发出WM_INITMENUPOPUP消息,然后由MFC 的基类如CFrameWnd接管.它创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数DoUpdate().这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针.同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项.更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目.用我自己的话来讲就是:命令更新机制就是当无论何时要显示菜单项时,系统都会对CMainFrame类发出一个包含每个菜单项的UPDATE_COMMAND_UI消息,如果你在CMainFrame类中针对某个菜单项做了相应的消息处理函数,那么你可以在这个函数中检测程序中的某些条件,做出让某个菜单项显示或不显示或者变灰的决定!8:如何制作右键弹出菜单:方法一:在VC中,点菜单上的Project/Add To Project/Components and Controls...,在弹出的对话框中打开Visual C++ Components文件夹,再找到Pop-up Menu,选择Insert按钮,在随后的确认框中点确定,然后在随后的 Add pop-up menu to:下拉列表框中选择View类,资源ID可以不改,按OK,然后重新编译工程就可以看到结果了.方法二:其实在方法一中,MFC在后台所做的就是在View类中加入了一个函数:OnContextMenu()函数如下:void CMyNotePadView::OnContextMenu(CWnd*, CPoint point){// CG: This block was added by the Pop-up Menu component {if (point.x == -1 && point.y == -1){ //keystroke invocationCRect rect;GetClientRect(rect);ClientToScreen(rect);point = rect.TopLeft();point.Offset(5, 5);}CMenu menu;VERIFY(menu.LoadMenu(CG_IDR_POPUP_MY_NOTE_PAD_VI EW));CMenu* pPopup = menu.GetSubMenu(0);ASSERT(pPopup != NULL);CWnd* pWndPopupOwner = this;while (pWndPopupOwner->GetStyle() & WS_CHILD)pWndPopupOwner = pWndPopupOwner->GetParent();pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWndPopupOwner);}}这就使得我们也可以模仿以上行为做出自己的弹出菜单,首先在View类中添加对右键的响应函数,在其中也写如下代码:CMenu menu;menu.LoadMenu(IDR_MENU1);//这个ID所对应的菜单你要事先做好CMenu* pPopup=menu.GetSubMenu(0);pPopup->TrackPopupMenu(PM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);就可以了.但是现在的菜单显示的位置很离谱,原来显示弹出菜单用得是屏幕坐标,而你在View类中捕获的位置坐标是窗口坐标,这时就得用这个函数:ClientToScreen(point);在TrackPopupMenu()之前做个转换!这样就正常了.9:右键弹出菜单项的响应:在响应右键弹出菜单时,你可以在View类中响应,也可以在MainFrame类中响应,这主要取决于你在做pPopup->TrackPopupMenu()时的第四个参数,也就是该弹出菜单的父窗口.如果如上是this,则只能在View 类中响应,如果用GetParent()设其为MainFrame类,则既可以View类中响应,又可以在MainFrame类中响应.并且如果View类跟MainFrame类皆有响应函数,则优先响应View类,其实也就是说MainFrame中的响应函数将被忽略.所以为了方便,弹出菜单的父类就尽可能设为MainFrame类!10:如何在菜单栏上动态增加,插入或删除一个子菜单因为以前菜单栏上的子菜单都是在菜单编辑器中事先做好的,而现在要求在程序运行期间按需添加一个子菜单到指定位置.可以用如下代码段实现:CMenu menu;menu.CreatePopupMenu();GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMe nu,"动态菜单");menu.Detach();如上的代码会将新子菜单放在所有已有子菜单项的后面,如果要想在指定的位置插入子菜单,则应该用InsertMenu()而不是AppendMenu(),示例如下:CMenu menu;menu.CreatePopupMenu();GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMe nu,"动态菜单");menu.Detach();menu.CreatePopupMenu();GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"插入菜单");menu.Detach();要在新加入的子菜单下面显示菜单项的示例代码如下:CMenu menu;menu.CreatePopupMenu();GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMe nu,"动态菜单");menu.Detach();menu.CreatePopupMenu();GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"插入菜单");menu.AppendMenu(MF_STRING,111,"How");menu.AppendMenu(MF_STRING,112,"I");menu.AppendMenu(MF_STRING,113,"Feel?");menu.Detach();要在已有子菜单中添加菜单项同样可以用AppendMenu()或者InsertMenu()它们的区别自然是前者在最后放置,后者由你指定位置.示例代码如下:GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,1 14,"动态");GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,M F_BYCOMMAND |MF_STRING,115,"安岳");以上函数中若使用MF_SEPARATOR参数可以插入分割符!要删除一个子菜单或者菜单项可以使用DeleteMenu()函数.如果调用它的是菜单栏,则删除的是子菜单,如果调用它的是子菜单,则删除的是菜单项.至于这种动态菜单项的命令响应则不能再借助于ClassWizard啦,跟手动添加对其它消息的响应一样,需要三个步骤:一:添加响应函数声明在CMainFrame的构造函数中,紧随系统原有的afx_msg int OnCreate();之类的语句之后写上:afx_msg void OnHow();但注意不要写在括住系统自己的响应函数的//{{AFx_MSG...及//}}AFX_MSG之内了.二:添加消息映射在MainFram.cpp中找到以(CMainFrame,CFrameWnd)为参数的BEGIN_MESSAGE_MAP,END_MESSAGE_MAP宏对,在其中添加: ON_COMMAND(IDM_HOW,OnHow)注意这一句是宏语句,不要在其后面加分号.(也注意不要写在//{{AFx_MSG...及//}}AFX_MSG之内了.)因为这个宏中需要ID,所以自己还要手动的在Resource.h中加入:#Define IDM_HOW 111 一句.(111是AppendMenu()时指定的ID).三:完成消息响应函数在MainFrame.cpp文件后面写如下代码段:void CMainFrame::OnHow(){MessageBox("Hey man,what are you doing!");}即完成了此命令消息响应.11:在本课的最后,为了实现一个在CMainFrame中截获WM_COMMAND消息,而不让它下流到View 类中去的方法:通过重载CWnd::OnCommand()函数!还介绍了一个非常典型的头文件包含编译错误的解决方法,LOWORD()宏的使用!以及如何在CMainFrame类的成员函数中调用CView类中的成员变量的问题-GetActiveView()的使用!<< 六:对话框编程 >>1:分清模态,非模态,系统模态对话框!2:在View类的cpp中响应菜单消息,利用已有的对话框类来产生对话框别忘了在该cpp中包含相应定义对话框类的头文件.3:制作非模态对话框要使用成员变量或者堆上分配的对象,不能在函数内定义一个局部变量来用.示例代码如下:dlg.Create(IDD_MYDIALOG,this);dlg.ShowWindow(SW_SHOW);4:注意一个模态对话框当点击其中的OK按钮时这个对话框是被Destroy了的,而对于一个非模态对话框而言,它只是隐藏了,而并未被Destroy,所以如果你定义了一个非模态对话框,并使用了原有的OK按钮,你一定要重载其基类CDialog 的OnOK()函数,在其中自己调用DestroyWindow().详见MSDN中CDialog::OnOK()函数的讲解页!5:静态文本控件的消息响应要注意一两点:一:要为静态文本控件指定具体的ID,而不能是默认的IDC_STATIC;二: 要在静态文本控件的属性页中的Styles页中将Notify前面的勾打上,才能实现消息响应.下面是一段静态文本控件的操作代码:(设置静态文本的内容);DWORD elapsedTime;elapsedTime=GetTickCount()/1000; //get secondsUINT hours=elapsedTime/3600;UINT minutes=elapsedTime%3600/60;UINT seconds=elapsedTime%3600%60;char times[100];sprintf(times,"系统已运行%d小时%d分钟%d秒!",hours,minutes,seconds); GetDlgItem(IDC_TIME)->SetWindowT ext(times);是在对话框类的成员函数中所做的.6:获取控件文本的几种方法:一:GetDlgItem()->GetWindowText();二:GetDlgItemText();三:GetDlgItemInt();这个函数取到控件文本并将之转换成int返回给调用者.这对如使用EDIT控件获取整数非常方便,并且它可以有效处理有符号数.这个函数的调用稍有复杂,查阅MSDN!四:将控件与成员变量相关联,比如要从EDIT控件获取整数,可以直接将EDIT控件与int类型的变量相关联,它会直接获取整数,并自动做相关的输入数据的类型检测,当然也可以关联CEdit型变量,再间接获取数据,用这种方法时要注意UpdateData()函数的适时调用!如果是CEdit 控件,可以用CEdit中的GetWindowT ext()函数获取其内容!五:通过发送WM_GETTEXT或者WM_SETTEXT消息的方式获取: 分为三种情况:(1)使用合全局的::SendMessage(),如下:::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd,WM_GET TEXT,10,(LPARAM)ch1 );//ch1是一个字符数组或者是:::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,(LPARAM )ch1); //这是已关联控件变量的情况下(2)使用CWnd的SendMessage(),如下:GetDlgItem(IDC_EDIT1)->SendMessage(WM_GETTEXT,10,(L PARAM)ch1);或者是:m_edit1.SendMessage(WM_GETTEXT,10,(LPARAM)ch1);(3):使用CWnd::SendDlgItemMessage()函数,它实际上相当于先用GetDlgItem()得到控件句柄,再SendMessage(),是一种组合的方便方式!如:SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10,(LPARAM)ch1 );在讲这个的时候,还讲到了可以获得一个控件中选中的内容的消息-EM_GETSEL以及可以设置哪些内容被选中的EM_SETSEL消息.使用方法见MSDN.如: SendDlgItemMessage(IDC_EDIT1,EM_SETSEL,1,3);m_edit1.SetFocus();//这句的意义是因为如果该控件不是当前的FOCUS所在,那么即使显示了选中也会不可见.7:实现对话框收缩与扩展功能的代码示例:void CTestDlg::OnButton1(){// TODO: Add your control notification handler code here CString str;GetDlgItemText(IDC_BUTTON1,str);static CRect rectLardge;static CRect rectSmall;if(rectLardge.IsRectNull()){CRect rectSeparator;GetWindowRect(&rectLardge);GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSepar ator);rectSmall.top=rectLardge.top;rectSmall.left=rectLardge.left;rectSmall.right=rectLardge.right;rectSmall.bottom=rectSeparator.bottom;}if(str=="收缩<<"){SetDlgItemT ext(IDC_BUTTON1,"扩展>>");SetWindowPos(NULL,0,0,rectSmall.Width(),rectSmall.Height( ),SWP_NOMOVE | SWP_NOZORDER);}else{SetDlgItemT ext(IDC_BUTTON1,"收缩<<");SetWindowPos(NULL,0,0,rectLardge.Width(),rectLardge.Heig ht(),SWP_NOMOVE | SWP_NOZORDER);}}8:这节课最后讲了如何实现回车时让输入焦点在各个控件之间遍历的方法,其中涉及到了默认键消息响应函数的操作,以及如何用SetWindowLong()来改变一个控件的窗口响应函数的方法.<< 七:对话框编程之二 >>1:要改变对话框内控件上的文本字体,要在对话框的属性对话框中设置,而不是在单个对话框的属性中设置!2:逃跑按钮的制作放置两个外观一样的按钮,初始化为隐藏其中的一个,在对话框类中响应MouseMove消息,一旦检测到鼠标位于某个当前正在显示的按钮上时,就隐藏之,将另一个按钮显示出来.这样给用户的感觉就是只有一个按钮,在躲藏你的鼠标! 而录像中的做法是新建一个继承自CButton的类,然后将两个按钮分别与该新类的一个对象所关联,然后由这个类实现对MouseMove的响应,并在每个对象中放置一个指向本类的公有成员指针变量,指向另一个对象,这样,在响应函数中很方便地实现了ShowWindow(SW_HIDE),以及ShowWindow(SW_SHOW)操作.至于那两个对象中指向对方的指针的初始化可以放在控件所在对话框类的构造函数中,也可以该对话框类中加入一个对WM_INITDIALOG的消息的响应函数,将初始化放置于其中!3:往对话框或其它地方插入组合框(combo box)资源时,初始放置时一定要将它拖大一些,要不然放置好以后其高度无法修改,而且下拉出的列表框部分无法显示出来,只能重新加入一个!4:关于属性表单对话框的制作,一:要插入属性页,并做好控件布局;二:要为每个属性页关联相应的继承自CPropertyPage类的自定义类.三:要在类视图中点根工程图标右键,加入一个类型为MFC Class的新类,其基类为CPropertySheet.四:在该类中分别加入每个属性页类的一个对象.(注意头文件的包含)五:在该类的两个构造函数中都通过AddPage()函数加载每个属性页对象.六:在某个菜单项的响应函数中定义一个该类的对象(其参数为属性表单的标题),然后调用该类的成员函数:DoModal()创建一个模态的表单,或者Create()创建一个非模态的表单.(注意头文件的包含);5:关于向导对话框的制作:向导对话框实际是属性表单的一种变体,只要在属性表单的DoModal()调用生成属性表单之前加上一句:propsheet.SetWizardMode();生成的就是向导对话框而不是表单了.如何去掉默认生成向导对话框第一页中的上一步按钮:在该页的类中增加一个虚函数OnSetActive(),在其中加入如下语句:((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB _NEXT);即可.如何去掉默认生成向导对话框最后一页的下一步按钮,并且加上结束按钮: 同上理: 在相应的类中增加虚函数OnSetActive(),加入如下语句:((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB _BACK | PSWIZB_FINISH);注意:如果你在第一页中将上一步按钮去掉了,则后面每一页的上一步按钮都没有了(我觉得这样不太合理)所以你要用同样的方法设置每一个属性页,在其中设置合适的按钮!6:本次课程中提到了用C语言函数memset初始化数组的一种方法很好,如: memset(m_bLike,0,sizeof(m_bLike));<<八:界面修改,工具栏,状态栏,启动画面的制作>>1:如何修改单文档应用程序的窗口标题,查阅MSDC文章:Changing the styles of a window created by MFC.要在MainFrame的PrecreatWindow()中加入如下代码:cs.style&=~FWS_ADDTOTITLE;cs.lpszName="This is a test!";可以先不要上一句试一试!另一种方法是:cs.style=WS_OVERLAPPEDWINDOW;再进行修改,也可以不修改,那么是去掉默认文档标题,而只显示原程序标题!另一类方法是在窗口创建后再修改,因为在OnCreate中,开始的这些代码: if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1;if (!m_wndT oolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC)|| !m_wndT oolBar.LoadToolBar(IDR_MAINFRAME)){TRACE0("Failed to create toolbar\n");return -1; // fail to create}if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT))){TRACE0("Failed to create status bar\n");return -1; // fail to create}// TODO: Delete these three lines if you don't want the toolbar to// be dockablem_wndT oolBar.EnableDocking(CBRS_ALIGN_ANY);EnableDocking(CBRS_ALIGN_ANY);DockControlBar(&m_wndT oolBar);完成了窗口创建,工具栏,状态栏的创建等工作,可以在后面利用一个系统全局函数SetWindowLong()函数进行修改:加入代码为:SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWIN DOW);与此相对,还有一个GetWindowLong()函数可供使用!如下面代码去掉了窗口上的最大化按钮:SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_ hWnd,GWL_STYLE) &~MAXIMIZEBOX);当然SetWindowLont()还可以做别的修改.与SetWindowLong()相类似的另一个系统全局函数为SetClassLong();2:如何完成一个动画图标其实就是准备好几个图标,在定时器消息响应中更改图标即可完成.第一步是准备好几个(如三个)图标.第二步是在MainFrame类中做三个图标类的相关对象的成员变量,或者是一个大小为3的HICON数组.第三步是在MainFrame类的OnCreate()函数中LoadIcon()进行对三个图标的加载.其中用到的实例句柄的获取有三种方法: 一:用全局函数AfxGetInstanceHandle()获取,二:先在MainFrame文件中用extern声明一下全局对象theApp,然后使用theApp.hInstance;三:使用全局函数AfxGetApp()获取全局对象theApp对象的指针,然后用AfxGetApp()->hInstance;第二个参数是一个字符指针,可我们只有图标的资源ID,所以要进行必要的转换:用MAKEINTRESOURCE宏!第四步是设置定时器,也在OnCreate()函数中定义:SetTimer(1,1000,NULL);第五步是在MainFrame中添加WM_TIMER消息响应,在其中加入代码:static int index=0;SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index] );index=++index%3;3:在工具栏上新加一个按钮,要让它与前一个按钮之间有一个分隔符,只需要将它轻轻向一旁拖动一点点再放开即可,而要删除工具栏上的一个按钮,你只是选中它再按DEL键是完不成的,它只是将按钮上的图案删除,所以删除一个按钮要将它拖动到工具栏之外,再松手!4:如何创建一个工具栏在MSDN 的关于CT oolBar的讲解页有详细说明!一:在CMainFrame中加入一个CT oolBar类对象的成员变量,二:插入工具栏资源,三:在CMainFrame的OnCreate()中加入:if (!m_MyToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |CBRS_SIZE_DYNAMIC)|| !m_MyToolBar.LoadToolBar(IDR_MYTOOLBAR)){TRACE0("Failed to create toolbar\n");return -1; // fail to create}m_MyToolBar.EnableDocking(CBRS_ALIGN_ANY);DockControlBar(&m_MyToolBar);各个函数调用及参数传递查看MSDN!5:如何让一个工具栏隐藏或显示:if(m_MyT oolBar.IsWindowVisible()){m_MyToolBar.ShowWindow(SW_HIDE);}else{m_MyToolBar.ShowWindow(SW_SHOW);}但这样做的结果是工具栏虽说隐藏了,但是工具条还在,所以还要在后面加上一句:ReCalcLayout();这样做还是有问题,如果工具栏没有停靠在边上而是一个单独的小窗口,那么只做上面的工作只使得工具栏上的按钮不见了,而那个小窗口还在,所以,还要调用一个函数:DockControlBar(&m_MyToolBar);经过上面这句,小窗口也如愿消失了,但问题还有一点,就是当用户将工具栏放置为一个小窗口时,再点击菜单,要让这个工具栏显示出来,当然我们应该将工具栏仍按用户先前的小窗口样式显示出来比较好,可是这次工具栏又自动停靠在客户区顶部了?这个功能如何实现呢?孙老师只是提示可以查MSDN中CToolBar的成员函数解决这个问题,并没细讲,所以我看了MSDN,发现有两个函数:CToolBar::IsFloating()利用这个函数可以判断一个工具栏是否处于浮动状态,另一个是CFrameWnd::FloatControlBar()这个函数可以让一个控制栏处于浮动状态,然后我在CMainFrame中加入了一个BOOL型的成员变量,在每次判断工具栏是否可见时用来记录工具栏是否处于浮动状态,然后在重新生成工具栏时根据它的置决定是否将工具栏设为浮动状态,但是第二个函数好像不太好使,所以我又换用了SetWindowPos()成员函数,可是也不能将它放置为一个独立的小窗口.显示和隐藏工具栏的第二种方法:用一个函数:CFrameWnd::ShowControlBar(),因为这个函数的固有特性,上面是if...else...判断就可以简化为一句代码:ShowControlBar(&m_MyToolBar,!m_MyToolBar.IsWindowVis ible(),FALSE); 并且我惊讶的发现,用这个函数时,上面提到的浮动工具栏让它在恢复的时候仍回复为浮动的问题自动解决了!哈哈,好.6:状态栏相关编程因为MFC自动生成的系统已经包含了一个状态栏,所以我们暂时仅限于已有状态栏的修改,而不是另外生成一个状态栏.状态栏最左边的那一长条,就是经常显示一些提示字符串的那部分叫做提示行,而右侧那三个小窗口是用来指示CapsLock,ScrollLock,NumLock开关的状态,称为状态指示器.状态栏跟工具栏一样,也是在CMainFrame类中定义并在OnCreate()中创建的. 下面的代码在状态指示器的最左边放置了两个小窗口,并在第一个小窗口中放置了一个时钟:同样的CMainFrame的OnCreate()中,CTime tm=CTime::GetCurrentTime();CString strTime=tm.Format("%H:%M:%S");CClientDC dc(this);CSize sz=dc.GetTextExtent(strTime);m_wndStatusBar.SetPaneInfo(1,IDS_TIMER,SBPS_NORMAL,s z.cx);//调整窗口大小。
网络编程之孙鑫vc套接字
SOCKETS (套接字)编程有三种,流式套接(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);
基于TCP的SOCKET编程是采用流式套接字(SOCK_STREAM).基于UDP采用的数据报套接字
char recvBuf[100];//和服务器端进行通信
recv(sockClient,recvBuf,100,0);
printf("%s\n",recvBuf);
send(sockClient,"This is lisi ",strlen("This is lisi ")+1,0);
1.TCP流式套接字的编程步骤在使用之前须链接库函数:工程>设置>LINK>输入WS2_32.LIB,
服务端程序:
1,加载套接字库
2.创建套接字(SOCKET)
3,将套接字绑定到一个本地地址和端口上(BIND)
4,将套接字设为监听模式,准备接收客户端请求(LISTEN)
while(1)
{
SOCKET sockConn = accept(sockSrv,(SOCKADR*)&addrClient,&len);//accept的第三个参数一定要有初始值.//等待客户请求到来当请求到来后,接受连接请求,返回一个亲的对应于此连接的套接字
//止时程序在此发生阻塞
if(LOBYTE(wsaData.wVersion) = 1 || HIBYTE(wsaData.wVersion) = 1)
孙鑫VC视频笔记_掌握CDC的文字处理程序的编写
1 首先在view类捕获WM_CHAR消息.
2 为了保存字符,我们去定义一个CString的对象,专门用来存储我们输入的字符。在view类增加一个成员变量,类型CString,名称m_strLine,权限public。
3 在view的构造函数,初始化CString的对象,把它赋一个空,先给它清空,m_strLine="";
//要获取一个字体的高度,要用到GetTextMetrics,所以首先的定义一个DC
CClientDC dc(this);//首先的定义一个DC
TEXTMETRIC tm;//定义TEXTMETRIC结构体,GetTextMetrics这个函数要用到
dc.GetTextMetrics(&tm);//获取字体的高度
根据当前设备描述表(DC)字体的来自小,来改变插入符。 要实现以上功能,首先要获取当前DC字体(文本)的信息,要获取文本的信息,可以用GetTextMetrics。
BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;
Points to the TEXTMETRIC structure that receives the metrics
那么就要在响应WM_PAINT的时候,将我们的文字再次输出。
在view中,就给我们提供了OnDraw函数,利用这个函数。可以解决以上的问题。也就是说想要我们,输出的文字,图型,在view类中,始终都被看到的话,就可以在OnDraw这个函数中处理。
另外,在函数被调用的时候,应用程序框架类,构造了一个CDC的指针pDC传进来[OnDraw(CDC* pDC)],可以方便我们不用构造DC了。
孙鑫C++教程 完整版
从变量的类型区分变量的用途
int x,y; x=30; y=30; //x和y既可以用来表示坐标点,也可以用来表示宽度和 高度,还可以用来表示身高和体重。 typedef int WIDTH typedef int HEIGHT WIDTH x; HEIGHT y; //好处:我们从变量的类型上就可以知道x和y是用来表 示宽度和高度。
2.掌握C++
C++的标准输入输出流
C++中提供了一套输入输出流类的对象,它们是cin 、cout和cerr,对 应c语言中的三个文件指针stdin、stdout、stderr,分别指向终端输入、 终端输出和标准出错输出(也从终端输出)。cin与>>一起完成输入 操作,cout、cerr与<<一起完成输出与标准错误输出。利用cin和cout 比C语言中的scanf和printf要方便得多,cin和cout可以自动判别输入输 出数据类型而自动调整输入输出格式,不必像scanf和printf那样一个 个由用户指定。使用cin,cout不仅方便,而且减少了出错的可能性。 对于输出来说,我们像以上方式调用就可以了,对于输入来说,我 们以如下方式调用即可: int i; cin>>i; 注意箭头的方向。在输出中我们还使用endl(end of line),表示换 行,注意最后一个是字符‘l’,而不是数字1,endl相当于C语言的 '\n',表示输出一个换行。
C++的特性
构造函数
1、构造函数最重要的作用是创建对象本身 。 2、C++规定,每个类必须有一个构造函数, 没有构造函数,就不能创建任何对象。
C++的特性
孙鑫VC学习笔记第16课
CString str;
CString strTemp;
HOSTENT *pHost;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
switch(LOWORD(lParam))
{
case FD_READ:
WSABUF wsabuf;
wsabuf.buf=new char[200];
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
}
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000); GetDlgItemText(IDC_EDIT_SEND,strSend);
5)然后完成消息响应!
头文件中:#define UM_SOCK WM_USER+1
afx_msg void OnSock(WPARAM,LPARAM);
源文件中:
ON_MESSAGE(UM_SOCK,OnSock)
实现消息响应函数:void CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
孙鑫vc学习笔记
孙鑫VC++视频笔记列表(全)简介第一课Windows程序内部运行机制第二课MFC程序框架的剖析第三课MFC消息映射机制和DC的获取第四课简单字处理软件第五课菜单编程第六课对话框编程 1第七课对话框编程 2第八课MFC中各类指针的获取第九课应用程序外观修改第十课图形绘制与通用对话框第十一课图形保存和重绘第十二课文件操作第十三课文档和串行化第十四课网络编程第十五课多线程和简单聊天室制作第十六课线程同步与异步套接字编程第十七课进程间通信第十八课ActiveX 控件第十九课动态链接库第二十课Hook钩子函数第0章简介Lesson1:Windows程序运行原理及程序编写流程,窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与cdecl 调用规范的比较,初学者常犯错误及注意事项。
Lesson2:C++经典语法与应用,类的编写与应用,构造与析构函数,函数的重载,类的继承,函数覆盖,基类与派生类的构造函数、析构函数先后调用顺序,如何在派生类构造函数中向基类的构造函数传递参数,this成员变量,类型转换的内幕,虚拟函数与多态性,引用和指针变量的区别与共同处。
VC工程的编译原理与过程,将工程中不同的类拆分到不同的文件中,每一个类由一个.h和.cpp文件共同完成,头文件重复定义问题的解决,培养了学员良好的编程习惯,也为以后分析MFC AppWizard生成的工程奠定了良好基础。
Lesson3:讲述MFC AppWizard的原理与MFC程序框架的剖析。
AppWizard是一个源代码生成工具,是计算机辅助程序设计工具,WinMain在MFC程序中是如何从源程序中被隐藏的,theApp全局变量是如何被分配的,MFC框架中的几个类的作用与相互关系,MFC框架窗口是如何产生和销毁的,对窗口类的PreCreateWidow和OnCreate两个函数的着重分析,Windows窗口与C++中的CWnd类的关系。
孙鑫VC教程之Windows编程基础
Windows基础-消息-消息循环
在应程序中有一个消息循环,它的代码如下: while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } 只要从应用程序消息队列中接受到的消息不是WM_QUIT,消息循环就继续 从消息队列中接收消息。当收到WM_QUIT消息后,GetMessage将返回 false,这样应用程序将终止运行。但只要循环继续下去,它就调用Win32 API的GetMessage函数获取下一消息;如果队列中没有消息,GetMessage 就会等待消息的到来。 当GetMessage返回一个消息后,采用TranslateMessage函数来看一下是否 由键盘输入,若由键盘输入则将原始的键盘消息转化为WM_CHAR消息。 TranslateMessage也可以用来处理键盘命令如Ctrl+X组合键等, TranslateMessage对非键盘消息则不进行任何处理。 最后,DispatchMessage消息确定应用程序哪一个窗口将获得消息,并把它 发送给该窗口。
设备描述表--句柄 Windows用句柄来标识和管理对象。句柄 是Windows系统给的,且是唯一的,并可通过 Windows访问。例如,调用GetDC函数可返回 设备描述表(即DC)句柄。设备描述表句柄类 型是HDC。Windows提供了许多句柄类型,如 窗口、画刷、字体等对象都有一个相关的句柄 类型:HWND、HBRUSH和HFONT,下表显示 了不同对象的句柄类型。
Windows基础-消息-谁来调用
在所编写的消息处理函数中,可调用Win32 API, 消息处理函数由Windows负责调用。大多数时间,程 序代码等待被调用。 对Windows几百个消息,如果程序员不编写消息 处理函数,则Windows提供一个缺省的DefWindowsProc 函数。 Windows如何知道是那一个函数被调用呢?在由 C编写的Windows程序中,在程序开始【WinMain函数 中】设置WinProc的名字,然后采用这个名字编写函数。 在WinProc函数中,使用C/C++switch语句,根据不同 的消息,调用相应的消息处理函数。当有一个窗口消 息时,Windows调用该处理函数。在MFC中,可以不 用编写窗口程序,只要编写消息处理函数,MFC通过 消息映射来为消息查找相关的消息处理函数。
孙鑫VC++从入门到精通开发详解视频教程FLASH版
第85集 第十课 A-1
第86集 第十课 A-2
第87集 第十课 B
第88集 第十课 C
第89集 第十课 D
第90集 第十课 E
第91集 第十课 F
第92集 第十课 G
第93集 第十课 H
第94集 第十课 I
第26集 第三课 G
第27集 第三课 H
第28集 第三课 I
第四课 MFC消息映射机制的剖析 讲述如何运用ClassWizard
第29集 第四课 A-1
第30集 第四课 A-2
第31集 第四课 B
第32集 第四课 C
第33集 第四课 D
第50集 第六课 G
第51集 第六课 H
第52集 第六课 I
第53集 第六课 J
第54集 第六课 K
第七课 对话框用户界面程序的编写
Hale Waihona Puke 第55集 第七课 A-1
第56集 第七课 A-2
第57集 第七课 B
第58集 第七课 C
第一课 Windows程序运行原理及程序编写流程
第1集 第一课 A-1
第2集 第一课 A-2
第3集 第一课 B
第4集 第一课 C
第5集 第一课 D
第6集 第一课 E
第7集 第一课 F
第8集 第一课 G
第9集 第一课 H
第42集 第五课 G
第六课 菜单的工作原理及编写应用
第43集 第六课 A-1
第44集 第六课 A-2
第45集 第六课 B
第46集 第六课 C
第47集 第六课 D
第48集 第六课 E
孙鑫vc课程笔记
作者tag:windows/.net孙鑫vc++讲座笔记 CSDN 推荐孙鑫VC++讲座笔记-(1)Windows程序内部运行机制1,windows程序设计是种事件驱动方式的程序设计,主要基于消息的。
当用户需要完成某种功能时,需要调用OS某种支持,然后OS将用户的需要包装成消息,并投入到消息队列中,最后应用程序从消息队列中取走消息并进行响应。
2,消息结构:typedef struct tagMSG { // msgHWND hwnd; //接收消息的窗口句柄。
和哪个窗口相关联。
UINT message; //消息标识。
消息本身是什么。
WPARAM wParam; //消息的附加信息。
具体取决于消息本身。
LPARAM lParam;DWORD time; //消息投递时间。
POINT pt; //消息投递时,光标在屏幕上的位置。
} MSG;3,消息队列:每个应用程序OS都为它建立一个消息队列,消息队列是个先进先出的缓冲区,其中每个元素都是一个消息,OS将生成的每个消息按先后顺序放进消息队列中,应用程序总是取走当前消息队列中的第一条消息,应用程序取走消息后便知道用户的操作和程序的状态,然后对其处理即消息响应,消息响应通过编码实现。
4,使用VC编程除了良好的C基础外还需要掌握两方面:一,消息本身。
不同消息所代表的用户操作和应用程序的状态。
二,对于某个特定的消息来说,要让OS执行某个特定的功能去响应消息。
5,Window程序入口:int WINAPI WinMain(HINSTANCE hInstance, // 当前事例句柄。
HINSTANCE hPrevInstance, // 先前事例句柄。
LPSTR lpCmdLine, // 命令行指针int nCmdShow // (窗口)显示的状态);说明:WinMain函数是Windows程序入口点函数,由OS调用,当OS启动应用程序的时候,winmain函数的参数由OS传递的。
基于消息的异步套接字和多线程技术网络通信编程
网 络 与 通 信《微计算机信息》( 管控一体化) 2009 年第 25 卷第 10-3 期文章编号:1008-0570(2009)10-3-0114-02基于消息的异步套接字和多线程技术网络通信编程Network Communications Programme Use of Asynchronism Socket Based On Massage and Multithreading Technology(海军航空工程学院) 赵均伟欧阳中辉 刘东鑫ZHAO Jun-wei OUYANG Zhong-hui LIU Dong-xin摘要: 在 模拟训练系统进行气象参数 , 本舰参数和目 标参数的传输时 , 为了能使数据实时和准确地传输和提高网络通信应用 程 序 的 性 能 , 我们采用基于消息的异步套接字 , 即 使 用 Windows Sockets 的异步选择函数 WSAAsyncSelect 。
对数据进行接收时 ,根 据数据的来源创建相应的接收线程接收数据 。
序 的 性 能 。
关键词: 网络通信; 消息机制; 异步套接字; 多线程 这种基于消息的异步套接字并结合多线程技术 ,极大地提高了网络应用程 中图分类号: TP319文献标识码: AAbstract: When the simulated traning system want to transmit meteorologica l parameter, this warship parameter and target parametercomponent,in order to make the data transmitting at real time with accurately, We adopt Asynchronism Socket based on message, the asynchronous selection function WSAAsyncSelect with Windows Sockets. When we receive the data, the corresponding receipt thread are founded according to the source of the data. This kind of Asynchronism Socket baseds on massage combined with Multithreading technology will greatly raised the function of network application program.Key words: Network commu nications; Massage mechanism; Asynchronism Socket ; Multithreading理,避免了某项任务长时间占用 CPU 时间,因此多线程可以提高 网络应用程序的实时性和并行性。
孙鑫C++视频教程总结(内功=算法+数据结构+编译原理+操作系统原理+软件工程+英文)
一:Windows程序运行原理及程序编写流程(创建窗口。
回调函数方法1int WINAPI WinMain( //主函数HINSTANCE hInstance, // handle to current instanceHINSTANCE hPrevInstance, // handle to previous instanceLPSTR lpCmdLine, // command lineint nCmdShow // show state)WNDCLASS wndcls;//创建一个窗口对象wndcls.cbClsExtra=0; //窗口类附加字节,为该类窗口所共享。
通常0。
wndcls.cbWndExtra=0; //窗口附加字节。
通常设为0。
wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);//背景wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);/光标wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);//位图(图标)wndcls.hInstance=hInstance; //当前应用程序事例句柄。
wndcls.lpfnWndProc=WinSunProc;// 窗口过程函数(回调函数)wndcls.lpszClassName="Weixin2003";// 窗口名//类的名字wndcls.lpszMenuName=NULL;//菜单名wndcls.style=CS_HREDRAW | CS_VREDRAW; //窗口的类型RegisterClass(&wndcls);//注册窗口HWND hwnd;hwnd=CreateWindow("Weixin2003","北京",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);//定义窗口句柄并创建窗口ShowWindow(hwnd,SW_SHOWNORMAL);//显示窗口UpdateWindow(hwnd);//更新窗口MSG msg;//定义消息对象/*做消息循环*/while(GetMessage(&msg, //接收消息的窗口句柄。
第十六讲 Stoket编程
第十六讲Stocket编程一、Stocket套接字提供对网络通信的基本操作,常用的类型有两种:1.流式套接字提供连接的、可靠的、数据无错并且无重复的数据发送服务,发送的数据按顺序接收。
无长度限制线路占用率高。
TCP协议使用这种接口。
如网页浏览、下载文件。
2.数据报式套接字提供面向无连接的服务,以独立的数据包形式发送数据,不提供正确性检查,不保证各数据包的发送顺序,可能出现数据的重发、丢失等现象。
接收的顺序由路由决定,线路占用率低,数据包长度不能大于32KB.UDP协议使用这种接口,如QQ。
面向连接就像打电话,一直占线,先发先至。
面向无连接就像寄信,只管收发,什么时候到由邮局定。
与这个比方不同的是面向连接的方式为了保证安全可靠,速度不如无连接方式。
首先我们解释一下在网络编程时候,经常遇到的几个概念:同步(synchronous)、异步(asynchronous)、阻塞(Block)和非阻塞(Unblock):所谓异步方式,就是发送方发送数据包以后,不等接受方响应,就接着发送下一个数据包。
同步方式就是当发送方发送一个数据包以后,一直等到接受方响应后,才接着发送下一个数据包。
阻塞套接字是指执行此套接字的网络调用时,直到调用成功才返回,否则此套节字就一直阻塞在网络调用上,比如调用StreamReader 类的Readlin ( 方法读取网络缓冲区中的数据,如果调用的时候没有数据到达,那么此Readlin ( 方法将一直挂在调用上,直到读到一些数据,此函数调用才返回;非阻塞套接字是指在执行此套接字的网络调用时,不管是否执行成功,都立即返回。
同样调用StreamReader 类的Readlin ( 方法读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。
在Windows网络通信软件开发中,最为常用的方法就是异步非阻塞套接字。
平常所说的C/S(客户端/服务器)结构的软件采用的方式就是异步非阻塞模式的。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
if(SOCKET_ERROR==retVal)
{
MessageBox("套接字绑定失败!");
return FALSE;
}
说明:WSAAsyncSelect方法第二个参数表示网络事件发生时用来接收消息的窗口,第三个参数表示处理响应的消息,第四个参数表示网络事件类型,采用或操作。我们当前采用读这样一个事件,网络上一旦有数据到来的时候就会触发这个事件,系统就会通过我们自定义的消息UM_SOCK来通知我们进行处理if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))
temp+="/r/n"+str;
SetDlgItemText(IDC_EDIT_RECV,temp);
break;
}
h. 信息的发送:
DWORD dwIP; //控件上填写的IP地址
CString strSend; //需要发送的信息内容
Windows Sockets为了支持Windows消息驱动机制,使应用程序开发者能够方便地处理网络通信,它对网络事件采用了基于消息的异步存取策略。
Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。
SOCKADDR_IN addrTo;
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(1234);
//GetBuffer函数将CString类型转换为char*类型
在上一章中编写的Chat程序中,因为接收程序放在了一个线程中,所以虽然它是阻塞的,也没有影响到主线程的运行性能。
2. 编写基于异步套接字的聊天室程序:
a. 因为MFC自带的AfxSocketInit函数初始化支持的是1.1版本的套接字,不适合异步套接字,我们需要调用的是Winsock2版本的套接字,那么加载套接字库的过程只能使用WSAStartup了。在CChatApp的InitInstance初始化函数中添加:
3. WSARecvFrom函数的第二个参数可表示一个WSABUF的结构体数组,可用于存放多个从网络上接收到的信息块,当然也可以将所有信息放在一个结构体中,然后将自己关心的信息块取出,但这样做比较麻烦,可以直接用WSABUF结构体数组接收不同信息的块即可。(没有具体的实际操作经验)
在消息响应函数中添加如下代码
WSACleanup( );
return;
}
b. 在StdAfx.h里添加#include <winsock2.h>,在setting里添加ws2_32.lib库文件。
c. 给CChatApp类添加析构函数,在其中添加WSACleanup来终止对套接字库的使用。
}
str.Format("from %s said:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
CString temp;
GetDlgItemText(IDC_EDIT_RECV,temp);
说明:在Winsock2版本中提供的WSASocket这样一个扩展方法用于创建套接字,对应于socket方法;bind方法在winsock2中没有提供相应的扩展方法。然后调用WSAAsyncSelect方法请求一个windows基于消息的网络事件通知。
m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
孙鑫VC视频教程笔记之第十六课(下)“异步套接字编程”
分类: 孙鑫VC视频教程笔记 2008-09-09 11:28 619人阅读 评论(0) 收藏 举报
1. 异步套接字编程:
Windows套接字在两种模式下执行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)。而在非阻塞模式下,Winsock函数无论如何都会立即返回。采用异步套接字,可有效改善程序的运行性能。
d. 给CChatDlg类添加成员变量SOCKET m_socket,并在构造函数中初始化为0
e. 给CChatDlg类添加析构函数,添加:
if(m_socket) //判断socket是否有值
closesocket(m_socket);
f. 创建初始化函数InitSocket(),代码如下:
1. 调用WSAStartup加载套接字库
2. 调用WSASocket创建套接字
3. 调用WSAAsyncSelect请求基于windows消息的网络事件通知
4. 创建自定义的消息响应函数,来处理捕获的网络事件
5. 在消息响应函数内部调用WSARecvFrom来处理接收到的数据
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
int len=sizeof(addrFrom);
switch(LOWORD(lParam)) { //lParam的低字节指明网络事件的类型
case FD_READ: //我们当前只有读取这样一个事件,这是在WSAAsyncSelect中设定的
WSABUF wsabuf;
wsabuf.buf=new char[200]; //网络上接收到的数据
{
MessageBox("发送数据失败!");
return;
}
else
{
SetDlgItemText(IDC_EDIT_SEND,"");
}
i. 综上所述,创建一个基于winsock2版本的异步套接字的网络聊天室程序有以下几个步骤:
WSABUF wsbuf; //需要发送的信息内容
DWORD dwSend;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
GetDlgItemText(IDC_EDIT_SEND,strSend);
2. 参看MSDN中WSAAsyncSelect方法的说明如下:
When one of the nominated network events occurs on the specified socket s, the application's window hWnd receives message wMsg. The wParam parameter identifies the socket on which a network event has occurred. The low word of lParam specifies the network event that has occurred. The high word of lParam contains any error code.
if(INVALID_SOCKET==m_socket)
{
MessageBox("创建套接字失败!");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr=htonl处理发送数据
3. 小结:
当前程序将消息的接收和发送放在了同一个线程中,即主线程中。如果采用先前使用过的阻塞套接字的话,程序会因为接收函数的调用导致主线程的暂停运行,就无法及时的发送消息了。但是采用异步套接字可使得发送和接收放在同一个线程中而不会有相互的影响。
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(1234);
int retVal;
retVal=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
//下面的消息框基本不会运行,因为WSARecvFrom方法是在有网络数据的情况下才会被调用的,所以运行到这段,基本是有数据的,做这样一个判断,只是出于编程风格一致而已
MessageBox("接收数据失败!");
return;
CString str;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
wsbuf.buf=strSend.GetBuffer(strSend.GetLength());