简单聊天工具,未添加传输文件的过程,道理类似

聊天程序,Server端代码及解释:
1)各Client上线,通过客户端list列表可以查看在线用户,双击在线用户,即可打开窗口交谈。
2)交流方式:Client交流内容需要经过Server端,这样可以保证信息过滤。
核心:在服务端定义一个map,保存客户端IP:Port和socket对应关系。
      客户端上线后,在服务端保存Client的localName:ip:port到一个双向链表中,方便添加删除操作。


文件:
一、 PubDefine.h           //公共成员定义
#ifndef _PUBDEFINE_H_
#define _PUBDEFINE_H_
//客户端命名的本地名称的长度,一般是个人名字的拼音,方便通信的辨别
const int LOCALNAMELEN = 64;
//获取在线用户信息,使用格式:"localname:ip:port;localname:ip:port"组合,解析. 这个长度如果用户多了还需要修改
const int USRINFOLEN = 2048;
//存放IP:Port形式的信息
const int IPPORTInfo = 64;
//聊天信息长度
const int CHATMSGLEN = 1920;    //为了使总长度为sizeof(CommonHead_S)+2048,因为接收buffer就这么长


//消息的CommandId
//1. 心跳消息(暂时未处理心跳)
const int HeartBeatReq = 0x00000001;
const int HeartBeatRsp = 0x80000001;

//2. 刷新消息,客户端用于刷新在线用户
const int RefreshReq = 0x00000002;
const int RefreshRsp = 0x80000002;

//3. 获取在线用户信息列表,Client第一次上线,获取在线用户刷新到list中
const int GetUsrListReq = 0x00000003;
const int GetUsrListRsp = 0x80000003;

//4. 下线消息,从Server中删掉Client信息,主要是IP、Port和socket信息。
const int OffLineReq = 0x00000004;
const int OffLineRsp = 0x80000004;

//5. 聊天消息
const int ChatMsgReq = 0x00000005;
const int ChatMsgRsp = 0x80000005;

//通用消息头
typedef struct tagCommonHead
{
 int nMsgTotalLen;                    //消息总长度
 int nCommandId;                      //消息的CommandId
 int nSequenceNum;                    //消息的序号
}CommonHead_S;

//心跳消息
typedef struct tagHeartBeat
{
 CommonHead_S Head;
}HeartBeat_S;

//刷新消息。Server返回用户数目及其相应信息
typedef struct tagRefreshMsg
{
 CommonHead_S Head;
 char cArryRefresh[USRINFOLEN];
}RefreshMsg_S;

//获取在线用户信息(兼上线消息)
typedef struct tagGetOnlineUsrInfo
{
 CommonHead_S Head;
 char cArryUsrInfo[USRINFOLEN];               //保存用户信息
}GetOnlineUsrInfo_S;

//下线消息
typedef struct tagOffLineMsg
{
 CommonHead_S Head;
 char cArryClientIP[16];
}OffLineMsg_S;

//聊天消息
typedef struct tagChatMsg
{
 CommonHead_S Head;
 char cArrySourceInfo[IPPORTInfo];           //本地的Ip:Port信息
 char cArryDestInfo[IPPORTInfo];             //聊天对端IP:Port形式
 char cArryMsg[CHATMSGLEN];                  //发送消息内容
}ChatMsg_S;

//localname:ip:port 单个节点度。即每个用户的长度
const int USRINFONODELEN = 64;
//用双向链表保存用户信息
typedef struct tagUsrInfoNode
{
 char cArryUsr[USRINFONODELEN];
 struct tagUsrInfoNode *pNodeNext;
 struct tagUsrInfoNode *pNodePrevious;
}UsrInfoNode_S;


//服务端监听端口
const short LISTENPORT = 14725;
//服务端接收消息内容长度,注意:不包括头
const int MSGBLOCKLEN = 2048;

#endif


二、 ThreadPoolBase.h     //线程池基类
/********************************************************************
File name:      ThreadPoolBase.h
Author:  lieyingshengbao  
Version:        1.0 
Date:           2013-1-6
Description:    本文件提供线程池的基类,工程中由接收线程和处理线程继承
Others:     本类型使用C++定义 
Function List:  请参见正文
History:        修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述
1. Date:
Author:
Modification:
2. ...
*********************************************************************/
#ifndef _THREAD_POOLBASE_H_
#define _THREAD_POOLBASE_H_
#include "stdafx.h"
#include <afxmt.h>           //互斥量
#include <process.h>         //_beginThreadEx开启线程
#include <queue>             //消息队列
#include <vector>            //保存线程,方便终止
using std::queue;
using std::vector;

//成功--失败标志
enum Symbol
{
 Success,
 Failed
};

//模板类,将任务抽象,方便接收类和处理类分别派生
template<typename T>
class CThreadPoolBase
{
public:
 CThreadPoolBase(int nThreadNum)                       //构造函数设置初始期望线程数目
 {
  m_nWantedThreadNum = nThreadNum;
  m_nCurrentThreadNum = 0;
  m_bQuitThread = false;
 }
 virtual ~CThreadPoolBase()                           //类中含有纯虚函数,所以析构函数为virtual的
 {}

 //在类中定义的函数都是默认的内联函数
 /*************************************************************************
 * Function name      :   GetCriticalSection
 * description        :   获取冲突域
 * input              :   无
 * output             :   指针
 * return             :   返回冲突域
 *************************************************************************/
 CCriticalSection *GetCriticalSection()
 {
  return &m_CriticalSection;
 }

 /*************************************************************************
 * Function name      :   AddTask
 * description        :   向消息队列中添加任务
 * input              :   T* pTask
 * output             :   无
 * return             :   无
 *************************************************************************/
 void AddTask(T* pTask)
 {
  GetCriticalSection()->Lock();
  m_TaskQueue.push(pTask);            //消息队列中添加任务
  m_Event.SetEvent();
  GetCriticalSection()->Unlock();
 }

 /*************************************************************************
 * Function name      :   CreateThreadPool
 * description        :   创建线程池
 * input              :   无
 * output             :   无
 * return             :   Success--0; Failed--1
 *************************************************************************/
 int CreateThreadPool()
 {
  HANDLE hHandle = NULL;
  while (m_nCurrentThreadNum < m_nWantedThreadNum)
  {
   hHandle = (HANDLE)_beginthreadex(NULL,
                

    NULL,
                

    CThreadPoolBase::ThreadFun,
                

    (LPVOID)this,
                

    NULL,
                

    NULL);
   if (NULL == hHandle)
   {
    AfxMessageBox(_T("Failed to create thread!"));
    return Failed;
   }
   else
   {
    //AfxMessageBox(_T("Create one thread!"));
    m_nCurrentThreadNum ++;
    m_vecThreadHandle.push_back(hHandle);
   }
  }
  return Success;
 }

 /*************************************************************************
 * Function name      :   QuitThread
 * description        :   关闭线程池
 * input              :   无
 * output             :   无
 * return             :   无
 *************************************************************************/
 void QuitThread()
 {
  if (m_bQuitThread)                           //关闭线程池的标志
  {
   for (vector<HANDLE>::size_type i=0; i!=m_vecThreadHandle.size(); ++i)
   {
    HANDLE hTempHandle = m_vecThreadHandle[i];
    if (NULL != hTempHandle)
    {
     WaitForSingleObject(hTempHandle, INFINITE);
     CloseHandle(hTempHandle);
     //AfxMessageBox(_T("Close one thread!"));
     hTempHandle = NULL;
    }
   }
  }

  m_nCurrentThreadNum = 0;
  m_vecThreadHandle.clear();
 }

protected:
 virtual void DealMsg(T* pTask) = 0;    //实际处理消息的函数为纯虚函数,供派生类重写

private:
 static unsigned __stdcall ThreadFun(LPVOID pParam)
 {
  CThreadPoolBase *pThis = (CThreadPoolBase *)pParam;
  while (!pThis->m_bQuitThread)
  {
   T* pTask = NULL;
   pThis->GetCriticalSection()->Lock();
   if (pThis->m_TaskQueue.empty())
   {
    pThis->GetCriticalSection()->Unlock();
    if (WAIT_TIMEOUT == WaitForSingleObject(pThis->m_Event.m_hObject, 1))
    {
     continue;
    }
   }
   else
   {
    pTask = pThis->m_TaskQueue.front();
    pThis->m_TaskQueue.pop();
    pThis->GetCriticalSection()->Unlock();
    pThis->DealMsg(pTask);                      //调用派生类方法
   }
  }
  return Success;
 }

private:
 int m_nCurrentThreadNum;               //当前线程数目
 int m_nWantedThreadNum;
 queue<T*> m_TaskQueue;
 CCriticalSection m_CriticalSection;   //冲突域
 CEvent m_Event;
 vector<HANDLE> m_vecThreadHandle;     //保存开辟线程池的线程Handle
public:
 bool m_bQuitThread;                   //退出线程池的标志
};

#endif


三、RecvMsg.h
/********************************************************************
File name:      RecvMsg.h
Author:  lieyingshengbao  
Version:        1.0 
Date:           2013-1-6
Description:    本文件提供创建socket及接收消息的类
Others:     本类型使用C++定义 
Function List:  请参见正文
History:        修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述
1. Date:
Author:
Modification:
2. ...
*********************************************************************/
#ifndef _RECVMSG_H_
#define _RECVMSG_H_
#include "ThreadPoolBase.h"
#include "PubDefine.h"
#include "DealMsg.h"
#include <WinSock2.h>
#include <map>
#include <vector>
using std::vector;
using std::map;


extern vector<SOCKET*> g_vecSocket;    //为了防止用户未发送下线消息,服务端就关闭,这样造成socket空间泄露
class CRecvMsg : public CThreadPoolBase<SOCKET>
{
public:
 CRecvMsg(int nRecvThreadNum):CThreadPoolBase(nRecvThreadNum),m_DealMsgObj(nRecvThreadNum) //默认接收和处理线程池个

数相同
 {
  m_SockListen = INVALID_SOCKET;
  m_SockConn = INVALID_SOCKET;
 }

 ~CRecvMsg()
 {
  if (INVALID_SOCKET != m_SockListen)
  {
   closesocket(m_SockListen);
   m_SockListen = INVALID_SOCKET;
  }

  if (INVALID_SOCKET != m_SockConn)
  {
   closesocket(m_SockConn);
   m_SockConn = INVALID_SOCKET;
  }

  //释放SOCKET new的空间,为了防止客户端不发下线消息,服务端直接关闭的情况
  for (vector<SOCKET*>::size_type i=0; i!=g_vecSocket.size(); ++i)
  {
   SOCKET *pTemp = g_vecSocket[i];
   if (pTemp)
   {
    closesocket(*pTemp);
    delete pTemp;
    pTemp = NULL;
   }
  }

 }

 //创建socket线程
 int CreateSocketThread();
 
 //初始化套接字
 int InitSocket();
 
 //获取消息长度
 int GetMsgLen(BYTE *pRecvBuffer);
 //int GetMsgCommandId();

 //创建处理消息的线程池
 void CreateDealMsgThreadPool()
 {
  m_DealMsgObj.CreateThreadPool();
 }

 //调用处理线程池的终止线程方法终止处理线程池
 void TerminateDealMsgThreadPool()
 {
  m_DealMsgObj.QuitThread();
 }

 //重写基类中方法,此处为实际接收消息
 void DealMsg(SOCKET* pSock);

 
private:
 static unsigned __stdcall ThreadDeal(LPVOID pParam); //套接字线程函数,包含创建socket、bind、listen、accept等
private:
 SOCKET m_SockListen;                //监听socket
 SOCKET m_SockConn;                  //连接socket
 CDealMsg m_DealMsgObj;
};

#endif

四、RecvMsg.cpp
#include "stdafx.h"
#include "RecvMsg.h"

//全局变量区域(尽量不用全局变量)---------------------------------------------------
CString g_sClientIP;                        //保存客户端ip信息。方便到DealMsg.cpp中处理
int g_nClientPort;
map<CString, SOCKET*> g_mapIPPort_Sock;     //保存客户端ip+port和连接socket之间关系,客户端下线时方便关闭服务端对应socket
vector<SOCKET*> g_vecSocket;      //为了防止用户还未发送下线消息,服务端就关闭,这样造成socket空间泄露
/*************************************************************************
* Function name      :   CreateSocketThread
* description            :   创建套接字线程
* input                       :   无
* output                    :   无
* return                     :   Success-0 Failed-1
*************************************************************************/
int CRecvMsg::CreateSocketThread()
{
 //创建接收线程池/处理线程池
 CreateThreadPool();
 CreateDealMsgThreadPool();
 HANDLE hHandle = (HANDLE)_beginthreadex(NULL,
                

      NULL,
                

      CRecvMsg::ThreadDeal,
                

      (LPVOID)this,
                

      NULL,
                

      NULL);
 if (NULL == hHandle)
 {
  AfxMessageBox(_T("RecvMsg.cpp--CreateSocketThread:Failed to create thread!"));
  return Failed;
 }

 return Success;
}

/*************************************************************************
* Function name      :   InitSocket
* description            :   初始化套接字
* input                       :   无
* output                    :   无
* return                     :   Success-0 Failed-1
*************************************************************************/
int CRecvMsg::InitSocket()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;

 wVersionRequested = MAKEWORD( 1, 1 );

 err = WSAStartup( wVersionRequested, &wsaData );
 if ( err != 0 ){
  return Failed;
 }

 if ( LOBYTE( wsaData.wVersion ) != 1 ||
  HIBYTE( wsaData.wVersion ) != 1 ) {
   WSACleanup();
   return Failed;
 }

 return Success;
}


/*************************************************************************
* Function name      :   CreateSocketThread
* description            :   创建套接字线程
* input                       :   无
* output                    :   无
* return                     :   Success-0 Failed-1
*************************************************************************/
unsigned __stdcall CRecvMsg::ThreadDeal(LPVOID pParam)
{
 CRecvMsg *pThis = (CRecvMsg*)pParam;

 //创建socket信息
 fd_set fdListen;
 struct timeval timeout = {0, 100};
 if (Success == pThis->InitSocket())            //初始化套接字成功
 {
  pThis->m_SockListen = socket( AF_INET, SOCK_STREAM, 0);
  if (SOCKET_ERROR == pThis->m_SockListen)
  {
   AfxMessageBox(_T("RecvMsg.cpp--ThreadDeal:Failed Create Socket"));
   return Failed;
  }

  //设置socket选项,重用地址
  int nReuseFlag = 1;
  int ret = setsockopt(pThis->m_SockListen, SOL_SOCKET, SO_REUSEADDR, (char*)&nReuseFlag, sizeof(int));

  //设置网络地址属性
  SOCKADDR_IN addr;
  memset(&addr, 0, sizeof(SOCKADDR_IN));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(LISTENPORT);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);

  //绑定地址
  ret = bind(pThis->m_SockListen, (struct sockaddr*)(&addr), sizeof(addr));
  if (SOCKET_ERROR == ret)
  {
   AfxMessageBox(_T("RecvMsg.cpp--ThreadDeal:Failed Bind socket"));
   return Failed;
  }
  //监听端口
  ret = listen(pThis->m_SockListen, SOMAXCONN);
  if (SOCKET_ERROR == ret)
  {
   AfxMessageBox(_T("RecvMsg.cpp--ThreadDeal:Failed Listen"));
   return Failed;
  }

  //客户端地址信息
  SOCKADDR_IN addrClient;
  int nLen = sizeof(addrClient);

  while (!pThis->m_bQuitThread)
  {
   if (SOCKET_ERROR == pThis->m_SockListen || INVALID_SOCKET == pThis->m_SockListen)
   {
    AfxMessageBox(_T("RecvMsg.cpp--ThreadDeal:Listen sock error!"));
    break;
   }

   FD_ZERO(&fdListen);
   FD_SET(pThis->m_SockListen, &fdListen);
   int nRet = select(0, &fdListen, NULL, NULL, &timeout);
   if ( SOCKET_ERROR == nRet )
   {
    AfxMessageBox(_T("select error"));
    break;
   }
   if ( 0 == nRet )             //超时,重新循环
   {
    continue;
   }

   pThis->m_SockConn = accept(pThis->m_SockListen, (struct sockaddr*)(&addrClient), &nLen);
   if ( pThis->m_SockConn == INVALID_SOCKET || SOCKET_ERROR == pThis->m_SockConn)
   {
    AfxMessageBox(_T("Connect Socket Error"));
    continue;
   }

   //通过addrClient获取客户端信息
   g_nClientPort = ntohs(addrClient.sin_port);
   g_sClientIP = inet_ntoa(addrClient.sin_addr);  //转为串形式
   CString sClientPortTemp;
   sClientPortTemp.Format("%d", g_nClientPort);
   CString sIPPortSum = (g_sClientIP + ":" + sClientPortTemp);  //将Client的ip和port拼接成ip:port形式

   SOCKET *pSock = new SOCKET;
   *pSock = pThis->m_SockConn;
   pThis->AddTask(pSock);           //驱动接收线程池运行。

   //为了防止服务端接收完连接后不等待客户端下线请求而直接关闭的情况,保存后在析构函数中释放
   g_vecSocket.push_back(pSock);
   //将Client的IPPort信息与连接socket对应加入map中
   g_mapIPPort_Sock.insert(map<CString, SOCKET*>::value_type(sIPPortSum, pSock));
  }
 }

 pThis->QuitThread();                            //终止接收线程池

 //终止处理线程池
 pThis->m_DealMsgObj.m_bQuitThread = true;
 pThis->TerminateDealMsgThreadPool();
 
 AfxMessageBox(_T("Listen Thread exit!"));

 return Success;
}

/*************************************************************************
* Function name      :   GetMsgLen
* description            :   解析消息头,获取消息长度
* input                       :   BYTE *pRecvBuffer
* output                    :   无
* return                     :   消息总长度
*************************************************************************/
int CRecvMsg::GetMsgLen(BYTE *pRecvBuffer)
{
 CommonHead_S *pHead = (CommonHead_S*)pRecvBuffer;
 int nMsgLen = ntohl(pHead->nMsgTotalLen);                        //注意转换
 return nMsgLen;
}


/*************************************************************************
* Function name      :   DealMsg
* description            :   重写基类方法,接收消息
* input                       :   SOCKET* pSock
* output                    :   无
* return                     :   无
*************************************************************************/
void CRecvMsg::DealMsg(SOCKET* pSock)
{
 fd_set fdsock;
 struct timeval timeout = {0, 100};
 //一定注意:如果申请的空间不够,接收的内容超出Buffer大小,释放Buffer时会崩溃。
 BYTE *pRecvBuffer = new BYTE[MSGBLOCKLEN + sizeof(CommonHead_S)];
 //BYTE *pDelete = pRecvBuffer;   完全不需要。因为未移动pRecvBuffer
 while (!m_bQuitThread)
 {
  FD_ZERO(&fdsock);
  FD_SET(*pSock, &fdsock);
  int nRet = select(0, &fdsock, NULL, NULL, &timeout);
  if ( SOCKET_ERROR == nRet )
  {
   //AfxMessageBox(_T("CRecvMsg::DealMsg: select error"));
   break;
  }
  if ( 0 == nRet )             //超时,重新循环
  {
   continue;
  }

  int nRecvHeadLen = 0;
  int nRecvHeadTotalLen = 0;
  memset(pRecvBuffer, 0, MSGBLOCKLEN+sizeof(CommonHead_S));
  while (true)              //先接收头
  {
   nRecvHeadLen = recv(*pSock, (char*)(pRecvBuffer + nRecvHeadTotalLen), sizeof(CommonHead_S) -

nRecvHeadTotalLen, 0);
   if (nRecvHeadLen == SOCKET_ERROR || nRecvHeadLen == 0)
   {
    //AfxMessageBox(_T("Client disconnect or error happen when recv Msg Head"));
    //客户端断开连接、释放空间
    /*if (INVALID_SOCKET != *pSock)    //不能在此处关闭,否则上面的select会出错
    {
     closesocket(*pSock);
     delete pSock;
     pSock = NULL;
    }*/
    if (pRecvBuffer)
    {
     delete [] pRecvBuffer;
     pRecvBuffer = NULL;
    }
    return;
   }
   nRecvHeadTotalLen += nRecvHeadLen;
   if (nRecvHeadTotalLen < sizeof(CommonHead_S))
   {
    Sleep(200);
    continue;
   }
   if (nRecvHeadTotalLen >= sizeof(CommonHead_S))
   {
    break;
   }
  }

  //解析消息总长度
  int nMsgSumLen = GetMsgLen(pRecvBuffer);
  if (nRecvHeadTotalLen < nMsgSumLen)
  {
   int nRecvBodyLen = 0;
   int nRecvBodyTotalLen = 0;
   int nRemainMsgLen = nMsgSumLen - nRecvHeadTotalLen;
   while (1)
   {
    nRecvBodyLen = recv(*pSock, (char*)(pRecvBuffer + nRecvHeadTotalLen + nRecvBodyTotalLen),

nRemainMsgLen - nRecvBodyTotalLen, 0);
    if (nRecvBodyLen == SOCKET_ERROR || nRecvBodyLen == 0)
    {
     //AfxMessageBox(_T("Client disconnect or error happen when recv Msg Body"));
     if (pRecvBuffer)
     {
      delete [] pRecvBuffer;
      pRecvBuffer = NULL;
     }
     return;
    }

    nRecvBodyTotalLen += nRecvBodyLen;
    if (nRecvBodyTotalLen < nRemainMsgLen)
    {
     Sleep(200);
     continue;
    }
    if (nRecvBodyTotalLen >= nRemainMsgLen)
    {
     break;
    }
   }
  }

  //接收完消息,然后将消息加入到处理消息队列中
  CMsg *pMsg = new CMsg;
  pMsg->m_SockConn = pSock;
  memset(pMsg->cArryMsg, 0, MSGBLOCKLEN+sizeof(CommonHead_S));
  memcpy(pMsg->cArryMsg, pRecvBuffer, MSGBLOCKLEN+sizeof(CommonHead_S));
  m_DealMsgObj.AddTask(pMsg);       //将CMsg加入处理线程池
 }

 if (pRecvBuffer)
 {
  delete [] pRecvBuffer;
  pRecvBuffer = NULL;
 }

 AfxMessageBox(_T("Quit recv proc"));
 return;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值