您的位置:首页 > 编程语言

孙鑫VC学习(第15课:多线程与聊天室程序的创建代码)

2011-05-21 21:35 549 查看
孙鑫15课:多线程与聊天室程序的创建
程序,进程(32位进程,分配2的32次方,4GB,2GB内核分区,2GB用户分区。),线程
进程是线程容器。真正完成代码执行的是线程。主线程:main(),winmain()
下面创建一个多线程的程序:(WIN32控制台程序)
#include <windows.h>//访问windows
API函数
#include <iostream.h>//C++标准输入输出流头文件

int index=0;
int tickets=100;
HANDLE hmutex;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while(true)
{
WaitForSingleObject(hmutex,INFINITE);///在使用互斥资源的前后要等待资源,只是互斥对象此时要申请,对于线程1和线程,实际上没有涉及到互斥对象的那部分程序还是在运行的。
if (tickets>0)
{
cout<<"thread1 is selling ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hmutex);

}
return 0;

}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while(true)
{
WaitForSingleObject(hmutex,INFINITE);
if (tickets>0)
{
cout<<"thread2 is selling ticket:"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hmutex);
}
return 0;

}
void main()
{
HANDLE thread1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
CloseHandle(thread1);
HANDLE thread2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(thread2);
//关闭线程1句柄,刚创建就关闭,关闭句柄,并没有终止线程1,只是表示在主线程中,对线程1的引用不感兴趣,所以关闭。
//让线程1的内核使用对象计数器减1。当线程1执行完后,线程1使用计数器清空,系统会释放线程1内核对象。
//如果在主线程中没有关闭线程1句柄,则在线程1使用完后,也不会将线程使用计数器-1,则程序执行完后,
//也不会释放线程1内核对象,只有等到进程终止的时候,才会清空。
//所以应该在不使用线程句柄的时候将其关闭,让线程内核引用计数-1。
//while(index++<1000)
//cout<<"main is running"<<endl;
hmutex=CreateMutex(NULL,false,NULL);//main函数不拥有对互CHI对象的拥有权
Sleep(1000);//在睡眠之前不能CloseHandle(hMutex);不然此互斥对象就不能被正常使用了

}//主线程退出,进程退出。则在此进程中包含的线程全部退出。
互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量,一个线程ID和一个计数器。
ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

判断先前是否有一个应用实例在运行的代码:(要//创建一个命名的互斥对象)
hMutex=CreateMutex(NULL,TRUE,"tickets");
if(hMutex)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only instance can run!"<<endl;
return;
}
}
//注意:hMutex = CreateMutex(NULL,FALSE,"MutexT");

hMutex = CreateMutex(NULL,FALSE,"MutexT"); //////

if (hMutex)

{
CreateMutex这个是它的返回值的说明段:Return Values

If the function succeeds, the return value is a handle to the mutex object. If the named mutex object existed before the function call, the function returns a handle to the existing object and GetLastError returns ERROR_ALREADY_EXISTS. Otherwise, the caller
created the mutex.
下面编写一个网络聊天室程序:
创建一个基于对话框的程序。
在BOOL CChatApp::InitInstance()中加上下面代码:
if (AfxSocketInit( )==0)//AfxSocketInit( )加载套接字版本1.1.//
//This function also ensures that ::WSACleanup is called for you before the application
terminates.

{
AfxMessageBox("加载套接字版本失败!");
return FALSE;
}
在StdAfx.h中加上#include
<Afxsock.h>。
给CChatDlg加上
private:
socket m_socket;
并在CChatDlg的构造函数中将m_socket=0
和一个成员函数:BOOL CChatDlg::InitSock()
BOOL CChatDlg::InitSock()
{
m_socket=socket(AF_INET,SOCK_DGRAM,0);
if (INVALID_SOCKET==m_socket)
{
MessageBox("套接字创建失败!");
return FALSE;
}

SOCKADDR_IN addrsk;
addrsk.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//接收来自任何地址的数据
addrsk.sin_family=AF_INET;
addrsk.sin_port=htons(6000);//接收端口

if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrsk,sizeof(SOCKADDR_IN)))//套接字和接收端绑定
{
MessageBox("绑定失败!");
closesocket(m_socket);//如果失败,关闭套接字
return FALSE;
}
return TRUE;
}
在BOOL CChatDlg::OnInitDialog()中加下面代码:
if(FALSE==InitSock())
{
MessageBox("初始化套接字失败!");
return FALSE;
}
recvfrom如果没有数据过来,它会阻塞进程执行。因此把接收数据操作放到一个单独的线程中完成。
给ChatDlg.h增加一个成员结构体的变量:
struct RECVPARAM
{
SOCKET sock;
HWND hwnd;
};
在BOOL CChatDlg::OnInitDialog()中增加下面代码:
RECVPARAM *recvparam=new RECVPARAM;
recvparam->sock=m_socket;
recvparam->hwnd=m_hWnd;//也可以把EDIT框的句柄传过去
HANDLE handle=CreateThread(NULL,0,RecvProc,(LPVOID)recvparam,0,NULL);
CloseHandle(handle);
在这本例中把接收函数写成是CChatDlg的成员函数
在class CChatDlg : public CDialog中加
static DWORD WINAPI RecvProc(LPVOID lpParameter);//static的话,这个函数不属于哪个成员变量,它只属于这个类本身。所以在其它函数中可以直接调用这个函数。另外,在C++中不能直接把普通函数名做为参数,除非这个普通函数是STATIC的,所以要定义成STATIC的,如果不想定义成类的STAITC的成员函数,可以把它们定义成全局函数,不过这样就破坏了类的面向对象的设计思想。
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
HWND hnd=((RECVPARAM*)lpParameter)->hwnd;//传句柄的时候,最好传对话框的句柄
SOCKET sk=((RECVPARAM*)lpParameter)->sock;
delete lpParameter; //释放用NEW分配的堆内存,不然会造成内存泄露
SOCKADDR_IN skaddr;
int len=sizeof(SOCKADDR);
char recvBuf[100];
char tempBuf[200];
while(1)
{
if(SOCKET_ERROR==recvfrom(sk,recvBuf,100,0,(SOCKADDR*)&skaddr,&len))//套接字和接收方绑定,发送和接收都用同一个套接字
break;//终止循环
else
{
sprintf(tempBuf,"%s say:%s",inet_ntoa(skaddr.sin_addr),recvBuf);
::PostMessage(hnd,WM_RECVDATA,0,(LPARAM )tempBuf);//给对话框窗口发送接收数据的消息 之所以调用全局的函数,是因为CWnd::PostMessage不是一个静态的函数,静态的成员函数只能调用静态的成员函数,而全局的PostMessage函数是从DLL库中导入进来的,所以没关系
}
}
return 0;
}
在ChatDlg.H中
#define WM_RECVDATA WM_USER+1
在class CChatDlg : public CDialog中加入:
afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);//此函数的形参只能是(WPARAM wParam,LPARAM
lParam);这个样子,如果不是这个样子,结果出不来,另外,它的返回值无所谓,哪怕返回值是int,也能出来
在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)
ON_MESSAGE(WM_RECVDATA,OnRecvData)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

END_MESSAGE_MAP()中加入上面红色的一句
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)//对话框窗口收到接收数据的消息后,将数据在EDIT中显示出来

{

CString str=(char*)lParam;//CString有一个operator(char *)的操作

CString strTemp;

GetDlgItemText(IDC_EDIT_RECV,strTemp);

str+="/r/n";

str+=strTemp;

SetDlgItemText(IDC_EDIT_RECV,str);

}

发送按钮的代码如下:
//记得让接收文本框的属性加一个多行
void CChatDlg::OnBtnSend()
{
// TODO: Add your control notification handler code here
DWORD dwIP;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);

SOCKADDR_IN addrTo;
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);//给这个IP的这个端口发送数据
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);//给这个IP发送数据

CString strSend;
GetDlgItemText(IDC_EDIT_SEND,strSend);
sendto(m_socket,strSend,strSend.GetLength()+1,0,
(SOCKADDR*)&addrTo,sizeof(SOCKADDR));//注意发送UDP报文使用的发送和接收函数为sendto和recvfrom,发送数据的时候不需要绑定,只需要用一个套接字和知道发送的目的地,直接发送即可
SetDlgItemText(IDC_EDIT_SEND,"");
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: