sockt练习-文本聊天c/s实现
来源:互联网 发布:网络大专可以做人事吗 编辑:程序博客网 时间:2024/06/07 00:39
前言
这几天,大家都在做这个练习。
有的同学做的很快,1,2天就搞定(还不是整个工作日). 我很疑惑, 也很惊奇 ^_^
像这样的小项目,一般我需要4个完整工作日(思考,规划,编码, 测试, 修改BUG, 完善).
再短的时限,臣做不到啊^_^
服务端:win32控制台(SDK版)
客户端 : MFCDialog
功能: 群聊,私聊,用户在线列表,登入,登出,心跳.
统计了一下代码行数, 从侧面看看自己有多挫~
知识点
* 以前以为用异步操作比同步操作好,退出速度快。现在才知道,原来在另外一条线程中,将对象句柄关了,她就失败退出了. 这样,可以用同步方法在线程中做事,逻辑简单. 对于远程文件操作或socket操作,均可以用将句柄关了的方法,让阻塞操作退出. 停止或退出操作很快.
* 这次,也是只靠本地msdn从头写的, 已经习惯在vc6中没有代码提示和语法高亮的环境. 代码熟练度也提高了一些. 感谢好心人给了在Win7X64下可用的SDK补丁, 在Win7X64下的vc6中,也可以用新的API了.
* 在C/S传递参数的过程中,我使用了固定头结构+柔性数组. 用字符串按照json或xml传递维护性较好,但是那要用第三方的xml库或json库, 没用. 如果是在正式工程中, 可以采用字符串来传递全部参数. 如果要保密,还要考虑传输加密问题. 练手的东西,就练习一下socket操作就好了:P
* 线程操作类,我又重新封装了一下, 对_beginthreadex的操作更方便了.
* 将发送操作和接收操作分开在发送线程和接收线程, 可以防止对同一个Socket读写操作时,不同线程同时读或同时写引起的内容非预期问题. 也能简化业务逻辑的流程.
客户端socket操作流程
** 初始化Socket环境, call WSAStartup
** 建立Socket对象, 得到Socket句柄. call socket
** 连接服务器. call connect
** 在接收线程中读取服务器数据. call recv(可以封装成为recvEx, 考虑一次接收不完的情况)
** 在发送线程中向服务器发送数据. call send(可以封装成为sendEx, 考虑一次发送不完的情况)
** 如果客户端要向服务器发送东西, 将内容压入发送队列,发送线程查到发送队列中有东西,就拿走发送给服务器. 如果一种操作,要进行多次的发送和接收的交互才能完成, 将每次的发送子操作和接收子操作加上帧号.
** 关闭Socket句柄. call closesocket
** 反初始化Socket环境, call WSACleanup
服务器socket操作流程
** 初始化Socket环境, call WSAStartup
** 建立Socket对象, 得到Socket句柄. call socket
** 指定服务器的IP和端口号. call bind
** 指定服务器能处理的客户端数量. call listen
** 建立一条发送线程和一条心跳检测线程, 所有客户端的发送任务和心跳检测都由这两条线程完成.
** 等待客户端连接. call accept
** 如果来了客户端连接, 对每个客户端建立一个接收线程, 启动接收线程. 回到上一步,继续等待新的客户端来连接服务器
** 反初始化Socket环境, call WSACleanup
工程下载点
SocketIM.zip
代码预览
客户端
// ControlCmd.h: interface for the CControlCmd class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_CONTROLCMD_H__099A6E75_DFBB_4E95_992E_7774AEFCDBA9__INCLUDED_)#define AFX_CONTROLCMD_H__099A6E75_DFBB_4E95_992E_7774AEFCDBA9__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include "stdafx.h"#define SERVER_HEARTBEAT_CHECK_SPANTIME ((DWORD)10 * 1000) ///< 服务器10秒检查一次心跳#define CLIENT_HEARTBEAT_SPANTIME ((DWORD)5 * 1000) ///< 客户端5秒发一次心跳enum eControlCmd { eControlCmd_unknown = -1, eControlCmd_loginByClient, ///< 客户端发起登录 eControlCmd_loginOutByClient, ///< 客户端发起登出 eControlCmd_ImPublic, ///< 群聊 eControlCmd_ImPrivate, ///< 私聊 eControlCmd_OnLine, ///< 用户上线 eControlCmd_DownLine, ///< 用户下线 eControlCmd_UserList, ///< 用户列表 eControlCmd_HeartBeat, ///< 心跳包};#pragma pack(push)#pragma pack(1)typedef struct _tag_ControlCmdData_loginByClient { TCHAR szUserName[MAX_PATH]; ///< 用户名 TCHAR szPwd[MAX_PATH]; ///< 口令 OUT BOOL bLoginOK; ///< 是否登录成功 OUT DWORD dwUserID; ///< 服务器返回的当前用户ID OUT TCHAR szDesc[MAX_PATH]; ///< 服务器返回的结果描述 void Clear() { ::ZeroMemory(this, sizeof(_tag_ControlCmdData_loginByClient)); } BOOL IsLoginOk() { return bLoginOK; } void SetLoginOk(BOOL bIn) { bLoginOK = bIn; } DWORD GetUserID() { return dwUserID; } TCHAR* GetUserName() { return szUserName; } BOOL IsValidLoginData() { return ((_tcslen(szUserName) > 0) && (_tcslen(szPwd) > 0)); } void ShowDetail() { _tprintf(_T("szUserName = %s\n"), szUserName); _tprintf(_T("szPwd = %s\n"), szPwd); _tprintf(_T("bLoginOK = %d\n"), bLoginOK); _tprintf(_T("dwUserID = 0x%X\n"), dwUserID); _tprintf(_T("szDesc = %s\n"), szDesc); }}TAG_CONTROLCMDDATA_LOGINBYCLIENT;typedef struct _tag_ControlCmdData_ImPublic { DWORD dwUserID; ///< 服务器返回的当前用户ID TCHAR szUserName[MAX_PATH]; ///< 当前用户名称 TCHAR szMsg[MAX_PATH]; ///< 聊天内容}TAG_CONTROLCMDDATA_IMPUBLIC;typedef struct _tag_ControlCmdData_ImPrivate { DWORD dwOtherUserID; ///< 对方ID TCHAR szOtherUserName[MAX_PATH]; ///< 对方用户名称 TAG_CONTROLCMDDATA_IMPUBLIC MyData; ///< 我的ID和聊天内容}TAG_CONTROLCMDDATA_IMPRIVATE;typedef struct _tag_ControlCmdData_HeartBeat { DWORD dwUserID; ///< 服务器返回的当前用户ID}TAG_CONTROLCMDDATA_HEARTBEAT;typedef struct _tag_ControlCmdData_User_Online { DWORD dwUserID; ///< 服务器返回的当前用户ID TCHAR szUserName[MAX_PATH]; ///< 当前用户名称}TAG_CONTROLCMDDATA_USER_ONLINE;typedef struct _tag_ControlCmdData_User_Downline { DWORD dwUserID; ///< 服务器返回的当前用户ID TCHAR szUserName[MAX_PATH]; ///< 当前用户名称}TAG_CONTROLCMDDATA_USER_DOWNLINE;typedef struct _tag_ControlCmdData_UserInfoList { /// 需要更新在线用户列表的用户 DWORD dwUserID; ///< 服务器返回的当前用户ID TCHAR szUserName[MAX_PATH]; ///< 当前用户名称 /// 用户列表信息 DWORD dwUserOnlineCnt; ///< 用户数量 TAG_CONTROLCMDDATA_USER_ONLINE UserInfoAry[1]; ///< 用户信息数组首地址}TAG_CONTROLCMDDATA_USER_USERINFOLIST;typedef union _tag_CmdData{ TAG_CONTROLCMDDATA_LOGINBYCLIENT LoginInfo; ///< 登录信息 TAG_CONTROLCMDDATA_IMPUBLIC ImPublic; ///< 群聊 TAG_CONTROLCMDDATA_IMPRIVATE ImPrivate; ///< 私聊 TAG_CONTROLCMDDATA_USER_ONLINE UserOnline; ///< 用户上线 TAG_CONTROLCMDDATA_USER_DOWNLINE UserDownline; ///< 用户下线 TAG_CONTROLCMDDATA_USER_USERINFOLIST UserInfoList; ///< 用户信息列表 TAG_CONTROLCMDDATA_HEARTBEAT ImHeartBeat; ///< 心跳}TAG_CMDDATA;typedef struct _tag_ControlCmdData { eControlCmd Cmd; TAG_CMDDATA Data; _tag_ControlCmdData() { Clear(); } void Clear() { Cmd = eControlCmd_unknown; ZeroMemory(&Data, sizeof(Data)); }}TAG_CONTROLCMDDATA;#pragma pack(pop)#endif // !defined(AFX_CONTROLCMD_H__099A6E75_DFBB_4E95_992E_7774AEFCDBA9__INCLUDED_)
// ClientDlg.h : header file//#if !defined(AFX_CLIENTDLG_H__17DAE0C4_EB5C_43F7_B087_840AE0D9E580__INCLUDED_)#define AFX_CLIENTDLG_H__17DAE0C4_EB5C_43F7_B087_840AE0D9E580__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include "stdafx.h"#include <windows.h>#include <winsock2.h>#pragma comment(lib, "Ws2_32.lib")#include <list>using namespace std;#include "ControlCmd.h"#include "MyThread.h"/////////////////////////////////////////////////////////////////////////////// CClientDlg dialog#define WM_SHOW_THREAD_MSG (WM_USER + 1000)class CClientDlg : public CDialog{// Constructionpublic: CClientDlg(CWnd* pParent = NULL); // standard constructor virtual ~CClientDlg();public: /// member function void SocketDataInit(); void SocketDataUnInit(); void ShowSocketErrMsg(); void ShowErrMsg(); void ShowErrMsg(int iErrSn); void ShowMsg(TCHAR* pMsg); void ShowThreadMsg(TCHAR* pMsg); void AddUserToList(DWORD dwUserID, TCHAR* pcUserName); void RemoveUserFromList(DWORD dwUserID, TCHAR* pcUserName); void FillOtherUserIDFromList(); BOOL IsNeedLogin(); void ConnectOpen(); void ConnectClose(); void UserLogin(); void UserLoginOut(); static unsigned __stdcall ThreadProc_Send(void* pParam); static unsigned __stdcall ThreadProc_Recv(void* pParam); unsigned ThreadProc_Send(); unsigned ThreadProc_Recv(); unsigned ThreadProc_Recv(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_loginByClient(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_loginOutByClient(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_ImPublic(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_ImPrivate(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_HeartBeat(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_UserOnline(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_UserList(TAG_CONTROLCMDDATA* pCmdData); unsigned ThreadProc_Recv_UserDownline(TAG_CONTROLCMDDATA* pCmdData); void AddDataToList(list<TAG_CONTROLCMDDATA*>& List, TAG_CONTROLCMDDATA* p, CRITICAL_SECTION& cs); TAG_CONTROLCMDDATA* GetDataFromList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs); void RemoveList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs);private: /// member WSADATA m_WSAData; SOCKET m_sClient; BOOL m_bConnectOk; /// 服务器返回的登录信息 TAG_CONTROLCMDDATA_LOGINBYCLIENT m_LoginInfoFromServer; BOOL m_bNeedUpdateLoginInfo; CMyThread m_ThreadSend; list<TAG_CONTROLCMDDATA*> m_ListCmdDataToSend; CRITICAL_SECTION g_csListCmdDataToSend; list<TAG_CONTROLCMDDATA_USER_ONLINE> m_ListUsersOnlineUpdate; list<TAG_CONTROLCMDDATA_USER_DOWNLINE> m_ListUsersDownlineUpdate; list<CString> m_ListDispMsg; CRITICAL_SECTION g_csDispMsg; CMyThread m_ThreadRecv;public:// Dialog Data //{{AFX_DATA(CClientDlg) enum { IDD = IDD_CLIENT_DIALOG }; CListCtrl m_ListUsersOnline; CEdit m_EditImMsg; CString m_csImMsg; CString m_csImMsgToSend; BOOL m_bCheckImPrivate; CString m_strOtherUserID; CString m_strMyIDFromServer; CString m_strMyName; CString m_strMyPwd; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CClientDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL// Implementationprotected: HICON m_hIcon; // Generated message map functions //{{AFX_MSG(CClientDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnButtonSendContent(); afx_msg void OnButtonLogin(); afx_msg void OnButtonLoginout(); afx_msg void OnDestroy(); afx_msg void OnShowThreadMsg(); afx_msg void OnButtonClearContent(); afx_msg void OnItemchangedListUsersOnline(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnClickListUsersOnline(NMHDR* pNMHDR, LRESULT* pResult); //}}AFX_MSG DECLARE_MESSAGE_MAP()};//{{AFX_INSERT_LOCATION}}// Microsoft Visual C++ will insert additional declarations immediately before the previous line.#endif // !defined(AFX_CLIENTDLG_H__17DAE0C4_EB5C_43F7_B087_840AE0D9E580__INCLUDED_)
// ClientDlg.cpp : implementation file//#include "stdafx.h"#include <windows.h>#include <tchar.h>#include <assert.h>#include "Client.h"#include "ClientDlg.h"#include "SocketOpt.h"#define USE_SERVER_IP FALSE ///< TRUE, use SERVER_IP; FALSE, use SERVER_NAME#define SERVER_NAME _T("localhost")#define SERVER_IP 0x7f000001 ///< 127.0.0.1 0x7f000001#define SERVER_PORT 12345#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif/////////////////////////////////////////////////////////////////////////////// CAboutDlg dialog used for App Aboutclass CAboutDlg : public CDialog{public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg)protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementationprotected: //{{AFX_MSG(CAboutDlg) //}}AFX_MSG DECLARE_MESSAGE_MAP()};CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD){ //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT}void CAboutDlg::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP}BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)//{{AFX_MSG_MAP(CAboutDlg)// No message handlers//}}AFX_MSG_MAPEND_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CClientDlg dialogCClientDlg::CClientDlg(CWnd* pParent /*=NULL*/): CDialog(CClientDlg::IDD, pParent){ //{{AFX_DATA_INIT(CClientDlg) m_csImMsg = _T(""); m_csImMsgToSend = _T(""); m_bCheckImPrivate = FALSE; m_strOtherUserID = _T(""); m_strMyName = _T(""); m_strMyPwd = _T(""); //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); SocketDataInit();}CClientDlg::~CClientDlg() { SocketDataUnInit();}void CClientDlg::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CClientDlg) DDX_Control(pDX, IDC_LIST_USERS_ONLINE, m_ListUsersOnline); DDX_Control(pDX, IDC_EDIT_MSG, m_EditImMsg); DDX_Text(pDX, IDC_EDIT_MSG, m_csImMsg); DDX_Text(pDX, IDC_EDIT_MSG_TO_SEND, m_csImMsgToSend); DDX_Check(pDX, IDC_CHECK_IM_PRIVATE, m_bCheckImPrivate); DDX_Text(pDX, IDC_EDIT_USER2_ID, m_strOtherUserID); DDX_Text(pDX, IDC_EDIT_MY_NAME, m_strMyName); DDX_Text(pDX, IDC_EDIT_MY_PWD, m_strMyPwd); //}}AFX_DATA_MAP}BEGIN_MESSAGE_MAP(CClientDlg, CDialog)//{{AFX_MSG_MAP(CClientDlg)ON_WM_SYSCOMMAND()ON_WM_PAINT()ON_WM_QUERYDRAGICON()ON_BN_CLICKED(IDC_BUTTON_SEND_CONTENT, OnButtonSendContent) ON_BN_CLICKED(IDC_BUTTON_LOGIN, OnButtonLogin) ON_BN_CLICKED(IDC_BUTTON_LOGINOUT, OnButtonLoginout) ON_WM_DESTROY() ON_MESSAGE(WM_SHOW_THREAD_MSG, OnShowThreadMsg) ON_BN_CLICKED(IDC_BUTTON_CLEAR_CONTENT, OnButtonClearContent) ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_USERS_ONLINE, OnItemchangedListUsersOnline) ON_NOTIFY(NM_CLICK, IDC_LIST_USERS_ONLINE, OnClickListUsersOnline) //}}AFX_MSG_MAPEND_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CClientDlg message handlersBOOL CClientDlg::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 m_ThreadSend.SetParam( ThreadProc_Send, NULL, NULL, this); m_ThreadRecv.SetParam( ThreadProc_Recv, NULL, NULL, this); m_strMyIDFromServer = _T("未登录用户"); this->SetWindowText((LPTSTR)(LPCTSTR)m_strMyIDFromServer); /// 初始化用户列表 m_ListUsersOnline.InsertColumn(0, _T("ID"), LVCFMT_LEFT); m_ListUsersOnline.InsertColumn(1, _T("姓名"), LVCFMT_LEFT); m_ListUsersOnline.SetColumnWidth(0, 60); m_ListUsersOnline.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER); m_ListUsersOnline.SetExtendedStyle( m_ListUsersOnline.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); return TRUE; // return TRUE unless you set the focus to a control}void CClientDlg::AddUserToList(DWORD dwUserID, TCHAR* pcUserName) { CString str; int i = 0; int iRowCnt = m_ListUsersOnline.GetItemCount(); LVITEM lvitem; BOOL bFind = FALSE; if (!m_LoginInfoFromServer.IsLoginOk() || (m_LoginInfoFromServer.GetUserID() == dwUserID)) { return; } for (i = 0; i < iRowCnt; i++) { if (dwUserID == m_ListUsersOnline.GetItemData(i)) { bFind = TRUE; break; } } if (!bFind) { str.Format(_T("%d"), dwUserID); lvitem.mask = LVIF_TEXT | LVIF_IMAGE; lvitem.iItem = iRowCnt; lvitem.iSubItem = 0; lvitem.pszText = (LPTSTR)(LPCTSTR)str; m_ListUsersOnline.InsertItem(&lvitem); m_ListUsersOnline.SetItemText(iRowCnt, 1, pcUserName); m_ListUsersOnline.SetItemData(iRowCnt, dwUserID); }}void CClientDlg::RemoveUserFromList(DWORD dwUserID, TCHAR* pcUserName) { int i = 0; int iRowCnt = m_ListUsersOnline.GetItemCount(); if (m_LoginInfoFromServer.GetUserID() == dwUserID) { return; } for (i = 0; i < iRowCnt; i++) { if (dwUserID == m_ListUsersOnline.GetItemData(i)) { m_ListUsersOnline.DeleteItem(i); break; } }}void CClientDlg::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 CClientDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (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 to obtain the cursor to display while the user drags// the minimized window.HCURSOR CClientDlg::OnQueryDragIcon(){ return (HCURSOR) m_hIcon;}void CClientDlg::SocketDataInit() { InitializeCriticalSection(&g_csListCmdDataToSend); InitializeCriticalSection(&g_csDispMsg); m_strMyIDFromServer.Empty(); m_bConnectOk = FALSE; m_LoginInfoFromServer.Clear(); m_bNeedUpdateLoginInfo = FALSE; m_sClient = INVALID_SOCKET; WSAStartup(MAKEWORD(2,2), &m_WSAData);}void CClientDlg::SocketDataUnInit() { WSACleanup(); DeleteCriticalSection(&g_csListCmdDataToSend); DeleteCriticalSection(&g_csDispMsg);}void CClientDlg::ShowSocketErrMsg() { int iErrSn = WSAGetLastError(); ShowErrMsg(iErrSn); ConnectClose(); ///< 防止断线后, 再登录时,登录不上}void CClientDlg::ShowErrMsg() { int iErrSn = GetLastError(); ShowErrMsg(iErrSn);}void CClientDlg::ShowErrMsg(int iErrSn) { LPVOID lpMsgBuf = NULL; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, iErrSn, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL); if (NULL != lpMsgBuf) { ShowThreadMsg((LPTSTR)lpMsgBuf); } LocalFree(lpMsgBuf);}void CClientDlg::ShowMsg(TCHAR* pMsg) { DWORD dwSel = 0; if (NULL != pMsg) { UpdateData(TRUE); m_csImMsg += pMsg; m_csImMsg += _T("\r\n"); UpdateData(FALSE); m_EditImMsg.LineScroll(65535); }}void CClientDlg::OnButtonSendContent() { int iRc = 0; TAG_CONTROLCMDDATA* pCmdData = NULL; DWORD dwTotalLen = 0; int iMsgLen = 0; do { if (IsNeedLogin()) { ShowThreadMsg(_T("请先登录")); break; } UpdateData(TRUE); if (m_csImMsgToSend.IsEmpty()) { ShowThreadMsg(_T("发送内容为空, 请输入聊天内容")); break; } if (m_bCheckImPrivate) { if (m_strOtherUserID.IsEmpty()) { ShowThreadMsg(_T("私聊需要提供对方用户ID")); break; } } pCmdData = new TAG_CONTROLCMDDATA; assert(NULL != pCmdData); pCmdData->Cmd = m_bCheckImPrivate ? eControlCmd_ImPrivate : eControlCmd_ImPublic; if (m_bCheckImPrivate) { pCmdData->Data.ImPrivate.dwOtherUserID = _ttol((LPTSTR)(LPCTSTR)m_strOtherUserID); pCmdData->Data.ImPrivate.MyData.dwUserID = m_LoginInfoFromServer.GetUserID(); _tcscpy(pCmdData->Data.ImPrivate.MyData.szUserName, m_LoginInfoFromServer.GetUserName()); _tcscpy(pCmdData->Data.ImPrivate.MyData.szMsg, (LPTSTR)(LPCTSTR)m_csImMsgToSend); } else { pCmdData->Data.ImPublic.dwUserID = m_LoginInfoFromServer.GetUserID(); _tcscpy(pCmdData->Data.ImPublic.szUserName, m_LoginInfoFromServer.GetUserName()); _tcscpy(pCmdData->Data.ImPublic.szMsg, (LPTSTR)(LPCTSTR)m_csImMsgToSend); } AddDataToList(m_ListCmdDataToSend, pCmdData, g_csListCmdDataToSend); m_csImMsgToSend.Empty(); UpdateData(FALSE); } while (0);}void CClientDlg::OnButtonLogin() { /// 连接服务器 if (!m_bConnectOk) { ConnectOpen(); } /// 登录 if (m_bConnectOk && !m_LoginInfoFromServer.IsLoginOk()) { UserLogin(); }}void CClientDlg::OnButtonLoginout() { /// 登出 if (m_LoginInfoFromServer.IsLoginOk()) { UserLoginOut(); /// 必须设置标志, 防止服务器掉线后, 登出不了 m_LoginInfoFromServer.SetLoginOk(FALSE); m_ListUsersOnline.DeleteAllItems(); } else { ShowThreadMsg(_T("已经是登出状态")); }}void CClientDlg::UserLogin() { TAG_CONTROLCMDDATA* pCmdData = NULL; int iRc = 0; do { if (m_LoginInfoFromServer.IsLoginOk()) { break; } UpdateData(TRUE); if (m_strMyName.IsEmpty() || m_strMyPwd.IsEmpty()) { ShowThreadMsg(_T("请输入用户名和口令!")); break; } pCmdData = new TAG_CONTROLCMDDATA; assert(NULL != pCmdData); pCmdData->Cmd = eControlCmd_loginByClient; _tcscpy(pCmdData->Data.LoginInfo.szUserName, (LPTSTR)(LPCTSTR)m_strMyName); _tcscpy(pCmdData->Data.LoginInfo.szPwd, (LPTSTR)(LPCTSTR)m_strMyPwd); AddDataToList(m_ListCmdDataToSend, pCmdData, g_csListCmdDataToSend); } while (0);}void CClientDlg::UserLoginOut() { TAG_CONTROLCMDDATA* pCmdData = NULL; int iRc = 0; do { if (!m_LoginInfoFromServer.IsLoginOk()) { break; } pCmdData = new TAG_CONTROLCMDDATA; assert(NULL != pCmdData); pCmdData->Cmd = eControlCmd_loginOutByClient; pCmdData->Data.LoginInfo = m_LoginInfoFromServer; AddDataToList(m_ListCmdDataToSend, pCmdData, g_csListCmdDataToSend); } while (0);}void CClientDlg::ConnectOpen() { sockaddr_in addrServer; struct hostent* pHost = NULL; int iRc = 0; CString str; do { if (INVALID_SOCKET == m_sClient) { m_sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == m_sClient) { ShowSocketErrMsg(); break; } } addrServer.sin_family = AF_INET; addrServer.sin_port = htons(SERVER_PORT); if (USE_SERVER_IP) { addrServer.sin_addr.S_un.S_addr = htonl(SERVER_IP); } else { pHost = gethostbyname(SERVER_NAME); if (NULL == pHost) { ShowSocketErrMsg(); break; } CopyMemory(&addrServer.sin_addr, pHost->h_addr_list[0], pHost->h_length); } if (!m_bConnectOk) { iRc = connect(m_sClient, (const struct sockaddr *)&addrServer, sizeof(addrServer)); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); break; } str.Format(_T("m_sClient[%d] was Connect to server"), m_sClient); ShowThreadMsg((LPTSTR)(LPCTSTR)str); m_bConnectOk = TRUE; m_ThreadSend.Start(); m_ThreadRecv.Start(); } } while (0);}void CClientDlg::ConnectClose() { if (INVALID_SOCKET != m_sClient) { closesocket(m_sClient); m_sClient = INVALID_SOCKET; m_bConnectOk = FALSE; RemoveList(m_ListCmdDataToSend, g_csListCmdDataToSend); }}BOOL CClientDlg::IsNeedLogin() { return (!m_bConnectOk || !m_LoginInfoFromServer.IsLoginOk());}unsigned __stdcall CClientDlg::ThreadProc_Send(void* pParam) { if (NULL == pParam) { return S_FALSE; } return ((CClientDlg*)pParam)->ThreadProc_Send();}unsigned __stdcall CClientDlg::ThreadProc_Recv(void* pParam) { if (NULL == pParam) { return S_FALSE; } return ((CClientDlg*)pParam)->ThreadProc_Recv();}unsigned CClientDlg::ThreadProc_Send() { TAG_CONTROLCMDDATA* p = NULL; int iRc = FALSE; CString str; DWORD dwHeartBeatSpanTime = CLIENT_HEARTBEAT_SPANTIME; DWORD dwHeartBeatTimeBegin = GetTickCount(); do { Sleep(1); if (!m_ThreadSend.cbIsCanContinue()) { continue; } if (m_ThreadSend.cbIsNeedQuit()) { break; } if (m_LoginInfoFromServer.IsLoginOk()) { if ((GetTickCount() - dwHeartBeatTimeBegin) >= dwHeartBeatSpanTime) { dwHeartBeatTimeBegin = GetTickCount(); /// 心跳发送间隔到, 制造一个心跳包,发往服务器 p = new TAG_CONTROLCMDDATA; assert(NULL != p); p->Cmd = eControlCmd_HeartBeat; p->Data.ImHeartBeat.dwUserID = m_LoginInfoFromServer.GetUserID(); // str.Format(_T("[%d] Send HeartBeat"), p->Data.ImHeartBeat.dwUserID); // ShowThreadMsg((LPTSTR)(LPCTSTR)str); AddDataToList(m_ListCmdDataToSend, p, g_csListCmdDataToSend); } } if ((INVALID_SOCKET != m_sClient) && m_bConnectOk) { p = GetDataFromList(m_ListCmdDataToSend, g_csListCmdDataToSend); if (NULL != p) { iRc = sendEx(m_sClient, (const char*)p, sizeof(TAG_CONTROLCMDDATA), 0); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); } delete p; p = NULL; } } } while (1); return S_OK;}unsigned CClientDlg::ThreadProc_Recv() { TAG_CONTROLCMDDATA CmdData; int iRc = FALSE; int iErrSnPrev = WSAGetLastError(); do { Sleep(1); if (!m_ThreadRecv.cbIsCanContinue()) { continue; } if (m_ThreadRecv.cbIsNeedQuit()) { break; } if ((INVALID_SOCKET != m_sClient) && m_bConnectOk) { ::ZeroMemory(&CmdData, sizeof(TAG_CONTROLCMDDATA)); iRc = recvEx(m_sClient, (char*)&CmdData, sizeof(TAG_CONTROLCMDDATA), 0); if (SOCKET_ERROR == iRc) { if (iErrSnPrev != WSAGetLastError()) { iErrSnPrev = WSAGetLastError(); ShowSocketErrMsg(); } } else if (iRc > 0){ iErrSnPrev = WSAGetLastError(); ThreadProc_Recv(&CmdData); } } } while (1); return S_OK;}unsigned CClientDlg::ThreadProc_Recv(TAG_CONTROLCMDDATA* pCmdData) { if (NULL != pCmdData) { switch (pCmdData->Cmd) { case eControlCmd_loginByClient: ThreadProc_Recv_loginByClient(pCmdData); break; case eControlCmd_loginOutByClient: ThreadProc_Recv_loginOutByClient(pCmdData); break; case eControlCmd_ImPublic: ThreadProc_Recv_ImPublic(pCmdData); break; case eControlCmd_ImPrivate: ThreadProc_Recv_ImPrivate(pCmdData); break; case eControlCmd_OnLine: ThreadProc_Recv_UserOnline(pCmdData); break; case eControlCmd_UserList: ThreadProc_Recv_UserList(pCmdData); break; case eControlCmd_DownLine: ThreadProc_Recv_UserDownline(pCmdData); break; case eControlCmd_HeartBeat: ThreadProc_Recv_HeartBeat(pCmdData); break; default: break; } } return S_OK;}void CClientDlg::OnShowThreadMsg() { CString str; TAG_CONTROLCMDDATA_USER_ONLINE UserOnlineInfo; TAG_CONTROLCMDDATA_USER_DOWNLINE UserDownlineInfo; EnterCriticalSection(&g_csDispMsg); while (!m_ListDispMsg.empty()) { str = m_ListDispMsg.front(); m_ListDispMsg.pop_front(); ShowMsg((LPTSTR)(LPCTSTR)str); } while (!m_ListUsersOnlineUpdate.empty()) { UserOnlineInfo = m_ListUsersOnlineUpdate.front(); m_ListUsersOnlineUpdate.pop_front(); AddUserToList( UserOnlineInfo.dwUserID, UserOnlineInfo.szUserName); } while (!m_ListUsersDownlineUpdate.empty()) { UserDownlineInfo = m_ListUsersDownlineUpdate.front(); m_ListUsersDownlineUpdate.pop_front(); RemoveUserFromList( UserDownlineInfo.dwUserID, UserDownlineInfo.szUserName); } LeaveCriticalSection(&g_csDispMsg); /// 需要更新UI的操作也通过这个自定义消息 if (m_bNeedUpdateLoginInfo) { m_bNeedUpdateLoginInfo = FALSE; if (m_LoginInfoFromServer.IsLoginOk()) { m_strMyIDFromServer.Format(_T("当前用户 : ID[%d] 姓名[%s]"), m_LoginInfoFromServer.GetUserID(), m_LoginInfoFromServer.GetUserName()); } else { m_strMyIDFromServer = _T("未登录用户"); } this->SetWindowText((LPTSTR)(LPCTSTR)m_strMyIDFromServer); }}void CClientDlg::ShowThreadMsg(TCHAR* pMsg) { if (NULL != pMsg) { EnterCriticalSection(&g_csDispMsg); m_ListDispMsg.push_back(pMsg); LeaveCriticalSection(&g_csDispMsg); PostMessage(WM_SHOW_THREAD_MSG, 0, 0); }}unsigned CClientDlg::ThreadProc_Recv_loginByClient(TAG_CONTROLCMDDATA* pCmdData) { CString str; if (NULL != pCmdData) { m_LoginInfoFromServer = pCmdData->Data.LoginInfo; str.Format(_T("登录结果: [%d]-[%s] 登录%s, 备注:%s"), m_LoginInfoFromServer.GetUserID(), m_LoginInfoFromServer.GetUserName(), m_LoginInfoFromServer.IsLoginOk() ? _T("成功") : _T("失败"), m_LoginInfoFromServer.szDesc); ShowThreadMsg((LPTSTR)(LPCTSTR)str); m_bNeedUpdateLoginInfo = TRUE; } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_loginOutByClient(TAG_CONTROLCMDDATA* pCmdData) { CString str; if (NULL != pCmdData) { m_LoginInfoFromServer = pCmdData->Data.LoginInfo; str.Format(_T("登出结果: [%d]-[%s] 登出%s, 备注:%s"), m_LoginInfoFromServer.GetUserID(), m_LoginInfoFromServer.GetUserName(), !m_LoginInfoFromServer.IsLoginOk() ? _T("成功") : _T("失败"), m_LoginInfoFromServer.szDesc); ShowThreadMsg((LPTSTR)(LPCTSTR)str); m_bNeedUpdateLoginInfo = TRUE; ConnectClose(); } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_UserOnline(TAG_CONTROLCMDDATA* pCmdData) { if (NULL != pCmdData) { EnterCriticalSection(&g_csDispMsg); m_ListUsersOnlineUpdate.push_back(pCmdData->Data.UserOnline); LeaveCriticalSection(&g_csDispMsg); PostMessage(WM_SHOW_THREAD_MSG, 0, 0); } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_UserList(TAG_CONTROLCMDDATA* pCmdData) { CString str; BOOL bNeedReadEx = FALSE; ///< 需要读扩展数据, TAG_CONTROLCMDDATA 装不下 DWORD cbNeedReadEx = 0; ///< 需要读多少扩展数据 BOOL bReadExOk = TRUE; ///< 读扩展数据是否成功 DWORD dwSizeToSend = sizeof(TAG_CONTROLCMDDATA); DWORD dwSizeUserList = 0; int iUserOnlineCnt = 0; int iRc = 0; TAG_CONTROLCMDDATA* pDst = NULL; int i = 0; TAG_CONTROLCMDDATA_USER_ONLINE* pUserInfoAry = NULL; if (NULL == pCmdData) { return FALSE; } iUserOnlineCnt = pCmdData->Data.UserInfoList.dwUserOnlineCnt;// str.Format(_T("ThreadProc_Recv_UserList iUserOnlineCnt = %d"), iUserOnlineCnt);// ShowThreadMsg((LPTSTR)(LPCSTR)str); if (iUserOnlineCnt > 1) { dwSizeUserList = sizeof(eControlCmd) + sizeof(TAG_CONTROLCMDDATA_USER_USERINFOLIST) + sizeof(TAG_CONTROLCMDDATA_USER_ONLINE) * (iUserOnlineCnt - 1); if (dwSizeToSend < dwSizeUserList) { dwSizeToSend = dwSizeUserList; bNeedReadEx = TRUE; } } pDst = (TAG_CONTROLCMDDATA*)(new BYTE[dwSizeToSend]); assert(NULL != pDst); memcpy(pDst, pCmdData, sizeof(TAG_CONTROLCMDDATA)); if (bNeedReadEx) { cbNeedReadEx = dwSizeToSend - sizeof(TAG_CONTROLCMDDATA); iRc = recvEx(m_sClient, (char*)pDst + sizeof(TAG_CONTROLCMDDATA), cbNeedReadEx, 0); if ((SOCKET_ERROR == iRc) || (0 == iRc)) { ShowSocketErrMsg(); bReadExOk = FALSE; } } if (bReadExOk) { EnterCriticalSection(&g_csDispMsg); for (i = 0; i < iUserOnlineCnt; i++) { m_ListUsersOnlineUpdate.push_back(pDst->Data.UserInfoList.UserInfoAry[i]); } LeaveCriticalSection(&g_csDispMsg); PostMessage(WM_SHOW_THREAD_MSG, 0, 0); } delete pDst; pDst = NULL; return S_OK;}unsigned CClientDlg::ThreadProc_Recv_UserDownline(TAG_CONTROLCMDDATA* pCmdData) { CString str; if (NULL != pCmdData) {// str.Format(_T("ThreadProc_Recv_UserDownline pCmdData->Data.UserDownline.szUserName = %s"), // pCmdData->Data.UserDownline.szUserName);// ShowThreadMsg((LPTSTR)(LPCSTR)str); EnterCriticalSection(&g_csDispMsg); m_ListUsersDownlineUpdate.push_back(pCmdData->Data.UserDownline); LeaveCriticalSection(&g_csDispMsg); PostMessage(WM_SHOW_THREAD_MSG, 0, 0); } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_ImPublic(TAG_CONTROLCMDDATA* pCmdData) { CString str; if (NULL != pCmdData) { str.Format(_T("[%d][%s] : %s"), pCmdData->Data.ImPublic.dwUserID, pCmdData->Data.ImPublic.szUserName, pCmdData->Data.ImPublic.szMsg); ShowThreadMsg((LPTSTR)(LPCTSTR)str); } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_ImPrivate(TAG_CONTROLCMDDATA* pCmdData) { CString str; if (NULL != pCmdData) { str.Format(_T("[%d][%s]=>[%d][%s] : %s"), pCmdData->Data.ImPrivate.MyData.dwUserID, pCmdData->Data.ImPrivate.MyData.szUserName, pCmdData->Data.ImPrivate.dwOtherUserID, pCmdData->Data.ImPrivate.szOtherUserName, pCmdData->Data.ImPrivate.MyData.szMsg); ShowThreadMsg((LPTSTR)(LPCTSTR)str); } return S_OK;}unsigned CClientDlg::ThreadProc_Recv_HeartBeat(TAG_CONTROLCMDDATA* pCmdData) { if (NULL != pCmdData) { } return S_OK;}void CClientDlg::AddDataToList(list<TAG_CONTROLCMDDATA*>& List, TAG_CONTROLCMDDATA* p, CRITICAL_SECTION& cs) { if (NULL != p) { EnterCriticalSection(&cs); List.push_back(p); LeaveCriticalSection(&cs); }}TAG_CONTROLCMDDATA* CClientDlg::GetDataFromList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs) { TAG_CONTROLCMDDATA* p = NULL; EnterCriticalSection(&cs); if (!List.empty()) { p = List.front(); List.pop_front(); } LeaveCriticalSection(&cs); return p;}void CClientDlg::RemoveList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs) { TAG_CONTROLCMDDATA* p = NULL; list<TAG_CONTROLCMDDATA*>::iterator it; EnterCriticalSection(&cs); while (!List.empty()) { p = List.front(); List.pop_front(); if (NULL != p) { delete p; } } LeaveCriticalSection(&cs);}void CClientDlg::OnDestroy() { CDialog::OnDestroy(); ConnectClose(); m_ThreadSend.Stop(); m_ThreadRecv.Stop(); RemoveList(m_ListCmdDataToSend, g_csListCmdDataToSend);}void CClientDlg::OnButtonClearContent() { UpdateData(TRUE); m_csImMsg.Empty(); UpdateData(FALSE);}void CClientDlg::OnItemchangedListUsersOnline(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; FillOtherUserIDFromList(); *pResult = 0;}void CClientDlg::OnClickListUsersOnline(NMHDR* pNMHDR, LRESULT* pResult) { FillOtherUserIDFromList(); *pResult = 0;}void CClientDlg::FillOtherUserIDFromList() { int nItem = 0; DWORD dwUserID = 0; POSITION pos = m_ListUsersOnline.GetFirstSelectedItemPosition(); if (NULL != pos) { nItem = m_ListUsersOnline.GetNextSelectedItem(pos); dwUserID = m_ListUsersOnline.GetItemData(nItem); UpdateData(TRUE); m_strOtherUserID.Format(_T("%d"), dwUserID); UpdateData(FALSE); }}
服务端
// SocketIM.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <windows.h>#include <Winsock2.h>#pragma comment(lib, "Ws2_32.lib")#include <tchar.h>#include <list>using namespace std;#include <assert.h>#include <time.h>#include "MyThread.h"#include "SocketRecvData.h"#include "ControlCmd.h"#include "SocketOpt.h"#define USE_SERVER_IP FALSE ///< TRUE, use SERVER_IP; FALSE, use SERVER_NAME#define SERVER_NAME _T("localhost")#define SERVER_IP 0x7f000001 ///< 127.0.0.1 0x7f000001#define SERVER_PORT 12345#define MAX_CLIENT_CNT 999CMyThread m_ThreadSend; ///< 发送线程list<TAG_CONTROLCMDDATA*> g_ListThreadSend; ///< 发送线程的DataListCRITICAL_SECTION g_csListThreadSend;/// 心跳检查不要放在发送线程中兼职, 会引起死锁/// 心跳检查必须是一条单独的线程CMyThread m_ThreadHeartBeat; ///< 心跳检查线程list<CMyThread*> g_ListThreadRecv; ///< 用于接收Client数据的线程列表CRITICAL_SECTION g_csListThreadRecv;void AddDataToList(list<TAG_CONTROLCMDDATA*>& List, TAG_CONTROLCMDDATA* p, CRITICAL_SECTION& cs);TAG_CONTROLCMDDATA* GetDataFromList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs);void RemoveList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs);void AddThreadToListThreadRecv(CMyThread* p);void StopAndRemoveAllFromListThreadRecv();BOOL RemoveTrashFromListThreadRecv();VOID EnterCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, TCHAR* pTip);VOID LeaveCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, TCHAR* pTip);void SocketServerProc();void SocketConnectProc(SOCKET sServer); ///< 处理客户端连接void ShowSocketErrMsg();void ShowErrMsg();void ShowErrMsg(int iErrSn);void ShowMsg(TCHAR* pMsg);BOOL FindExistUser(TAG_CONTROLCMDDATA_LOGINBYCLIENT* pLoginInfo);BOOL PrintfInfo_ListThreadRecv();BOOL ProcessUserLogin(CSocketRecvData* pData, TAG_CONTROLCMDDATA_LOGINBYCLIENT& LoginInfoByClient);BOOL ProcessUserLoginOut(CSocketRecvData* pData, TAG_CONTROLCMDDATA_LOGINBYCLIENT& LoginInfo);BOOL ProcessIm(CSocketRecvData* pData, TAG_CONTROLCMDDATA& CmdData);BOOL ProcessIm_PublicTalk(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData);BOOL ProcessIm_PrivateTalk(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData);BOOL ProcessIm_HeartBeat(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData);BOOL Notify_UserOnLine(TAG_CONTROLCMDDATA* p);BOOL Notify_UpdateListUserOnLine(TAG_CONTROLCMDDATA* p);BOOL Notify_UserDownLine(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_ImPublic(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_loginByClient(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_loginOutByClient(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_ImPrivate(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_UserOnline(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_UpdateUserList(TAG_CONTROLCMDDATA* p);unsigned fnThreadProc_SocketSend_UserDownline(TAG_CONTROLCMDDATA* p);unsigned __stdcall fnThreadProc_SocketSend(void* pParam);unsigned __stdcall fnThreadProc_HeartBeatCheck(void* pParam);unsigned __stdcall fnThreadProc_SocketRecv(void* pParam);unsigned __stdcall fnStopThread_SocketRecv(void* pParam);unsigned __stdcall fnDetoryUserDataProc_SocketRecv(void* pParam);int main(int argc, char* argv[]){ WSADATA WSAData; WSAStartup(MAKEWORD(2,2), &WSAData); InitializeCriticalSection(&g_csListThreadSend); InitializeCriticalSection(&g_csListThreadRecv); ShowMsg(_T("------------------------------------------------------------")); ShowMsg(_T("| IM Server |")); ShowMsg(_T("------------------------------------------------------------")); SocketServerProc(); m_ThreadHeartBeat.Stop(); StopAndRemoveAllFromListThreadRecv(); m_ThreadSend.Stop(); RemoveList(g_ListThreadSend, g_csListThreadSend); ShowMsg(_T("IM Server END\n")); DeleteCriticalSection(&g_csListThreadRecv); DeleteCriticalSection(&g_csListThreadSend); WSACleanup(); return 0;}void SocketServerProc() { SOCKET sServer = INVALID_SOCKET; struct sockaddr_in addrServer; struct hostent* pHost = NULL; int iRc = 0; TCHAR cBufToDisp[MAXBYTE] = {_T('\0')}; do { sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == sServer) { ShowSocketErrMsg(); break; } addrServer.sin_family = AF_INET; addrServer.sin_port = htons(SERVER_PORT); if (USE_SERVER_IP) { addrServer.sin_addr.S_un.S_addr = htonl(SERVER_IP); } else { pHost = gethostbyname(SERVER_NAME); if (NULL == pHost) { ShowSocketErrMsg(); break; } CopyMemory(&addrServer.sin_addr, pHost->h_addr_list[0], pHost->h_length); } iRc = bind(sServer, (SOCKADDR*)&addrServer, sizeof(addrServer)); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); break; } iRc = listen(sServer, MAX_CLIENT_CNT); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); break; } m_ThreadSend.SetParam( fnThreadProc_SocketSend, NULL, NULL, NULL); m_ThreadSend.Start(); m_ThreadHeartBeat.SetParam( fnThreadProc_HeartBeatCheck, NULL, NULL, NULL); m_ThreadHeartBeat.Start(); _stprintf(cBufToDisp, _T("Server : %s - %d.%d.%d.%d:%d"), SERVER_NAME, (SERVER_IP >> 24) & 0xff, (SERVER_IP >> 16) & 0xff, (SERVER_IP >> 8) & 0xff, (SERVER_IP >> 0) & 0xff, SERVER_PORT); ShowMsg(cBufToDisp); do { SocketConnectProc(sServer); } while (1); } while (0);}void SocketConnectProc(SOCKET sServer) { sockaddr_in addr; SOCKET sClient = SOCKET_ERROR; int nSize = sizeof(addr); CMyThread* pThread = NULL; TCHAR cBufToDisp[MAXBYTE] = {_T('\0')}; do { ShowMsg(_T("Waiting Client Connect...")); sClient = accept(sServer, (SOCKADDR*)&addr, &nSize); if (SOCKET_ERROR == sClient) { ShowSocketErrMsg(); break; } _stprintf(cBufToDisp, _T("Client[%d] Was Connect"), sClient); ShowMsg(cBufToDisp); /// 开始新线程 pThread = new CMyThread; pThread->SetParam( fnThreadProc_SocketRecv, fnStopThread_SocketRecv, fnDetoryUserDataProc_SocketRecv, new CSocketRecvData()); ((CSocketRecvData*)pThread->GetUserData())->SetSock(sClient); ((CSocketRecvData*)pThread->GetUserData())->SetThreadClass(pThread); AddThreadToListThreadRecv(pThread); pThread->Start(); } while (0);}BOOL ProcessUserLogin(CSocketRecvData* pData, TAG_CONTROLCMDDATA_LOGINBYCLIENT& LoginInfoByClient) { int iRc = 0; TAG_CONTROLCMDDATA* pCmdData = NULL; TCHAR cBufToDisp[4096] = {_T('\0')}; CMyThread* pThread = NULL; assert(NULL != pData); /// 认证 LoginInfoByClient.dwUserID = pData->GetSock(); if (LoginInfoByClient.IsValidLoginData()) { /// 查找是否该用户已经登录? if (FindExistUser(&LoginInfoByClient)) { LoginInfoByClient.bLoginOK = FALSE; _tcscpy(LoginInfoByClient.szDesc, _T("该用户已经登录, 请使用新的身份重新登录")); } else { LoginInfoByClient.bLoginOK = TRUE; _tcscpy(LoginInfoByClient.szDesc, _T("登录成功")); } } pData->GetLoginInfo() = LoginInfoByClient; _stprintf(cBufToDisp, _T("sClient[%d] : %s, UserName = %s, Pwd = %s"), LoginInfoByClient.GetUserID(), LoginInfoByClient.szDesc, LoginInfoByClient.szUserName, LoginInfoByClient.szPwd); ShowMsg(cBufToDisp); pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { pCmdData->Cmd = eControlCmd_loginByClient; pCmdData->Data.LoginInfo = LoginInfoByClient; /// 将认证结果发给客户端(先发!, 否则客户端用户在线状态加不上) /// 只有用户先登录了, 才会有好友列表 AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); if (LoginInfoByClient.IsLoginOk()) { /// 通知其它用户, 此用户上线 Notify_UserOnLine(pCmdData); /// 该用户更新用户列表 Notify_UpdateListUserOnLine(pCmdData); } } return LoginInfoByClient.bLoginOK;}BOOL Notify_UserDownLine(TAG_CONTROLCMDDATA* p) { TAG_CONTROLCMDDATA* pCmdData = NULL; if ((NULL == p) || (eControlCmd_loginOutByClient != p->Cmd)) { return FALSE; } pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { pCmdData->Cmd = eControlCmd_DownLine; pCmdData->Data.UserDownline.dwUserID = p->Data.LoginInfo.GetUserID(); _tcscpy(pCmdData->Data.UserDownline.szUserName, p->Data.LoginInfo.GetUserName()); AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); } return TRUE;}BOOL Notify_UserOnLine(TAG_CONTROLCMDDATA* p) { TAG_CONTROLCMDDATA* pCmdData = NULL; if ((NULL == p) || (eControlCmd_loginByClient != p->Cmd)) { return FALSE; } pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { pCmdData->Cmd = eControlCmd_OnLine; pCmdData->Data.UserOnline.dwUserID = p->Data.LoginInfo.GetUserID(); _tcscpy(pCmdData->Data.UserOnline.szUserName, p->Data.LoginInfo.GetUserName()); AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); } return TRUE;}BOOL Notify_UpdateListUserOnLine(TAG_CONTROLCMDDATA* p) { TAG_CONTROLCMDDATA* pCmdData = NULL; if ((NULL == p) || (eControlCmd_loginByClient != p->Cmd)) { return FALSE; } pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { pCmdData->Cmd = eControlCmd_UserList; pCmdData->Data.UserInfoList.dwUserID = p->Data.LoginInfo.GetUserID(); _tcscpy(pCmdData->Data.UserInfoList.szUserName, p->Data.LoginInfo.GetUserName()); AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); } return TRUE;}BOOL ProcessUserLoginOut(CSocketRecvData* pData, TAG_CONTROLCMDDATA_LOGINBYCLIENT& LoginInfo) { TCHAR cBufToDisp[4096] = {_T('\0')}; TAG_CONTROLCMDDATA CmdData; TAG_CONTROLCMDDATA* pCmdData = NULL; assert(NULL != pData); LoginInfo.bLoginOK = FALSE; _tcscpy(LoginInfo.szDesc, _T("用户要求退出登录")); CmdData.Data.LoginInfo = LoginInfo; CmdData.Cmd = eControlCmd_loginOutByClient; _stprintf(cBufToDisp, _T("sClient[%d] : %s, UserName = %s, Pwd = %s"), pData->GetSock(), LoginInfo.szDesc, LoginInfo.szUserName, LoginInfo.szPwd); ShowMsg(cBufToDisp); /// 将认证结果发给客户端 pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { memcpy(pCmdData, &CmdData, sizeof(TAG_CONTROLCMDDATA)); AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); } /// 遍历用户列表, 通知此用户下线 Notify_UserDownLine(&CmdData); return TRUE;}BOOL FindExistUser(TAG_CONTROLCMDDATA_LOGINBYCLIENT* pLoginInfo) { BOOL bRc = FALSE; list<CMyThread*>::iterator it; CSocketRecvData* pData = NULL; if (NULL == pLoginInfo) { return FALSE; } EnterCriticalSectionEx(&g_csListThreadRecv, _T("1.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end(); it++) { if (NULL != *it) { if (!(*it)->IsRunning()) { continue; } pData = (CSocketRecvData*)(*it)->GetUserData(); if (NULL != pData) { if (0 == _tcscmp(pData->GetLoginInfo().szUserName, pLoginInfo->szUserName)) { bRc = TRUE; break; } } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("1.1")); return bRc;}BOOL PrintfInfo_ListThreadRecv() { BOOL bRc = FALSE; list<CMyThread*>::iterator it; CSocketRecvData* pData = NULL; EnterCriticalSectionEx(&g_csListThreadRecv, _T("2.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end(); it++) { if (NULL != *it) { pData = (CSocketRecvData*)(*it)->GetUserData(); if (NULL != pData) { pData->GetLoginInfo().ShowDetail(); } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("2.1")); return bRc;}BOOL ProcessIm(CSocketRecvData* pData, TAG_CONTROLCMDDATA& CmdData) { /// 接收登录数据 int iRc = 0; iRc = recvEx(pData->GetSock(), (char*)&CmdData, sizeof(CmdData), 0); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); return FALSE; } if (iRc != sizeof(CmdData)) { return FALSE; } return TRUE;}BOOL ProcessIm_PublicTalk(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData) { TAG_CONTROLCMDDATA* pCmdDataDst = NULL;// ShowMsg(_T("收到群聊数据")); if (NULL != pCmdData) { pCmdDataDst = new TAG_CONTROLCMDDATA; if (NULL != pCmdDataDst) { memcpy(pCmdDataDst, pCmdData, sizeof(TAG_CONTROLCMDDATA)); AddDataToList(g_ListThreadSend, pCmdDataDst, g_csListThreadSend); } } return TRUE;}BOOL ProcessIm_PrivateTalk(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData) { TAG_CONTROLCMDDATA* pCmdDataDst = NULL;// ShowMsg(_T("收到私聊数据")); if (NULL != pCmdData) { pCmdDataDst = new TAG_CONTROLCMDDATA; if (NULL != pCmdDataDst) { memcpy(pCmdDataDst, pCmdData, sizeof(TAG_CONTROLCMDDATA)); AddDataToList(g_ListThreadSend, pCmdDataDst, g_csListThreadSend); } } return TRUE;}BOOL ProcessIm_HeartBeat(CSocketRecvData* pData, TAG_CONTROLCMDDATA* pCmdData) { CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; TCHAR cBufToDisp[4096] = {_T('\0')}; SYSTEMTIME st; if (NULL != pCmdData) { /// 更新心跳时间戳 EnterCriticalSectionEx(&g_csListThreadRecv, _T("3.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (pUserData->GetLoginInfo().dwUserID == pCmdData->Data.ImHeartBeat.dwUserID) { /// 更新时间戳 pUserData->UpdateHeartBeatTimestamp(); st = pUserData->GetHeartBeatTimestampSystemTime(); _stprintf(cBufToDisp, _T("收到[%d]心跳数据[%d], 时间戳[%d-%d-%d %d:%d:%d %d]"), pCmdData->Data.ImHeartBeat.dwUserID, pUserData->GetHeartBeatTimestampCnt(), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); ShowMsg(cBufToDisp); break; } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("3.1")); } return TRUE;}BOOL tickcount_2_systemtime(long lTickCount, SYSTEMTIME* pst){ BOOL bRc = FALSE; time_t tmt = -1; struct tm* pGmt = NULL; long lTmp = lTickCount; do { if (NULL == pst) break; //pGmt = localtime(&lTmp); tmt = lTickCount; pGmt = gmtime(&tmt); if (NULL == pGmt) break; pst->wYear = 1900 + pGmt->tm_year; pst->wMonth = 1 + pGmt->tm_mon; pst->wDay = pGmt->tm_mday; pst->wHour = pGmt->tm_hour; pst->wMinute = pGmt->tm_min; pst->wSecond = pGmt->tm_sec; pst->wDayOfWeek = pGmt->tm_wday; bRc = TRUE; } while (0); return bRc;}unsigned fnThreadProc_SocketSend_UserOnline(TAG_CONTROLCMDDATA* p) { int iRc = 0; CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; if (NULL == p) { return S_FALSE; } /// 遍历当前用户接收线程列表, 将p发到每个用户 EnterCriticalSectionEx(&g_csListThreadRecv, _T("4.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (pUserData->GetLoginInfo().dwUserID == p->Data.UserOnline.dwUserID) { /// 不发给用户自己 continue; } iRc = sendEx(pUserData->GetLoginInfo().dwUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("4.1")); iRc = 0; return iRc;}unsigned fnThreadProc_SocketSend_UpdateUserList(TAG_CONTROLCMDDATA* p) { int iUserOnlineCnt = 0; int iIndex = 0; DWORD dwSizeToSend = 0; ///< 最终发送需要的容量 DWORD dwSizeUserList = 0; ///< 用户列表信息需要的容量 int iRc = 0; CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; TAG_CONTROLCMDDATA* pDst = NULL; if (NULL == p) { return iRc; } /// 遍历当前用户接收线程列表, 统计在线用户数量(不包括自己) EnterCriticalSectionEx(&g_csListThreadRecv, _T("5.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (pUserData->GetLoginInfo().dwUserID == p->Data.UserOnline.dwUserID) { /// 不发给用户自己 continue; } iUserOnlineCnt++; } } /// 填充在线用户列表数据结构, 并发送给刚登录的用户 if (iUserOnlineCnt > 0) { dwSizeUserList = sizeof(eControlCmd) + sizeof(TAG_CONTROLCMDDATA_USER_USERINFOLIST) + sizeof(TAG_CONTROLCMDDATA_USER_ONLINE) * (iUserOnlineCnt - 1); dwSizeToSend = sizeof(TAG_CONTROLCMDDATA); if (dwSizeToSend < dwSizeUserList) { dwSizeToSend = dwSizeUserList; } pDst = (TAG_CONTROLCMDDATA*)(new BYTE[dwSizeToSend]); assert(NULL != pDst); pDst->Cmd = eControlCmd_UserList; pDst->Data.UserInfoList.dwUserID = p->Data.UserOnline.dwUserID; _tcscpy(pDst->Data.UserInfoList.szUserName, p->Data.UserOnline.szUserName); pDst->Data.UserInfoList.dwUserOnlineCnt = iUserOnlineCnt; for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (pUserData->GetLoginInfo().dwUserID == p->Data.UserOnline.dwUserID) { /// 用户自己的在线状态不发给用户自己 continue; } pDst->Data.UserInfoList.UserInfoAry[iIndex].dwUserID = pUserData->GetLoginInfo().dwUserID; _tcscpy(pDst->Data.UserInfoList.UserInfoAry[iIndex].szUserName, pUserData->GetLoginInfo().szUserName); iIndex++; } } iRc = sendEx(p->Data.UserOnline.dwUserID, (const char *)pDst, dwSizeToSend, 0); delete pDst; pDst = NULL; } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("5.1")); return iRc;}unsigned fnThreadProc_SocketSend_loginByClient(TAG_CONTROLCMDDATA* p) { sendEx(p->Data.LoginInfo.dwUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); return 0;}unsigned fnThreadProc_SocketSend_loginOutByClient(TAG_CONTROLCMDDATA* p) { sendEx(p->Data.LoginInfo.dwUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); return 0;}unsigned fnThreadProc_SocketSend_UserDownline(TAG_CONTROLCMDDATA* p) { int iRc = 0; CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; if (NULL == p) { return S_FALSE; } /// 遍历当前用户接收线程列表, 将p发到每个用户 EnterCriticalSectionEx(&g_csListThreadRecv, _T("6.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (pUserData->GetLoginInfo().dwUserID == p->Data.UserDownline.dwUserID) { /// 不发给用户自己 continue; } iRc = sendEx(pUserData->GetLoginInfo().dwUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("6.1")); return S_OK;}unsigned fnThreadProc_SocketSend_ImPublic(TAG_CONTROLCMDDATA* p) { int iRc = 0; CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; if (NULL == p) { return S_FALSE; } /// 遍历当前用户接收线程列表, 将p发到每个用户 EnterCriticalSectionEx(&g_csListThreadRecv, _T("7.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL != pUserData) { iRc = sendEx(pUserData->GetLoginInfo().dwUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("7.1")); return 0;}unsigned fnThreadProc_SocketSend_ImPrivate(TAG_CONTROLCMDDATA* p) { int iRc = 0; CMyThread* pThread = NULL; list<CMyThread*>::iterator it; CSocketRecvData* pUserData = NULL; if (NULL == p) { return iRc; } /// 遍历当前用户接收线程列表, 将p发到指定用户, 缺的参数填上 EnterCriticalSectionEx(&g_csListThreadRecv, _T("8.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();it++) { pThread = *it; if ((NULL != pThread) && pThread->IsRunning()) { pUserData = (class CSocketRecvData *)pThread->GetUserData(); if (NULL != pUserData) { if (pUserData->GetLoginInfo().GetUserID() == p->Data.ImPrivate.dwOtherUserID) { _tcscpy(p->Data.ImPrivate.szOtherUserName, pUserData->GetLoginInfo().GetUserName()); iRc = sendEx(p->Data.ImPrivate.dwOtherUserID, (const char *)p, sizeof(TAG_CONTROLCMDDATA), 0); if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); } break; } } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("8.1")); return iRc;}unsigned __stdcall fnThreadProc_HeartBeatCheck(void* pParam) { DWORD dwHeartBeatSpanTime = SERVER_HEARTBEAT_CHECK_SPANTIME; DWORD dwHeartBeatTimeBegin = GetTickCount(); ShowMsg(_T(">> fnThreadProc_HeartBeatCheck")); do { Sleep(1); if (!m_ThreadHeartBeat.cbIsCanContinue()) { continue; } if (m_ThreadHeartBeat.cbIsNeedQuit()) { break; } if ((GetTickCount() - dwHeartBeatTimeBegin) >= dwHeartBeatSpanTime) { dwHeartBeatTimeBegin = GetTickCount(); /// 心跳发送间隔到, 开始清理垃圾接收线程, 更新客户端在线用户列表 RemoveTrashFromListThreadRecv(); } } while (1); ShowMsg(_T("<< fnThreadProc_HeartBeatCheck")); return 0;}unsigned __stdcall fnThreadProc_SocketSend(void* pParam) { int iRc = 0; TAG_CONTROLCMDDATA* p = NULL; ShowMsg(_T(">> fnThreadProc_SocketSend")); do { Sleep(1); if (!m_ThreadSend.cbIsCanContinue()) { continue; } if (m_ThreadSend.cbIsNeedQuit()) { break; } p = GetDataFromList(g_ListThreadSend, g_csListThreadSend); if (NULL == p) { continue; } // _tprintf(_T("p->Cmd = %d\n"), p->Cmd); switch (p->Cmd) { case eControlCmd_loginByClient: iRc = fnThreadProc_SocketSend_loginByClient(p); break; case eControlCmd_loginOutByClient: iRc = fnThreadProc_SocketSend_loginOutByClient(p); break; case eControlCmd_ImPublic: iRc = fnThreadProc_SocketSend_ImPublic(p); break; case eControlCmd_ImPrivate: iRc = fnThreadProc_SocketSend_ImPrivate(p); break; case eControlCmd_OnLine: iRc = fnThreadProc_SocketSend_UserOnline(p); break; case eControlCmd_UserList: iRc = fnThreadProc_SocketSend_UpdateUserList(p); break; case eControlCmd_DownLine: iRc = fnThreadProc_SocketSend_UserDownline(p); break; case eControlCmd_HeartBeat: break; default: break; } if (NULL != p) { delete p; p = NULL; } if (SOCKET_ERROR == iRc) { ShowSocketErrMsg(); break; } } while (1); ShowMsg(_T("<< fnThreadProc_SocketSend")); return 0;}unsigned __stdcall fnThreadProc_SocketRecv(void* pParam) { TCHAR cBufToDisp[4096] = {_T('\0')}; CSocketRecvData* pData = (CSocketRecvData*)pParam; TAG_CONTROLCMDDATA CmdData; BOOL bNeedQuit = FALSE; if (NULL == pData) { return 0; } do { if (NULL != pData->GetThreadClass()) { if (!pData->GetThreadClass()->cbIsCanContinue()) { continue; } if (pData->GetThreadClass()->cbIsNeedQuit()) { break; } } /// 接收认证成功后的数据(群聊,私聊,心跳) if (ProcessIm(pData, CmdData)) { switch (CmdData.Cmd) { case eControlCmd_loginByClient: { ProcessUserLogin(pData, CmdData.Data.LoginInfo); } break; case eControlCmd_loginOutByClient: { ProcessUserLoginOut(pData, pData->GetLoginInfo()); bNeedQuit = TRUE; } break; case eControlCmd_ImPublic: { ProcessIm_PublicTalk(pData, &CmdData); } break; case eControlCmd_ImPrivate: { ProcessIm_PrivateTalk(pData, &CmdData); } break; case eControlCmd_HeartBeat: { ProcessIm_HeartBeat(pData, &CmdData); } break; default: ShowMsg(_T("收到未知的协议格式, 是否需要更新服务端?")); break; } } else { bNeedQuit = TRUE; } } while (!bNeedQuit); _stprintf(cBufToDisp, _T("<< fnThreadProc_SocketRecv[%d]"), pData->GetSock()); ShowMsg(cBufToDisp); return 0;}unsigned __stdcall fnStopThread_SocketRecv(void* pParam) { CSocketRecvData* pData = (CSocketRecvData*)pParam; if (NULL != pData) { /// 关掉socket句柄, socket句柄相关的阻塞操作,就失败返回了 if ((NULL != pData->GetSock()) && (SOCKET_ERROR != pData->GetSock())) { closesocket(pData->GetSock()); } } return 0;}unsigned __stdcall fnDetoryUserDataProc_SocketRecv(void* pParam) { CSocketRecvData* pData = (CSocketRecvData*)pParam; if (NULL != pData) { delete pData; } return 0;}void ShowSocketErrMsg() { int iErrSn = WSAGetLastError(); ShowErrMsg(iErrSn);}void ShowErrMsg() { int iErrSn = GetLastError(); ShowErrMsg(iErrSn);}void ShowErrMsg(int iErrSn) { LPVOID lpMsgBuf = NULL; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, iErrSn, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL); if (NULL != lpMsgBuf) { ShowMsg((TCHAR*)lpMsgBuf); } LocalFree(lpMsgBuf);}void ShowMsg(TCHAR* pMsg) { if (NULL != pMsg) { _tprintf(_T("%s\r\n"), pMsg); }}void AddThreadToListThreadRecv(CMyThread* p) { if (NULL != p) { EnterCriticalSectionEx(&g_csListThreadRecv, _T("9.0")); g_ListThreadRecv.push_back(p); LeaveCriticalSectionEx(&g_csListThreadRecv, _T("9.1")); }}void StopAndRemoveAllFromListThreadRecv() { list<CMyThread*>::iterator it; CMyThread* pThread = NULL; EnterCriticalSectionEx(&g_csListThreadRecv, _T("10.0")); while (!g_ListThreadRecv.empty()) { it = g_ListThreadRecv.begin(); pThread = *it; if (NULL != pThread) { g_ListThreadRecv.erase(it); pThread->Stop(); delete pThread; pThread = NULL; } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("10.1"));}BOOL RemoveTrashFromListThreadRecv() { BOOL bHeartBeatTimeOut = FALSE; CMyThread* pThread = NULL; CSocketRecvData* pUserData = NULL; list<CMyThread*>::iterator it; TAG_CONTROLCMDDATA* pCmdData = NULL; TCHAR cBufToDisp[4096] = {_T('\0')}; EnterCriticalSectionEx(&g_csListThreadRecv, _T("11.0")); for (it = g_ListThreadRecv.begin(); it != g_ListThreadRecv.end();) { pThread = *it; if (NULL != pThread) { pUserData = (CSocketRecvData*)pThread->GetUserData(); if (NULL == pUserData) { continue; } if (!pThread->IsRunning()) { /// 压入客户端下线数据 if (NULL != pUserData) { pCmdData = new TAG_CONTROLCMDDATA; if (NULL != pCmdData) { _stprintf(cBufToDisp, _T("HeartBeat : UserDownline[%d]"), pUserData->GetLoginInfo().GetUserID()); ShowMsg(cBufToDisp); pCmdData->Cmd = eControlCmd_DownLine; pCmdData->Data.UserDownline.dwUserID = pUserData->GetLoginInfo().GetUserID(); _tcscpy(pCmdData->Data.UserDownline.szUserName, pUserData->GetLoginInfo().GetUserName()); AddDataToList(g_ListThreadSend, pCmdData, g_csListThreadSend); } } /// 只能直接删已经停止的线程, 防止要删除的线程中用到g_csListThreadRecv死锁 it = g_ListThreadRecv.erase(it); pThread->Stop(); delete pThread; pThread = NULL; } else { bHeartBeatTimeOut = ((GetTickCount() - pUserData->GetTickHeartBeatTimestamp()) > SERVER_HEARTBEAT_CHECK_SPANTIME); if (bHeartBeatTimeOut) { /// 对于超时的接收线程, 关掉Sockt句柄, 让她由于错误而自然退出. closesocket(pUserData->GetSock()); pUserData->SetSock(INVALID_SOCKET); } it++; } } } LeaveCriticalSectionEx(&g_csListThreadRecv, _T("11.1")); return TRUE;}void AddDataToList(list<TAG_CONTROLCMDDATA*>& List, TAG_CONTROLCMDDATA* p, CRITICAL_SECTION& cs) { if (NULL != p) { EnterCriticalSectionEx(&cs, _T("AddDataToList.0")); List.push_back(p); LeaveCriticalSectionEx(&cs, _T("AddDataToList.1")); }}TAG_CONTROLCMDDATA* GetDataFromList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs) { TAG_CONTROLCMDDATA* p = NULL; EnterCriticalSectionEx(&cs, NULL); if (!List.empty()) { p = List.front(); List.pop_front(); } LeaveCriticalSectionEx(&cs, NULL); return p;}void RemoveList(list<TAG_CONTROLCMDDATA*>& List, CRITICAL_SECTION& cs) { TAG_CONTROLCMDDATA* p = NULL; list<TAG_CONTROLCMDDATA*>::iterator it; EnterCriticalSectionEx(&cs, _T("RemoveList.0")); while (!List.empty()) { p = List.front(); List.pop_front(); if (NULL != p) { delete p; } } LeaveCriticalSectionEx(&cs, _T("RemoveList.1"));}VOID EnterCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, TCHAR* pTip) { if (NULL != pTip) { /// 可以调试下,哪个临界区在多线程中卡住了// if (NULL != _tcsstr(pTip, _T("11.0")))// _tprintf(_T("%s\n"), pTip); } if (NULL != lpCriticalSection) { EnterCriticalSection(lpCriticalSection); }}VOID LeaveCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, TCHAR* pTip) { if (NULL != lpCriticalSection) { LeaveCriticalSection(lpCriticalSection); } if (NULL != pTip) { /// only for debug// if (NULL != _tcsstr(pTip, _T("11.1")))// _tprintf(_T("%s\n"), pTip); }}
socket读写封装
// SocketOpt.h: interface for the CSocketOpt class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_SOCKETOPT_H__7FB1AF93_27E4_4722_8F1E_8A6AB9885786__INCLUDED_)#define AFX_SOCKETOPT_H__7FB1AF93_27E4_4722_8F1E_8A6AB9885786__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include <windows.h>#include <winsock2.h>int sendEx(SOCKET s, const char FAR *buf, int len, int flags);int recvEx(SOCKET s, char FAR *buf, int len, int flags);#endif // !defined(AFX_SOCKETOPT_H__7FB1AF93_27E4_4722_8F1E_8A6AB9885786__INCLUDED_)
// SocketOpt.cpp: implementation of the CSocketOpt class.////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "SocketOpt.h"#ifdef _DEBUG#undef THIS_FILEstatic char THIS_FILE[]=__FILE__;#define new DEBUG_NEW#endif//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////int sendEx(SOCKET s, const char FAR *buf, int len, int flags) { int iRc = 0; int iLenOpt = 0; int iPos = 0; do { iRc = send(s, buf + iPos, len - iPos, flags); if (SOCKET_ERROR == iRc) { break; } else { if (0 == iRc) { break; } iLenOpt += iRc; iPos -= iRc; } } while (len != iLenOpt); return ((len == iLenOpt) ? len : iRc);}int recvEx(SOCKET s, char FAR *buf, int len, int flags) { int iRc = 0; int iLenOpt = 0; int iPos = 0; do { iRc = recv(s, buf + iPos, len - iPos, flags); if (SOCKET_ERROR == iRc) { break; } else { if (0 == iRc) { break; } iLenOpt += iRc; iPos -= iRc; } } while (len != iLenOpt); return ((len == iLenOpt) ? len : iRc);}
线程类封装
// MyThread.h: interface for the CMyThread class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_MYTHREAD_H__8B713CD4_E5C4_467D_87C0_B5197689E5C8__INCLUDED_)#define AFX_MYTHREAD_H__8B713CD4_E5C4_467D_87C0_B5197689E5C8__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include "stdafx.h"#include <windows.h>#include <process.h>class CMyThread {public: typedef unsigned (__stdcall *PFN_THREADPROC)(void*);public: CMyThread(); virtual ~CMyThread(); void Start(); ///< 线程开始 void Pause(); ///< 线程挂起 void Continue(); ///< 线程恢复运行 void Stop(); ///< 线程停止 BOOL IsRunning(); ///< 是否在运行? BOOL cbIsCanContinue(); ///< 给线程函数的回调-是否可以继续运行线程 BOOL cbIsNeedQuit(); ///< 给线程函数的回调-是否退出线程 void SetParam( PFN_THREADPROC pThreadProc, PFN_THREADPROC pStopProc, PFN_THREADPROC pDetoryUserDataProc, void* pUserData); void* GetUserData();private: /// 用户传入的参数 PFN_THREADPROC m_pThreadProc; ///< 线程处理函数 PFN_THREADPROC m_pStopProc; ///< 线程停止前的调用者处理, e.g. 解除用户代码的线程阻塞 PFN_THREADPROC m_pDetoryUserDataProc; ///< 请调用者销毁用户数据 void* m_pUserData; ///< 用户数据指针, 我们不动, 创建线程时, 当线程参数 /// 内部数据 HANDLE m_hThread; ///< 线程句柄 UINT m_uThreadId; ///< 线程ID HANDLE m_hEventContinue; ///< 事件句柄 - 继续 HANDLE m_hEventQuit; ///< 事件句柄 - 退出线程};#endif // !defined(AFX_MYTHREAD_H__8B713CD4_E5C4_467D_87C0_B5197689E5C8__INCLUDED_)
// MyThread.cpp: implementation of the CMyThread class.////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include <windows.h>#include <process.h>#include "MyThread.h"#ifdef _DEBUG#undef THIS_FILEstatic char THIS_FILE[]=__FILE__;#define new DEBUG_NEW#endif//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////CMyThread::CMyThread(){ m_hThread = NULL; m_pThreadProc = NULL; m_pStopProc = NULL; m_pDetoryUserDataProc = NULL; m_uThreadId = (UINT)-1; m_pUserData = NULL; /// 事件对象用于通知机制时的创建要求, 手工复位, 有信号 m_hEventContinue = CreateEvent(NULL, TRUE, TRUE, NULL); m_hEventQuit = CreateEvent(NULL, TRUE, TRUE, NULL);}CMyThread::~CMyThread(){ Stop(); if (NULL != m_pDetoryUserDataProc) { (*m_pDetoryUserDataProc)(m_pUserData); }}void CMyThread::SetParam( PFN_THREADPROC pThreadProc, PFN_THREADPROC pStopProc, PFN_THREADPROC pDetoryUserDataProc, void* pUserData) { m_pThreadProc = pThreadProc; m_pStopProc = pStopProc; m_pDetoryUserDataProc = pDetoryUserDataProc; m_pUserData = pUserData;}void* CMyThread::GetUserData() { return m_pUserData;}void CMyThread::Start() { if (NULL != m_pThreadProc) { Continue(); if (!IsRunning()) { m_hThread = (HANDLE)_beginthreadex( NULL, 0, m_pThreadProc, m_pUserData, 0, &m_uThreadId); } }}void CMyThread::Pause() { ResetEvent(m_hEventContinue); ResetEvent(m_hEventQuit);}void CMyThread::Continue() { SetEvent(m_hEventContinue); ResetEvent(m_hEventQuit);}BOOL CMyThread::cbIsCanContinue() { WaitForSingleObject(m_hEventContinue, INFINITE); return TRUE;}BOOL CMyThread::cbIsNeedQuit() { return (WAIT_OBJECT_0 == WaitForSingleObject(m_hEventQuit, 0));}void CMyThread::Stop() { if (NULL != m_pStopProc) { (*m_pStopProc)(m_pUserData); m_pUserData = NULL; } SetEvent(m_hEventQuit); Continue(); if (NULL != m_hThread) { WaitForSingleObject(m_hThread, INFINITE); m_hThread = NULL; }}BOOL CMyThread::IsRunning() { DWORD dwRc = 0; DWORD dw0 = WAIT_ABANDONED; ///< 0x80 DWORD dw1 = WAIT_OBJECT_0; ///< 0x0 DWORD dw2 = WAIT_TIMEOUT; ///< 0x102 if (NULL == m_hThread) { return FALSE; } /// 在线程运行时, 等到 WAIT_TIMEOUT /// 在线程没有运行时, 返回 WAIT_OBJECT_0 dwRc = WaitForSingleObject(m_hThread, 0); return (WAIT_OBJECT_0 == dwRc) ? FALSE : TRUE;}
线程类中用户数据上下文类
// SocketRecvData.h: interface for the CSocketRecvData class.////////////////////////////////////////////////////////////////////////#if !defined(AFX_SOCKETRECVDATA_H__B87C87F9_7976_432D_9305_B50010127AE3__INCLUDED_)#define AFX_SOCKETRECVDATA_H__B87C87F9_7976_432D_9305_B50010127AE3__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#include "MyThread.h"#include "ControlCmd.h"class CSocketRecvData {public: CSocketRecvData(); virtual ~CSocketRecvData(); SOCKET& GetSock(); void SetSock(SOCKET s); CMyThread* GetThreadClass(); void SetThreadClass(CMyThread* p); TAG_CONTROLCMDDATA_LOGINBYCLIENT& GetLoginInfo(); void UpdateHeartBeatTimestamp(); DWORD GetTickHeartBeatTimestamp(); DWORD GetHeartBeatTimestampCnt(); SYSTEMTIME GetHeartBeatTimestampSystemTime();private: DWORD tmTickHeartBeatTimestamp; ///< 心跳时间戳 DWORD dwHeartBeatTimestampCnt; ///< 总共收到的时间戳数量 SYSTEMTIME stHeartBeatTimestamp; ///< 心跳时间戳对应的本地时间 SOCKET m_socket; CMyThread* m_pMyThread; TAG_CONTROLCMDDATA_LOGINBYCLIENT LoginInfo; ///< 登录信息};#endif // !defined(AFX_SOCKETRECVDATA_H__B87C87F9_7976_432D_9305_B50010127AE3__INCLUDED_)
// SocketRecvData.cpp: implementation of the CSocketRecvData class.////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "SocketRecvData.h"//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////CSocketRecvData::CSocketRecvData(): dwHeartBeatTimestampCnt(-1){ SetSock(NULL); SetThreadClass(NULL); UpdateHeartBeatTimestamp(); ::ZeroMemory(&LoginInfo, sizeof(LoginInfo)); LoginInfo.bLoginOK = FALSE;}CSocketRecvData::~CSocketRecvData(){}void CSocketRecvData::UpdateHeartBeatTimestamp() { tmTickHeartBeatTimestamp = GetTickCount(); GetLocalTime(&stHeartBeatTimestamp); dwHeartBeatTimestampCnt++;}DWORD CSocketRecvData::GetHeartBeatTimestampCnt() { return dwHeartBeatTimestampCnt;}DWORD CSocketRecvData::GetTickHeartBeatTimestamp() { return tmTickHeartBeatTimestamp;}SYSTEMTIME CSocketRecvData::GetHeartBeatTimestampSystemTime() { return stHeartBeatTimestamp;}SOCKET& CSocketRecvData::GetSock() { return m_socket;}void CSocketRecvData::SetSock(SOCKET s) { m_socket = s;}CMyThread* CSocketRecvData::GetThreadClass() { return m_pMyThread;}void CSocketRecvData::SetThreadClass(CMyThread* p) { m_pMyThread = p;}TAG_CONTROLCMDDATA_LOGINBYCLIENT& CSocketRecvData::GetLoginInfo() { return LoginInfo;}
<2016-0404>fix
发现sendEx和recvEx整错了, 这样都能测试通过,可能是因为数据量小,一次都执行完了
// SocketOpt.cpp: implementation of the CSocketOpt class.////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "SocketOpt.h"#ifdef _DEBUG#undef THIS_FILEstatic char THIS_FILE[]=__FILE__;#define new DEBUG_NEW#endif//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////int sendEx(SOCKET s, const char FAR *buf, int len, int flags) { int iRc = 0; int iLenOpt = 0; do { iRc = send(s, buf + iLenOpt, len - iLenOpt, flags); if (SOCKET_ERROR == iRc) { break; } else { if (0 == iRc) { break; } iLenOpt += iRc; } } while (len != iLenOpt); return ((len == iLenOpt) ? len : iRc);}int recvEx(SOCKET s, char FAR *buf, int len, int flags) { int iRc = 0; int iLenOpt = 0; do { iRc = recv(s, buf + iLenOpt, len - iLenOpt, flags); if (SOCKET_ERROR == iRc) { break; } else { if (0 == iRc) { break; } iLenOpt += iRc; } } while (len != iLenOpt); return ((len == iLenOpt) ? len : iRc);}
- sockt练习-文本聊天c/s实现
- c语言sockt实现通过浏览器访问ip返回数据
- python c/s 聊天
- llinux C练习十 Tcp通信select实现简易聊天
- 用socket简单实现C/S聊天通信
- 基于C/S架构的聊天系统的实现
- QT 局域网聊天 C/S
- C++primer 文本查询练习
- Java swing实现简单的C/S聊天及文件传输系统
- 模拟QQ聊天——采用TCP协议的C/S架构实现
- C/S聊天模型——服务器端
- C/S聊天模型——客户端
- 用JAVA编程C/S聊天程序
- java----------C/S编程-----简单聊天程序
- [C#]即时通讯——文本聊天做好了,文件传输还没做
- Flex与Ruby通过socket实现通简易文本聊天
- JAVASCRIPT实现基于文本的自动智能聊天机器人
- 基于Java的tcp实现文本聊天系统
- strcpy 和 memcpy 用法的区别
- 设计模式4#值对象
- DuiLib(6)——界面管家CPaintManagerUI的函数简介
- 第三周项目四(7)-谁是小偷
- Step by Step into Spring (AOP)
- sockt练习-文本聊天c/s实现
- HDU 1698 Just a Hook(线段树的区间更新)
- Android设计模式应用--访问者模式
- Apue学习:线程
- LEETCODE 7. Reverse Integer 判断溢出的解决方案
- [3] OFDM符号ofdm_signal
- leetcode 328. Odd Even Linked List
- oj问题二-结构体-职工信息结构体
- iOS 绘制渐变图形 Quartz2D 之Swift