聊天程序,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;
}