多线程 socket

       MFC下使用CSocket或者CAsyncSocket进行Socket通信,CSocket继承自 CAsyncSocket。这两者的区别在于,CSocket是同步的Socket,CAsyncSocket则是异步的。使用时,CSocket::Receive()和CSocket::Send()函数会阻塞当前线程,直至操作完成;而 CAsyncSocket::Receive()和CAsyncSocket::Send()函数则不阻塞线程,函数立即返回。所以这两者在使用方式上有所不同。这里探讨一种使用CSocket配合CSocketFile、CArchive和 CThread的多线程C/S模式。

CSocket通过CSocketFile由CArchive管理,可以得到类似iostream方式的流输入输出。这种方式的主要过程有:创建连接、接受数据、发送数据和断开连接。CSocket必须附加在与其一起工作的线程上,不能通过其他线程调用,所以主要通过在线程之间的传递消息和加锁实现线程的通信和同
步。我们将分服务端( Server)和客户端(Client)分别讨论具体实现。

服务端

服务端有一个主界面,其类为CSCEServerDlg,继承自 CDialog,它保存线程池和锁。为了简单起见,后面的类声明中大部分的成员变量访问控制都是public。声明类似如下:

typedef
list PThreadList;

class
CSCEServerDlg : public
CDialog

{

...

public:

CCriticalSection m_csThrdList; // 线程池锁

PThreadList m_thrdList; // 线程池

int m_thrdIndex; // 线程计数器

CServerSocket m_socketListen // 监听CSocket

...

};

和CSocket一起工作的线程类为CSerSocketThread,继承自CWinThread。这里使用
CWinThread是因为它可以处理消息,这样便于线程间通信。重载了
CWinThread::InitInstance(),在线程建立时做处理,具体实现在后面会有。声明如下:

class
CSerSocketThread : public
CWinThread
{
DECLARE_DYNCREATE(CSerSocketThread)
public:
CSerSocketThread(void);
~CSerSocketThread(void);

virtual
int
ExitInstance();

virtual
BOOL
InitInstance();

int m_thrdIndex;

CSCEServerDlg* m_pSerDlg; // 主界面指针

SOCKET m_hSocket; // Socket 句柄


CServerSocket m_serverSocket; // 附加在这个线程上的CSocket
CSocketFile* m_socketFile; // CSocketFile
CArchive* m_arIn; // 输入CArchive
CArchive* m_arOut; // 输出CArchive
...

protected:
DECLARE_MESSAGE_MAP()
afx_msg
void
OnCustomMsg(WPARAM wParam,LPARAM lParam);

void
ReceiveData();
void
SendData(const
int WP, const
CString& strMsg);
...


};


我们使用的具体的CSocket类是CServerSocket,继承自CSocket,重载了
CSocket::OnAccept()函数和CSocket::OnReceive()函数。监听端口的CSocket在有Socket
连接时会调用OnAccept()函数处理连接的建立。当已经连接的CSocket接收到数据时,则会调用
OnReceive()函数,这里要注意的是,如果对方发送的数据较大,则有可能被分成多次发送,这种情况
下会多次调用OnReceive()函数,后面会提高如何来处理这个问题。同样的道理,在发送数据时则会调
用OnSend()函数,这里我们不会用到它。

CServerSocket的声明:
class
CServerSocket :
public
CSocket

{
public:
CServerSocket(void);
~CServerSocket(void);

virtual
void
OnAccept(int nErrorCode);
virtual
void
OnReceive(int nErrorCode);


CSCEServerDlg* m_pSerDlg; // 主界面指针
CWinThread* m_pThrd; // 该CSocket所在的线程
};
int m_thrdIndex; // 该CSocket所属线程的index,服务 Socket 为 -1


OnAccept()函数的实现:
void
CServerSocket::OnAccept(int nErrorCode)
{


// TODO: 在此添加专用代码和/或调用基类
if(nErrorCode == 0)



{
// 创建一个连接Socket
CServerSocket connectSocket;
Accept(connectSocket); // connectSocket将和客户端连接


// 创建一个线程
CSerSocketThread* pThread =

(CSerSocketThread*)AfxBeginThread(
RUNTIME_CLASS(CSerSocketThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);

// 添加到线程池中
m_pSerDlg->m_csThrdList.Lock(); // 加锁
m_pSerDlg->m_thrdList.push_back(pThread);
m_pSerDlg->m_csThrdList.Unlock(); // 解锁


pThread->m_pSerDlg = m_pSerDlg;
/*

* 将CSocket交给新建的线程
* 这样这个CSocket将和新建的线程一起工作
* 注意现在是在界面线程中,因为是监听CSocket,是CServerDlg的成员变量
* 必须通过Detach()得到HANDLE之后在工作线程中重新Attach()才行
*/
pThread->m_hSocket = connectSocket.Detach();
// 线程编号,通过这个在通信时区别不同的客户端
pThread->m_thrdIndex = m_pSerDlg->m_thrdIndex++;

// 启动线程
pThread->ResumeThread();
}

CSocket::OnAccept(nErrorCode);

}

当监听CSocket监听到有客户端接入,调用OnAccept()函数。通过Accept()函数将连接建立,
之后建立一个新的线程,让连接的CSocket附加上去,将线程加入线程池,OnAccept()函数的主要工
作就完成了。

OnReceive()函数的实现:

void CServerSocket::OnReceive(int nErrorCode)
{
// 通过自定义消息WM_THREAD和消息参数WP_RECV通知线程去读取数据
m_pThrd->PostThreadMessage(WM_THREAD, WP_RECV, 0);


//CSocket::OnReceive(nErrorCode);
}


当接收到数据时,CServerSocket就通知它附加的线程去接收数据,以便进一步处理。
下面看一下CSerSocketThread的几个处理的实现:
InitInstance()函数,这个函数在新线程被构造出来后调用,用来完成一些自定义的新建过程:

BOOL
CSerSocketThread::InitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
if (!AfxSocketInit()) // 初始化CSocket必须调用的
{
return
CWinThread::InitInstance(); // 立刻退出
}

if(m_serverSocket.Attach(m_hSocket)) // 重新Attach之前传入的Socket
{
m_serverSocket.m_pThrd = this; // 告诉CSocket它所附加工作的线程
m_serverSocket.m_thrdIndex = m_thrdIndex;
m_serverSocket.m_pSerDlg = m_pSerDlg;

// 建立CSocketFile,将CSocket附加在上面
m_socketFile = new
CSocketFile(&m_serverSocket);
// 输入CArchive
m_arIn = new
CArchive(m_socketFile, CArchive::load);
// 输出CArchive
m_arOut = new
CArchive(m_socketFile, CArchive::store);


return TRUE; // 这样线程就不会立刻退出
}

}
return
CWinThread::InitInstance(); // 立刻退出

当新线程构造好了之后,InitInstance()函数被调用。将传入的CSocket的HANDLE重新
Attach到线程自己的CSocket成员上,之后建立CSocketFile和两个CArchive用于输入输出,这
里CSocketFile会互斥访问,输入和输出的CArchive无法并发操作它。

之前的CServerSocket::OnReceive()中通过消息通知线程来接受数据,下面看具体的实现。

首先声明将WM_THREAD消息绑定到CSerSocketThread::OnCustomMsg()函数:
BEGIN_MESSAGE_MAP(CSerSocketThread, CWinThread)
ON_THREAD_MESSAGE(WM_THREAD, OnCustomMsg)
...
END_MESSAGE_MAP()


OnCustomMsg()函数的实现:
void  CSerSocketThread::OnCustomMsg(WPARAM wParam,LPARAM lParam)
{

switch(wParam)
{
case WP_RECV:


// 接收数据

// 先把接收数据事件关掉
m_serverSocket.AsyncSelect(FD_CLOSE);


*m_arIn >> ...; // 读取数据
// 重新打开接收数据事件
m_serverSocket.AsyncSelect(FD_READ|FD_CLOSE);
...
break;


...
}
}

接收到的WM_THREAD消息包含WP_RECV的消息参数后,首先先关掉接收数据事件,这样就可以通
过CArchive的operator >>一次读取完所有数据(根据自己定义的方式),读取完成后再打开接收
数据事件。通过这个流程,每次数据发送就只会调用一次OnReceive()函数。这里要注意的是必须用
CArchive的operator >>根据发送的情况正确的读取数据,最好都读取出来,否则下次读取时可能会
出现错误。

如果需要发送数据,则在CSerSocketThread线程中使用CArchive的operator <<发送所有
支持
operator <<的类型。注意的是必须是在这个工作线程中调用,CSocket是附加在线程上的,同
样CArchive也只能在工作线程上使用,如果通过别的线程调用,就和系统的Socket表不对应了。另
外还有注意的是输出完后需要显式调用CArchive::Flush()将数据发出。

主界面对CServerSocket的使用则比较简单,如下:
m_socketListen.m_pSerDlg = this;
if (!AfxSocketInit()) // Socket初始化


SomeMessageFunc("SOCKET 初始化失败!");
else
if (!m_socketListen.Create(xxxx)) // 创建监听Socket
SomeMessageFunc("监听 SOCKET 创建失败!");

else
if (!m_socketListen.Listen()) // Socket监听
SomeMessageFunc("监听 SOCKET 监听失败!");

else

SomeMessageFunc("监听建立");

客户端

客户端部分的大部分处理和服务端是类似的,主要区别在于客户端主动连接服务端的监听端口。客
户端首先建立工作线程,在线程重载的InitInstance()函数中建立到服务端的连接,成功就继续工作,
失败就停止线程即可。之后当CSocket接收数据是,调用重载的OnReceive()函数,通过消息通知工
作线程使用CArchive读取数据。发送时则由工作线程使用CArchive发送数据,显式调用
CArchive::Flush()。

连接代码,依然是在:

BOOL
CCliSocketThread::InitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
if (!AfxSocketInit()) // 一样的初始化,必须调用
{
return
CWinThread::InitInstance();
}

if (!m_clientSocket.Create()) // 建立CSocket

{
SomeMessageFunc("Socket 创建失败");
return
CWinThread::InitInstance();

}

m_clientSocket.m_pCliDlg = m_pCliDlg;
CString strIP("xxx.xxx.xxx.xxx");
UINT port = xxxx;
if (m_clientSocket.Connect(strIP, port)) // 连接服务端
{

// 连接建立
m_clientSocket.m_thrd = this;


m_socketFile = new
CSocketFile(&m_clientSocket);
m_arIn = new
CArchive(m_socketFile, CArchive::load);
m_arOut = new
CArchive(m_socketFile, CArchive::store);


SomeMessageFunc("连接成功");

return TRUE; // 线程继续工作

}
return
CWinThread::InitInstance();
}

该结构的示意图如下:




你可能感兴趣的:(多线程 socket)