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

本文介绍了一个简单的聊天程序的设计与实现过程,包括客户端与服务器之间的交互流程、消息格式定义、socket通信处理等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

聊天程序,Client端代码及解释:
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

//Client绑定的Port(为了处理方便,将客户端绑定端口)
const int BINDPORT = 65432;


//消息的CommandId
//1. 心跳消息
const int HeartBeatReq = 0x00000001;
const int HeartBeatRsp = 0x80000001;

//2. 刷新消息
const int RefreshReq = 0x00000002;
const int RefreshRsp = 0x80000002;

//3. 获取在线用户信息 列表消息,显示到本地list中
const int GetUsrListReq = 0x00000003;
const int GetUsrListRsp = 0x80000003;

//4. 下线消息
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];
 char cArryDestInfo[IPPORTInfo];                      //存放聊天对端IP:Port形式
 char cArryMsg[CHATMSGLEN];
}ChatMsg_S;

#endif

二、Socket.h            //socket信息
#ifndef _SOCKET_H_
#define _SOCKET_H_
#include "PubDefine.h"
#include <WinSock2.h>

class CSockConn
{
public:
 CSockConn();
 ~CSockConn();

 int InitSocket();                       //初始化socket,返回值0--成功 ,1---失败
 int ConnectServer();                    //连接Server, 返回值0--成功,1---失败
 int SendMsg(char* pSendMsg, int nMsgLen);     //发送消息
 char* RecvMsg(int nRecvMsgLen);               //接收消息
 int GetMsgLen(BYTE *pRecvBuffer);
private:
 SOCKET m_SockConn;                  //连接socket
public:
 char m_cArryServerIp[16];           //连接Server Ip
 short m_nPort;                      //连接server端口
 bool m_bTerminate;
};

#endif

三、Socket.cpp
//本cpp文件提供Socket支持
#include "stdafx.h"
#include "Socket.h"

//构造函数
CSockConn::CSockConn()
{
 m_SockConn = INVALID_SOCKET;
 memset(m_cArryServerIp, 0, 16);
 m_nPort = 0;
 m_bTerminate = false;
}

//析构函数
CSockConn::~CSockConn()
{
 if (m_SockConn != INVALID_SOCKET)
 {
  closesocket(m_SockConn);
  m_SockConn = INVALID_SOCKET;
 }
}

//初始化套接字
//返回值:0---成功     1---失败
int CSockConn::InitSocket()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;

 wVersionRequested = MAKEWORD( 1, 1 );

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

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

 //最后需要清除
 //WSACleanup();
 return 0;
}

//连接Server
//返回值, 0---成功  1---失败
int CSockConn::ConnectServer()
{
 int nInitSuccess = InitSocket();
 if ( 1 == nInitSuccess )
 {
  AfxMessageBox(_T("Failed to InitSocket!"));
  return 1;
 }

 m_SockConn = socket(AF_INET, SOCK_STREAM, 0);
 if ( m_SockConn == INVALID_SOCKET )
 {
  CString sError;
  sError.Format("Failed to Create socket! %ld\n", WSAGetLastError());
  AfxMessageBox(sError);
  WSACleanup();
  return 1;
 }

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

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

 //绑定端口、为了处理方便
 ret = bind(m_SockConn, (struct sockaddr*)(&addrClient), sizeof(addrClient));
 if (SOCKET_ERROR == ret)
 {
  AfxMessageBox(_T("ConnectServer:Failed Bind port"));
  return 1;
 }

 SOCKADDR_IN AddrServer;
 AddrServer.sin_addr.S_un.S_addr=inet_addr((char*)m_cArryServerIp);
 AddrServer.sin_family=AF_INET;
 AddrServer.sin_port=htons(m_nPort);

 if ( connect( m_SockConn, (SOCKADDR*) &AddrServer, sizeof(SOCKADDR) ) == SOCKET_ERROR)
 {
  CString sError;
  sError.Format("Failed to connect server! %ld\n", WSAGetLastError());
  AfxMessageBox(sError);
  WSACleanup();
  return 1;
 }

 return 0;
}

//发送消息
//成功0;失败1
int CSockConn::SendMsg(char* pSendMsg, int nMsgLen)
{
 if (SOCKET_ERROR == send(m_SockConn, pSendMsg, nMsgLen, 0))
 {
  AfxMessageBox(_T("Failed to send Msg"));
  return 1;
 }
 return 0;
}

//解析接收到的消息,返回消息总长度
int CSockConn::GetMsgLen(BYTE *pRecvBuffer)
{
 CommonHead_S *pHead = (CommonHead_S*)pRecvBuffer;
 int nMsgLen = ntohl(pHead->nMsgTotalLen);                        //注意转换
 return nMsgLen;
}

//接收消息
//返回接收到的消息内容
char* CSockConn::RecvMsg(int nRecvMsgLen)
{
 fd_set fdsock;
 struct timeval timeout = {0, 100};
 BYTE *pRecvBuffer = new BYTE[nRecvMsgLen];    //接收消息长度
 char *pReturn = (char*)pRecvBuffer;
 while (!m_bTerminate)
 {
  FD_ZERO(&fdsock);
  FD_SET(m_SockConn, &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;
  while (true)              //先接收头
  {
   nRecvHeadLen = recv(m_SockConn, (char*)(pRecvBuffer + nRecvHeadTotalLen), sizeof(CommonHead_S) -

nRecvHeadTotalLen, 0);
   if (nRecvHeadLen == SOCKET_ERROR || nRecvHeadLen == 0)
   {
    AfxMessageBox(_T("peer disconnect or error happen when recv Msg Head"));
    return NULL;
   }
   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(m_SockConn, (char*)(pRecvBuffer + nRecvHeadTotalLen +

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

    nRecvBodyTotalLen += nRecvBodyLen;
    if (nRecvBodyTotalLen < nRemainMsgLen)
    {
     Sleep(200);
     continue;
    }
    if (nRecvBodyTotalLen >= nRemainMsgLen)
    {
     break;
    }
   }
  }
  break;                      //接收完消息,跳出循环
 }
 return pReturn;
}

四、DeliverClientDlg.h          //上线窗口
#pragma once
#include "afxwin.h"
#include "afxcmn.h"
#include "MutualDlg.h"
#include "Socket.h"
#include <vector>
using std::vector;

// CDeliverClientDlg dialog
class CDeliverClientDlg : public CDialog
{
// Construction
public:
 CDeliverClientDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data
 enum { IDD = IDD_DELIVERCLIENT_DIALOG };

 protected:
 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support


// Implementation
protected:
 HICON m_hIcon;

 // Generated message map functions
 virtual BOOL OnInitDialog();
 afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
 afx_msg void OnPaint();
 afx_msg HCURSOR OnQueryDragIcon();
 DECLARE_MESSAGE_MAP()
public:
 afx_msg void OnBnClickedButtonclose();
public:
 CEdit m_LocalNameCtrl;
public:
 CString m_LocalNameValue;
public:
 CEdit m_ServerIpCtrl;
public:
 CString m_ServerIpValue;
public:
 CEdit m_ServerPortCtrl;
public:
 CString m_ServerPortValue;
public:
 CListCtrl m_UserList;
public:
 afx_msg void OnBnClickedButtonfresh();
public:
 afx_msg void OnBnClickedButtononline();
public:
 afx_msg void OnNMDblclkListuserlist(NMHDR *pNMHDR, LRESULT *pResult);
public:
 CStatusBar m_wndStatusBar;                 //窗口下方的状态条
public:
 afx_msg void OnTimer(UINT_PTR nIDEvent);   //计时
private:
 void GetOnLineClientNum(CStringArray &cArryStr);     //获取在线用户信息
 int GetClientIPInfo(CString &sClientIPInfo);         //获取客户端本地的Ip信息,只是处理一张网卡的情况
 void ParseEntireToPart(CString sMsg, CStringArray &cArryStr);
 void RefreshUsrList(CStringArray &cArryStr);         //点击刷新按钮获取在线用户消息
private:
 CSockConn m_Sock;             //socket对象
 CString m_sClientIPInfo;
 CButton m_OnLineButton;
 CMutualDlg m_ChatDeliverDlg;  //聊天子窗口
 bool m_bOnLineMark;           //当未上线时,false;上线后true。(上线表示跟服务器已经连接了)
};

五、DeliverClientDlg.cpp        //上线窗口
// DeliverClientDlg.cpp : implementation file
//

#include "stdafx.h"
#include "DeliverClient.h"
#include "DeliverClientDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


static UINT indicators[] =      //在resource的String Table中添加的两个ID,供状态栏使用
{
 IDS_INDICATOR_MESSAGE,
 IDS_INDICATOR_TIME
};
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
 CAboutDlg();

// Dialog Data
 enum { IDD = IDD_ABOUTBOX };

 protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
 DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


// CDeliverClientDlg dialog


CDeliverClientDlg::CDeliverClientDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CDeliverClientDlg::IDD, pParent)
 , m_LocalNameValue(_T(""))
 , m_ServerIpValue(_T(""))
 , m_ServerPortValue(_T(""))
{
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 m_bOnLineMark = false;
}

void CDeliverClientDlg::DoDataExchange(CDataExchange* pDX)   //被UpdateData调用
{
 CDialog::DoDataExchange(pDX);
 DDX_Control(pDX, IDC_EDITLocalName, m_LocalNameCtrl);
 DDX_Text(pDX, IDC_EDITLocalName, m_LocalNameValue);
 DDX_Control(pDX, IDC_EDITServerIP, m_ServerIpCtrl);
 DDX_Text(pDX, IDC_EDITServerIP, m_ServerIpValue);
 DDX_Control(pDX, IDC_EDITServerPort, m_ServerPortCtrl);
 DDX_Text(pDX, IDC_EDITServerPort, m_ServerPortValue);
 DDX_Control(pDX, IDC_LISTUserList, m_UserList);
 DDX_Control(pDX, IDC_BUTTONOnLine, m_OnLineButton);
}

BEGIN_MESSAGE_MAP(CDeliverClientDlg, CDialog)
 ON_WM_SYSCOMMAND()
 ON_WM_PAINT()
 ON_WM_QUERYDRAGICON()
 //}}AFX_MSG_MAP
 ON_BN_CLICKED(IDC_BUTTONClose, &CDeliverClientDlg::OnBnClickedButtonclose)
 ON_BN_CLICKED(IDC_BUTTONFresh, &CDeliverClientDlg::OnBnClickedButtonfresh)
 ON_BN_CLICKED(IDC_BUTTONOnLine, &CDeliverClientDlg::OnBnClickedButtononline)
 ON_NOTIFY(NM_DBLCLK, IDC_LISTUserList, &CDeliverClientDlg::OnNMDblclkListuserlist)
 ON_WM_TIMER()
END_MESSAGE_MAP()


//解析字符串,将整体化为部分,逐条保存。例如:解析"qin:IP:Port;gang:IP:Port"
void CDeliverClientDlg::ParseEntireToPart(CString sMsg, CStringArray &cArryStr)
{
 CString sLeft;
 CString sRight;
 int nPos = sMsg.Find(";");
 if (-1 == nPos)
 {
  cArryStr.Add(sMsg);
 }
 else
 {
  sLeft = sMsg.Left(nPos);
  sRight = sMsg.Right(sMsg.GetLength() - nPos - 1);
  cArryStr.Add(sLeft);
  while (!sRight.IsEmpty())
  {
   nPos = sRight.Find(";");
   if (-1 == nPos)
   {
    cArryStr.Add(sRight);
    break;
   }
   else
   {
    sLeft = sRight.Left(nPos);
    sRight = sRight.Right(sRight.GetLength() - nPos - 1);
    cArryStr.Add(sLeft);
   }
  }
 }
 return;
}

//获取在线客户端数目及其相应信息
void CDeliverClientDlg::GetOnLineClientNum(CStringArray &cArryStr)
{
 //连接服务端
        m_Sock.ConnectServer();
 
 //发送消息获取在线客户端信息
 GetOnlineUsrInfo_S GetUsrInfoObj;
 GetUsrInfoObj.Head.nSequenceNum = htonl(3);
 GetUsrInfoObj.Head.nCommandId = htonl(GetUsrListReq);
 memset(GetUsrInfoObj.cArryUsrInfo, 0, USRINFOLEN);
 memcpy(GetUsrInfoObj.cArryUsrInfo, m_LocalNameValue.GetBuffer(m_LocalNameValue.GetLength()),

m_LocalNameValue.GetLength());
 GetUsrInfoObj.Head.nMsgTotalLen = htonl(sizeof(GetOnlineUsrInfo_S));

 //调用socket.h中的函数SendMsg发送消息
 m_Sock.SendMsg((char*)&GetUsrInfoObj, sizeof(GetOnlineUsrInfo_S));

 //调用socket.h中的函数RecvMsg接收服务端消息
 char* pRecvMsg = m_Sock.RecvMsg(sizeof(GetOnlineUsrInfo_S));
 char* pDeleteBuffer = pRecvMsg;
 pRecvMsg += sizeof(CommonHead_S);
 CString sMsg = pRecvMsg;

 ParseEntireToPart(sMsg, cArryStr);      //解析接收到的在线用户信息,整体到部分

 if (pDeleteBuffer)
 {
  delete [] pDeleteBuffer;
  pDeleteBuffer = NULL;
 }
 return;
}

// CDeliverClientDlg message handlers

BOOL CDeliverClientDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  }
 }

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon

 // TODO: Add extra initialization here
 DWORD dwStyleFun = m_UserList.GetExtendedStyle();
 dwStyleFun |= LVS_EX_FULLROWSELECT;
 dwStyleFun |= LVS_EX_GRIDLINES;
 m_UserList.SetExtendedStyle(dwStyleFun); //设置列表的扩展风格

 //初始化List
 m_UserList.InsertColumn( 0, "LocalName", LVCFMT_LEFT, 180);
 m_UserList.InsertColumn( 1, "Ip", LVCFMT_LEFT, 180);
 m_UserList.InsertColumn( 2, "Port", LVCFMT_LEFT, 160);

 //创建两个状态栏
 m_wndStatusBar.Create(this);
 m_wndStatusBar.SetIndicators(indicators,2);         //Set the number of panes
 CRect rect;
 GetClientRect(&rect);

 //第一个显示在线用户数;第二个实时显示时间
 m_wndStatusBar.SetPaneInfo(0,IDS_INDICATOR_MESSAGE,SBPS_NORMAL,rect.Width()-100);     
 m_wndStatusBar.SetPaneInfo(1,IDS_INDICATOR_TIME,SBPS_STRETCH ,0);

 //This is where we actually draw it on the screen。实际显示到界面窗口上
 RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,IDS_INDICATOR_TIME);

 SetTimer(1,1000,NULL);

 return TRUE;  // return TRUE  unless you set the focus to a control
}

void CDeliverClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
 if ((nID & 0xFFF0) == IDM_ABOUTBOX)
 {
  CAboutDlg dlgAbout;
  dlgAbout.DoModal();
 }
 else
 {
  CDialog::OnSysCommand(nID, lParam);
 }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CDeliverClientDlg::OnPaint()
{
 if (IsIconic())
 {
  CPaintDC dc(this); // device context for painting

  SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

  // Center icon in client rectangle
  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() - cxIcon + 1) / 2;
  int y = (rect.Height() - cyIcon + 1) / 2;

  // Draw the icon
  dc.DrawIcon(x, y, m_hIcon);
 }
 else
 {
  CDialog::OnPaint();
 }
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CDeliverClientDlg::OnQueryDragIcon()
{
 return static_cast<HCURSOR>(m_hIcon);
}

//获取客户端本地的IP信息
//返回值:成功--0  失败--1
int CDeliverClientDlg::GetClientIPInfo(CString &sClientIPInfo)
{
 WSAData wsaData;
 if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)     //必须有初始化,否则gethostname会报错
 {
  system("pause");
  return 255;
 }

 char host_name[255];
 memset(host_name, 0, 255);
 //获取本地主机名称
 if (gethostname(host_name, sizeof(host_name)) == SOCKET_ERROR) {
  CString sInfo;
  sInfo.Format("Error %d when getting local host name.n", WSAGetLastError());
  AfxMessageBox(sInfo);
  return 1;
 }
 //printf("Host name is: %s/n", host_name);
 //从主机名数据库中得到对应的“主机”
 struct hostent *phe = gethostbyname(host_name);
 if (phe == 0) {
  AfxMessageBox(_T("Yow! Bad host lookup."));
  return 1;
 }

 //循环得出本地机器所有IP地址
 for (int i = 0; phe->h_addr_list[i] != 0; ++i) {
  struct in_addr addr;
  memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr));
  //printf("Address %s\n" , inet_ntoa(addr));
  //sClientIPInfo = inet_ntoa(addr);            //暂时只处理只有一张网卡的情况
  CString sIPinfo = inet_ntoa(addr);            //为了处理大网和小网情况
  if (-1 != sIPinfo.Find("10."))                //过滤掉小网地址,只取大网
  {
   sClientIPInfo = sIPinfo;
  }
 }
 WSACleanup();
 return 0;
}


void CDeliverClientDlg::OnBnClickedButtonclose()
{
 // TODO: Add your control notification handler code here
 //发送下线消息,通知服务端关掉相应socket
 //1. 已经获取客户端本地IP地址,发送到服务端,服务端通过IP、port信息找到map中对应的socket关闭
 //2. 构造下线消息、发送
 if (m_bOnLineMark)    //如果已经上线,发送下线消息;否则,不需要发送,直接关闭窗口即可
 {
  OffLineMsg_S OffLineObj;
  OffLineObj.Head.nCommandId = htonl(OffLineReq);
  OffLineObj.Head.nSequenceNum = htonl(4);
  memset(OffLineObj.cArryClientIP, 0, 16);
  memcpy(OffLineObj.cArryClientIP, m_sClientIPInfo.GetBuffer(m_sClientIPInfo.GetLength()),

m_sClientIPInfo.GetLength());
  OffLineObj.Head.nMsgTotalLen = htonl(sizeof(OffLineMsg_S));

  m_Sock.SendMsg((char*)&OffLineObj, sizeof(OffLineMsg_S));  //发送下线消息
  m_Sock.m_bTerminate = true;
  Sleep(500);
 }
 
 OnCancel();
}


//刷新按钮刷新在线用户信息
void CDeliverClientDlg::RefreshUsrList(CStringArray &cArryStr)
{
 //发送刷新消息获取在线客户端信息
 RefreshMsg_S RefreshMsgObj;
 RefreshMsgObj.Head.nSequenceNum = htonl(2);
 RefreshMsgObj.Head.nCommandId = htonl(RefreshReq);
 memset(RefreshMsgObj.cArryRefresh, 0, USRINFOLEN);
 RefreshMsgObj.Head.nMsgTotalLen = htonl(sizeof(RefreshMsg_S));

 m_Sock.SendMsg((char*)&RefreshMsgObj, sizeof(RefreshMsg_S));

 //接收服务端消息
 char* pRecvMsg = m_Sock.RecvMsg(sizeof(RefreshMsg_S));
 char* pDeleteBuffer = pRecvMsg;
 pRecvMsg += sizeof(CommonHead_S);
 CString sMsg = pRecvMsg;
 ParseEntireToPart(sMsg, cArryStr);

 if (pDeleteBuffer)
 {
  delete [] pDeleteBuffer;
  pDeleteBuffer = NULL;
 }
 
 return;
}


//刷新用户列表, 注意去掉本身的信息,只显示其他Client信息
void CDeliverClientDlg::OnBnClickedButtonfresh()
{
 // TODO: Add your control notification handler code here
 //获取在线用户,不需要重新连接,直接发送请求消息即可
 CStringArray cArryStr;
 RefreshUsrList(cArryStr);  //获取在线客户端信息(服务端完全返回,Client端负责过滤掉本地的,然后显示。)
 m_UserList.DeleteAllItems();
 INT_PTR nCount = cArryStr.GetCount();
 //List列表中显示其他在线客户端
 for (INT_PTR i=0; i<nCount; ++i)
 {
  CString sTemp = cArryStr.GetAt(i);
  int nLocalIpPos = sTemp.Find(m_sClientIPInfo);
  if (-1 != nLocalIpPos)
  {
   continue;
  }
  int nPos = sTemp.Find(":");
  CString sLocalName = sTemp.Left(nPos);
  sTemp = sTemp.Right(sTemp.GetLength() - nPos -1);
  nPos = sTemp.Find(":");
  CString sIP = sTemp.Left(nPos);
  CString sPort = sTemp.Right(sTemp.GetLength() - nPos -1);
  int nListCount = m_UserList.GetItemCount();
  m_UserList.InsertItem(nListCount, sLocalName);
  m_UserList.SetItemText(nListCount, 1, sIP);
  m_UserList.SetItemText(nListCount, 2, sPort);
 }

 //状态栏中显示在线用户数目
 CString sInfo;
 sInfo.Format("Hello,Welcome! Now %d Users Online",nCount-1);
 m_wndStatusBar.SetPaneText(0, sInfo);
}

//上线
void CDeliverClientDlg::OnBnClickedButtononline()
{
 // TODO: Add your control notification handler code here
 //点击上线时候要从Server获取在线客户端的个数及相应客户端信息
 UpdateData(TRUE);
 m_LocalNameValue.Trim("");
 m_ServerIpValue.Trim(" ");        //过滤掉空格
 m_ServerPortValue.Trim(" ");      //过滤掉空格
 if (m_LocalNameValue.IsEmpty())   //为了通过localname清晰区分不同的用户,而不是根据IP区分。必须填写
 {
  AfxMessageBox(_T("LocalName empty!"));
  m_LocalNameCtrl.SetFocus();
  return;
 }
 if (m_ServerIpValue.IsEmpty())
 {
  AfxMessageBox(_T("ServerIP empty!"));
  return;
 }
 if (m_ServerPortValue.IsEmpty())
 {
  AfxMessageBox(_T("ServerPort empty!"));
  return;
 }
 
 memcpy(m_Sock.m_cArryServerIp, m_ServerIpValue.GetBuffer(m_ServerIpValue.GetLength()), 16);
 m_Sock.m_nPort = atoi(m_ServerPortValue);

 m_bOnLineMark = true;
 GetClientIPInfo(m_sClientIPInfo);  //获取本地IP地址
 
 CStringArray cArryStr;
 GetOnLineClientNum(cArryStr);    //获取在线客户端信息(服务端完全返回,Client端过滤掉本地的,然后显示。)
 INT_PTR nCount = cArryStr.GetCount();

 //List列表中显示其他在线客户端
 for (INT_PTR i=0; i<nCount; ++i)
 {
  CString sTemp = cArryStr.GetAt(i);
  int nLocalIpPos = sTemp.Find(m_sClientIPInfo);
  if (-1 != nLocalIpPos)
  {
   continue;
  }
  int nPos = sTemp.Find(":");
  CString sLocalName = sTemp.Left(nPos);
  sTemp = sTemp.Right(sTemp.GetLength() - nPos -1);
  nPos = sTemp.Find(":");
  CString sIP = sTemp.Left(nPos);
  CString sPort = sTemp.Right(sTemp.GetLength() - nPos -1);
  int nListCount = m_UserList.GetItemCount();
  m_UserList.InsertItem(nListCount, sLocalName);
  m_UserList.SetItemText(nListCount, 1, sIP);
  m_UserList.SetItemText(nListCount, 2, sPort);
 }

 //状态栏中显示对应内容
 CString sInfo;
 sInfo.Format("Hello,Welcome! Now %d Users Online",nCount-1);
 m_wndStatusBar.SetPaneText(0, sInfo);

 m_OnLineButton.EnableWindow(FALSE);
 //客户端不需要发送上线消息,服务端接收连接后如果链表中不存在,则自动加入链表中

 //开辟线程,死循环发送心跳消息
 //此处省略心跳消息
}


//List列表的双击事件、打开另一个窗口
void CDeliverClientDlg::OnNMDblclkListuserlist(NMHDR *pNMHDR, LRESULT *pResult)
{
 // TODO: Add your control notification handler code here
 *pResult = 0;
 //界面列表中没有内容,即没有其他Client的信息
 int nClientCount = m_UserList.GetItemCount();
 if (0 == nClientCount)
 {
  AfxMessageBox(_T("No Client Info,Refresh list first!"));
  return;
 }

 //如果有用户信息,则取出用户信息的列,显示到聊天窗口中去、
 for (int i=0; i<nClientCount; ++i)
 {
  if (m_UserList.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED)
  {
   m_ChatDeliverDlg.m_sDestLocalName = m_UserList.GetItemText(i, 0);
   m_ChatDeliverDlg.m_sDestIP = m_UserList.GetItemText(i, 1);
   m_ChatDeliverDlg.m_sDestPort = m_UserList.GetItemText(i, 2);
   break;
  }
 }
 m_ChatDeliverDlg.m_pSock = &m_Sock;
 m_ChatDeliverDlg.m_sSourceIP = m_sClientIPInfo;   //传递本地IP信息
 m_ChatDeliverDlg.DoModal();         //弹出窗口之前,将成员变量赋值
}

//计时器
void CDeliverClientDlg::OnTimer(UINT_PTR nIDEvent)
{
 // TODO: Add your message handler code here and/or call default
 CTime t1;
 t1=CTime::GetCurrentTime();
 m_wndStatusBar.SetPaneText(1,t1.Format("%H:%M:%S"));
 CDialog::OnTimer(nIDEvent);
}


六、MutualDlg.h               //聊天子窗口
#pragma once
#include "afxwin.h"
#include "PubDefine.h"
#include "Socket.h"
#include <process.h>
#include <afxmt.h>            //互斥量头文件
#include <queue>
using std::queue;

#define UM_MSG WM_USER+111    //自定义消息,由子线程发送,主线程接收,操作控件。

//struct保存当前对象和窗口句柄
class CMutualDlg;             //声明类
typedef struct tagHwndObj
{
 CMutualDlg *pThis;
 HWND hWnd;
}HwndObj_S;
// CMutualDlg dialog

class CMutualDlg : public CDialog
{
 DECLARE_DYNAMIC(CMutualDlg)

public:
 CMutualDlg(CWnd* pParent = NULL);   // standard constructor
 virtual ~CMutualDlg();

// Dialog Data
 enum { IDD = IDD_DIALOGChatDeliver };

protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

 DECLARE_MESSAGE_MAP()
public:
 virtual BOOL OnInitDialog();
public:
 afx_msg void OnBnClickedCancel();
public:
 CEdit m_EditMsgCtrl;
public:
 CString m_EditMsgValue;
public:
 CEdit m_ShowMsgCtrl;
public:
 CString m_ShowMsgValue;
public:
 CString m_sDestLocalName;
 CString m_sDestIP;
 CString m_sDestPort;
 CString m_sSourceIP;
 CString m_sSourceInfo;               //源ip和port组合信息
 CSockConn *m_pSock;
public:
 CEdit m_DestInfoCtrl;
public:
 CString m_DestInfoValue;        //localname + ip + port
 CString m_DestIPPort;           //目的地的IP和Port,不需要LocalName,方便服务端查找对端socket
public:
 afx_msg void OnBnClickedOk();
private:
 static unsigned __stdcall ThreadSendMsg(LPVOID pParam); //开线程发送消息。
 static UINT __cdecl ThreadRecvMsg(LPVOID pParam);       //开线程接收消息。收到消息后添加到Show文本框中
 LRESULT OnMsg(WPARAM wParam, LPARAM lParam);            //自定义消息响应函数
public:
 CCriticalSection *GetCriticalSection()
 {
  return &m_CriticalSection;
 }
 void AddTask(char* pTask)
 {
  GetCriticalSection()->Lock();
  m_TaskQueue.push(pTask);
  m_Event.SetEvent();
  GetCriticalSection()->Unlock();
 }
private:
 bool m_bExitThread;              //退出开辟的发送和接收线程
 HwndObj_S m_HwndObj;             //保存当前对象和窗口句柄
 CCriticalSection m_CriticalSection;
 CEvent m_Event;
 queue<char*> m_TaskQueue;        //存放待发送的消息
 char *m_pSendMsg;                //用于存储Edit控件编辑的要发送的消息
};

七、MutualDlg.cpp           //聊天窗口
// MutualDlg.cpp : implementation file
//

#include "stdafx.h"
#include "DeliverClient.h"
#include "MutualDlg.h"


// CMutualDlg dialog

IMPLEMENT_DYNAMIC(CMutualDlg, CDialog)

CMutualDlg::CMutualDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CMutualDlg::IDD, pParent)
 , m_EditMsgValue(_T(""))
 , m_ShowMsgValue(_T(""))
 , m_DestInfoValue(_T(""))
{
 m_bExitThread = false;
 m_pSock = NULL;
 m_pSendMsg = new char[USRINFOLEN];
 memset(m_pSendMsg, 0, USRINFOLEN);
}

CMutualDlg::~CMutualDlg()
{
 if (m_pSendMsg)
 {
  delete [] m_pSendMsg;
  m_pSendMsg = NULL;
 }
}

void CMutualDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 DDX_Control(pDX, IDC_EDITDeliver, m_EditMsgCtrl);
 DDX_Text(pDX, IDC_EDITDeliver, m_EditMsgValue);
 DDX_Control(pDX, IDC_EDITShow, m_ShowMsgCtrl);
 DDX_Text(pDX, IDC_EDITShow, m_ShowMsgValue);
 DDX_Control(pDX, IDC_EDITDestInfo, m_DestInfoCtrl);
 DDX_Text(pDX, IDC_EDITDestInfo, m_DestInfoValue);
}


BEGIN_MESSAGE_MAP(CMutualDlg, CDialog)
 ON_BN_CLICKED(IDCANCEL, &CMutualDlg::OnBnClickedCancel)
 ON_BN_CLICKED(IDOK, &CMutualDlg::OnBnClickedOk)
 ON_MESSAGE(UM_MSG, OnMsg)    //自定义消息添加处理函数
END_MESSAGE_MAP()


// CMutualDlg message handlers

BOOL CMutualDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 m_ShowMsgCtrl.SetReadOnly(TRUE);  //Edit只读,但是可以拉动下拉条
 m_DestInfoValue = m_sDestLocalName + ":" + m_sDestIP + ":" + m_sDestPort;
 m_DestIPPort = m_sDestIP + ":" + m_sDestPort;    //保存聊天对端的Ip和Port,通过server查找对应的socket通信
 CString sSourcePort;
 sSourcePort.Format("%d", BINDPORT);              //获取Client绑定的port
 m_sSourceInfo = m_sSourceIP + ":" + sSourcePort; //源IP、Port信息

 
 //将信息发送到Server端.开辟线程处理
 HANDLE hHandle = (HANDLE)_beginthreadex(NULL,
  NULL,
  CMutualDlg::ThreadSendMsg,
  (LPVOID)this,
  NULL,
  NULL);
 if (NULL == hHandle)
 {
  AfxMessageBox(_T("Failed to create thread to send Msg"));
  return FALSE;
 }

 //开辟线程接收消息,然后发消息到主线程,在主线程中操作控件,不能直接在子线程中处理
 m_HwndObj.pThis = this;
 m_HwndObj.hWnd = GetSafeHwnd();   //获取窗口句柄
 AfxBeginThread(ThreadRecvMsg, (LPVOID)&m_HwndObj);  //传递窗口句柄+this指针.MFC创建线程方式

 //定位光标
 UpdateData(FALSE);
 m_EditMsgCtrl.SetFocus();

 return FALSE;  // return TRUE unless you set the focus to a control
 // EXCEPTION: OCX Property Pages should return FALSE
}

//关闭按钮内容
void CMutualDlg::OnBnClickedCancel()
{
 // TODO: Add your control notification handler code here
 m_pSock->m_bTerminate = true;
 Sleep(200);
 m_bExitThread = true;
 Sleep(500);
 OnCancel();
}

//处理消息的线程函数,接收到消息后处理ShowEdit. send发送到Server端
//从消息队列中获取消息,互斥使用
unsigned __stdcall CMutualDlg::ThreadSendMsg(LPVOID pParam)
{
 CMutualDlg *pThis = (CMutualDlg*)pParam;
 while (!pThis->m_bExitThread)
 {
  char* pTask = NULL;
  pThis->GetCriticalSection()->Lock();
  if (pThis->m_TaskQueue.empty())        //消息队列为空
  {
   pThis->GetCriticalSection()->Unlock();
   if (WAIT_TIMEOUT == WaitForSingleObject(pThis->m_Event.m_hObject, 10))
   {
    continue;
   }
  }
  else
  {
   pTask = pThis->m_TaskQueue.front();
   pThis->m_TaskQueue.pop();
   pThis->GetCriticalSection()->Unlock();
   //发送消息
   ChatMsg_S ChatMsgObj;
   ChatMsgObj.Head.nCommandId = htonl(ChatMsgReq);
   ChatMsgObj.Head.nSequenceNum = htonl(5);
   memset(ChatMsgObj.cArrySourceInfo, 0, IPPORTInfo);   //源IP和Port信息
   memcpy(ChatMsgObj.cArrySourceInfo, pThis->m_sSourceInfo.GetBuffer(pThis->m_sSourceInfo.GetLength

()), pThis->m_sSourceInfo.GetLength());
   memset(ChatMsgObj.cArryDestInfo, 0, IPPORTInfo);     //目的Ip和Port信息
   memcpy(ChatMsgObj.cArryDestInfo, pThis->m_DestIPPort.GetBuffer(pThis->m_DestIPPort.GetLength()),

pThis->m_DestIPPort.GetLength());
   memset(ChatMsgObj.cArryMsg, 0, CHATMSGLEN);
   memcpy(ChatMsgObj.cArryMsg, pTask, strlen(pTask)+1); //待发送消息
   ChatMsgObj.Head.nMsgTotalLen = htonl(sizeof(ChatMsg_S));
   pThis->m_pSock->SendMsg((char*)&ChatMsgObj,sizeof(ChatMsg_S));
  }
 }

 return 0;
}

//开辟线程接收消息,然后发消息到主线程中,通知主线程更新控件
//使用struct将窗口句柄和当前对象组合到一起
UINT __cdecl CMutualDlg::ThreadRecvMsg(LPVOID pParam)
{
 HwndObj_S *pTemp = (HwndObj_S*)pParam;
 HWND hWnd = pTemp->hWnd;                   //获取窗口句柄
 CMutualDlg *pThis = pTemp->pThis;          //获取当前对象
 //pThis->m_ShowMsgValue;
 LPARAM lParam;
 while (!pThis->m_bExitThread)
 {
  char* pMsg = pThis->m_pSock->RecvMsg(sizeof(CommonHead_S) + USRINFOLEN);
  ChatMsg_S *pChatMsg = (ChatMsg_S*)pMsg;
  pThis->m_DestIPPort = pChatMsg->cArrySourceInfo;
  lParam = (LPARAM)(pChatMsg->cArryMsg);
  ::SendMessage(hWnd, UM_MSG, NULL, lParam);

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

 return 0;
}


//发送消息,同时显示到ShowEdit控件中
void CMutualDlg::OnBnClickedOk()
{
 //OnOK();
 //Show窗口显示
 CString sMe = "Me:\r\n";
 CString sChangLineMe = "\r\nMe:\r\n";
 UpdateData(TRUE);

 //判断编辑框是否为空,空消息不允许发送
 if (m_EditMsgValue.IsEmpty())
 {
  AfxMessageBox(_T("Empty can not send"));
  return;
 }

 if (m_ShowMsgValue.IsEmpty())   //刚开始为空
 {
  m_ShowMsgValue = (sMe + m_EditMsgValue);
 }
 else
 {
  m_ShowMsgValue = (m_ShowMsgValue + sChangLineMe + m_EditMsgValue);
 }
 UpdateData(FALSE);

 //保存消息
 memset(m_pSendMsg, 0, USRINFOLEN);
 memcpy(m_pSendMsg, m_EditMsgValue.GetBuffer(m_EditMsgValue.GetLength()), m_EditMsgValue.GetLength());
 AddTask(m_pSendMsg);

 //LineScroll自动定位Edit中的垂直滚动条
 m_ShowMsgCtrl.LineScroll(m_ShowMsgCtrl.GetLineCount(), 0);
 m_EditMsgCtrl.SetWindowText("");
 m_EditMsgCtrl.SetFocus();
 return;
}


//接收子线程发送的消息,lParam作为传递参数。负责更新界面控件
LRESULT CMutualDlg::OnMsg(WPARAM wParam, LPARAM lParam)
{
 CString sChangeLineOther = "\r\nPeer:\r\n";
 CString sOther = "Peer:\r\n";
 CString sValue = (char*)lParam;
 if (m_ShowMsgValue.IsEmpty())
 {
  m_ShowMsgValue = (m_ShowMsgValue + sOther + sValue);
 }
 else
  m_ShowMsgValue = (m_ShowMsgValue + sChangeLineOther + sValue);
 
 m_EditMsgValue = "";     //防止编辑框出现字段
 UpdateData(FALSE);
 m_ShowMsgCtrl.LineScroll(m_ShowMsgCtrl.GetLineCount(), 0); //文本框的垂直滚动条自动滚动到最新位置,实时显示内容
 return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值