怎么创建聊天室,第15课 多线程与聊天室程序创建(基于可视化...

第15课 多线程与聊天室程序创建(基于可视化界面)
必备知识:程序---存储在硬盘上的数据。
进程---只是提供了一个框架,不执行任何内容,相当于提供了一个容器。
线程---CPU真正执行的东西,在被提供的容器里执行。
单线程---就像只有一个医生在给病人做手术一样
多线程---就想一个医生再给病人做手术(主线程),旁边有其他护士帮忙拿剪刀、擦汗等工作在进行(副进程),这就是多进程。
注意:1、上述概念只是作者按照自己的理解简单的描述,如果想学习多线程,必须自己下去仔细琢磨这个概念,弄明白才行。
2、此节内容以单个CPU为例,本人经过测试,在自己的双核机子上运行下边的程序,会遇到一些小问题;但如果真正弄明白了多线程的原理,那么即使移植在双核、四核甚至更多核都能自己解决了。
一、多线程的编写
1、用到的函数:
HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, //一个指向SECURITY_ATTRIBUTES结构体的指针,表明安全性;如果设为NULL则表示使用默认安全性
__in SIZE_T dwStackSize, //创建该线程时,设置使用堆栈的大小;如果设为0则表示使用调用线程的堆栈大小,即默认值。
__in LPTHREAD_START_ROUTINE lpStartAddress, //指向一个函数,该函数为线程的入口函数
__in_opt LPVOID lpParameter, //参数,注意此函数只能传进一个参数。
__in DWORD dwCreationFlags, //如果设为0表示此线程立即生效;设为CREATE_SUSPENDED表示暂时不运行,直到调用ResumeThread()函数才会运行。
__out_opt LPDWORD lpThreadId //线程ID
); //此函数调用成功则返回一个句柄值;如果不成功则返回NULL;如果线程ID已经存在,则返回一个相同的HANDLE,但调用GetLastError()返回ERROR_ALREADY_EXISTS。
DWORD WINAPI ThreadProc(
__in LPVOID lpParameter
); //线程的入口函数,注意函数名可以改变。
HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向SECURITY_ATTRIBUTES结构体的指针,设为NULL表示使用默认安全属性
__in BOOL bInitialOwner, //设为TURE表示该线程拥有这个互斥对象,设为FALSE表示该线程不拥有这个互斥对象。
__in_opt LPCTSTR lpName //为该互斥对象取一个名字,可以为NULL;利用此参数可以使系统只能运行一个进程
);//该函数创建一个互斥对象,返回一个句柄值
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle, //传入互斥对象,即传入钥匙
__in DWORD dwMilliseconds //设定时间,以毫秒为单位;如果设为INFINITE表示此参数无效
);等待直到句柄传入或设定的时间流逝。
2、利用多线程编写一个火车票售票系统
#include <windows.h>
#include <iostream.h>
DWORD WINAPI Fun1Proc(
LPVOID lpParameter // thread data
);
DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
);
int tickets=100;
HANDLE hMutex; //为了不让多个线程同时访问tickets这个全局变量,引入了互斥对象,可以把它看做是一把钥匙。
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); //创建两个线程来售票
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1); //注意此句代码不会终止新建线程的运行,只是将内核对象计数减1。
CloseHandle(hThread2);
hMutex=CreateMutex(NULL,TRUE,"tickets"); //创建一个互斥对象,并且该线程拥有这个互斥对象
if(hMutex) //利用此判断句可以使系统只能创建一个进程。
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only instance can run!"<<endl;
return;
}
}
WaitForSingleObject(hMutex,INFINITE); //传入互斥对象才能运行
ReleaseMutex(hMutex); //因为此主线程调用了两次互斥对象,所以只有释放两次才能使互斥对象成为有信号状态
ReleaseMutex(hMutex);
Sleep(4000); //睡眠4秒钟,使新建的两个线程能够有机会运行。
}
DWORD WINAPI Fun1Proc( LPVOID lpParameter)
{
while(TRUE) //设计这个死循环是为了使此线程一直能够买票
{
WaitForSingleObject(hMutex,INFINITE); //等待有信号的互斥对象
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket : "<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex); //卖完票后设置互斥对象为有信号状态
}
// WaitForSingleObject(hMutex,INFINITE);
// cout<<"thread1 is running"<<endl; //用这两句代码时需要将上述代码注释起来。注意下边没有ReleaseMutex(hMutex);
return 0;
}
DWORD WINAPI Fun2Proc( LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE); //等待有信号的互斥对象
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex); //卖完票后设置互斥对象为有信号状态
}
// WaitForSingleObject(hMutex,INFINITE);
// cout<<"thread2 is running"<<endl; //注意事项同上
return 0;
}
注意:1、线程创建时和线程调用时,内核对象都加1。
关闭线程句柄时和线程结束时,内核对象都减1。(自理解)
2、互斥对象包含两个内容:线程ID (指明当前哪个线程在使用该互斥对象)
和 计数器 (线程使用了几次该互斥对象)
3、互斥对象在有信号状态时,线程ID和计数器都为0
4、上述两个副线程中的注释代码能说明的问题:系统管理内核对象,当系统发现拥有互斥对象的线程结束了以后,就会自动设置该互斥对象为有信号状态。
二、聊天室工具的创建(基于可视化界面)
1、文件---新建,创建一个基于对话框的MFC应用程序,创建后将对话框上的控件全部删除,然后创建一个静态框,再在静态框中创建一个编辑控件,修改标题为“接收数据”
2、在chat对话框(新建的对话框,非系统生成的关于对话框)中增加一个函数用来创建套接字并绑定,如下:
BOOL CChatDlg::InitSocket()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return 0;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup( );
return 0;
}
sock=socket(AF_INET,SOCK_DGRAM,0);
if (INVALID_SOCKET==sock)
{
cout<<"The Socket creation is fail! "<<endl;
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);
int len=sizeof(SOCKADDR);
int level;
level=bind(sock,(SOCKADDR*)&addrsock,len);
if (SOCKET_ERROR==level)
{
cout<<"The binding is fail!";
return FALSE;
}
return TRUE;
3、在CChatDlg::OnInitDialog()创建一个用来接受数据的线程,需要注意的是,在线程入口函数中应该传入“对话框句柄和套接字”两个参数;
但注意到创建线程的函数中只能传入一个参数,所以决定让该参数指向一个结构体(此结构体中存放了“对话框句柄和套接字”)如下:
struct RECVPARAM
{
SOCKET sock;
HWND hwnd;
};
BOOL CChatDlg::OnInitDialog()
{
InitSocket();
RECVPARAM *pRecvparam=new RECVPARAM;
pRecvparam->hwnd=m_hWnd;
pRecvparam->sock=sock;
HANDLE Thread1=CreateThread(NULL,0,ThreadProc,(LPVOID)pRecvparam,0,NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
4、声明线程入口函数并实现,注意必须声明为全局函数,如果在ChatDlg类中进行此操作的话,必须声明为静态。
DWORD WINAPI ThreadProc (LPVOID lpParamete);//在CChatDlg.h中的声明,注意不要放在CChatDlg类中
DWORD WINAPI ThreadProc (LPVOID lpParamete) //在CChatDlg.h中定义,注意不要放在CChatDlg类中
{
SOCKET socket=((RECVPARAM*)lpParamete)->sock;
HWND hwnd=((RECVPARAM*)lpParamete)->hwnd;
delete lpParamete; //注意释放对内存
SOCKADDR_IN addrRecv;
int len=sizeof(SOCKADDR);
char recvBuf[100];
char temBuf[200];
while(TRUE)
{
int retvel;
retvel=recvfrom(socket,recvBuf,100,0,(SOCKADDR*)&addrRecv,&len);
if (SOCKET_ERROR==retvel)
{
cout<<"Receiving data false!";
break;
}
sprintf(temBuf,"%s 说: %s",inet_ntoa(addrRecv.sin_addr),recvBuf);
::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)temBuf); //要想将格式化到temBuf中的内容传到编辑框中,需要发送一个自定义消息。
}
return 0;
}
5、自定义一个消息,步骤如下:
#define WM_RECVDATA WM_USER+1 //在CChatDlg.h中自定义消息,注意不要放在类中
// Generated message map functions
//{{AFX_MSG(CChatDlg)
virtual BOOL _disibledevent=>
CString strtem;
GetDlgItemText(IDC_EDIT_RECV,strtem); //保存已经接收的内容
strtem+="\r\n";
strtem+=str; //旧内容回车换行后加上新内容,注意编辑框一定要让其具有支持多行的属性,否则所有的内容将在一行中输出
SetDlgItemText(IDC_EDIT_RECV,strtem); //连新带旧重新置入编辑框中
CEdit *pRecvWnd=(CEdit*)GetDlgItem(IDC_EDIT_RECV);
pRecvWnd->SetSel(-1); //在有滚动条的情况下立即看到最后的光标所在位置
} //自此,消息接收的代码就完成了,下边为发送数据的代码
6、在对话框上创建一个静态框,设标题为“数据发送”,再在其中创建一个编辑框用于发送数据;在创建一个IP地址控件用于输入对方的IP地址:
void CChatDlg::OnSend()
{
// TODO: Add your control notification handler code here
DWORD IpAddress;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->GetAddress(IpAddress); //将输入的IP地址保存到IpAddress中
sockaddr_in addrSend;
addrSend.sin_addr.S_un.S_addr=htonl(IpAddress); //将DWORD类型的IP地址为转换为可用类型
addrSend.sin_family=AF_INET;
addrSend.sin_port=htons(6000);
int len=sizeof(SOCKADDR);
CString strSend;
GetDlgItemText(IDC_EDIT_SEND ,strSend); //将要发送的内容保存到strSend中
sendto(sock,strSend,strlen(strSend)+1,0,(SOCKADDR*)&addrSend,len); //向目标地址饭送数据,并多发一个字节“\0”
SetDlgItemText(IDC_EDIT_SEND,""); //数据发送完成后,将发送编辑框置空
}
7、给IP地址编辑框中设置一个初值:
void CChatDlg::OnPaint()
{
CIPAddressCtrl *pIpctrl=(CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS);
if ((pIpctrl->IsBlank())) \\判断IP地址编辑框是否为空,如果为空则进入。
{
pIpctrl->SetAddress(127,0,0,1); \\设置默认地址为本地回路地址
}
}
Tags:  可视化编程 可视化效果 java多线程 多线程 怎么创建聊天室

延伸阅读

最新评论

发表评论