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

孙鑫15课:多线程与聊天室程序的创建

程序,进程(32位进程,分配232次方,4GB2GB内核分区,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_socket0

和一个成员函数: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,"");

}

你可能感兴趣的:(thread,多线程,socket,聊天,winapi)