程序實現功能:
局域網聊天軟件,啟動即可找到在線設備,並能夠進行簡單的文字聊天。
其實下面這個框圖已經說明了程序的絕大部分功能原理。
核心類的程序框圖
我覺得,這個程序中使用的最好的技術,應該就是IOCP了。後面我會針對IOCP好好地寫一篇博文,這個技術雖然剛學的時候有點亂,但是確實很好用。
上面的框圖中中間的UDPServer線程等待的事件完成是MainServer線程在Listen函數調用結束後設置的事件。這裡忘了標了。
說明
前幾天在實驗室看《Windows網絡與通信程序設計》這本書,看完了前5章吧,就覺得目前手頭的技術去做一個局域網聊天軟件應該差不多了。雖然還有很多細節性的東西還不是非常懂,但是做一個小規模的軟件,我自認為是問題不大的。就這樣,在大約4天之後,也就是這個月的18號,這款LANChat程序誕生~
首先我聲明:因為我第一次寫網絡相關的程序,所以肯定存在疏忽,在相關方面肯定是存在不少bug的,另外我在測試的時候也經常遇到程序啟動失敗的情況,而且原因尚未查明。所以我並不保證這款程序的穩定性和安全性。(作為這個程序的設計人員真是感到汗顏~以後會好的)
另外代碼中大部分析構函數形同虛設,畢竟最初實現的時候尚不清楚能夠實現與其功能,所以根本就沒顧忌資源釋放這一塊。比如,聊天窗口建立這一塊我就沒使用delete。
多線程部分因為涉及到對數據的訪問問題,所以我使用了關鍵段:CriticalSection結構以及相關函數。
因為這個文檔是在寢室寫的,所以沒有在線設備,也無法打開聊天窗口,在實驗室三台計算機之間使用沒問題。
另外winsock2初始化是在工程中選擇的,在如console類的程序中使用socket程序前一定要做好相關的初始化以及庫,文件包含工作。
socket使用程序初次嘗試。
1 程序實現基本思想
首先用UDP廣播通知在線設備,在本地IP對應的位置有設備上線。局域網內的在線設備收到UDP廣播消息後,首先會做的事情是,獲取目標的IP地址,然後初始化一個sockaddr結構,向其發送TCP連接請求。
剛上線的設備此時會已經做好接受准備(這裡使用的事件線程同步),在TCP連接請求建立後,會將其加入到IOCP中,並且為其投遞一個數據接受的消息。之後任何到來的消息都會由IOCP的線程函數專門進行處理,十分的方便。而IOCP線程函數是通過調用GetQueueCompletionStatues函數將線程添加到IOCP的線程池中的。
這樣任何一台剛上限的設備都能夠找到局域網內的其他在線設備並與其簡歷TCP鏈接,為局域網聊天軟件的實現奠定了理論基礎。
2 界面實現
因為這個程序其實並不設計什麼算法性的代碼,連數據查找我用的貌似也是STL的find方法,所以對於程序的網絡代碼我並不想將太多,意義不大,等會介紹下核心類的接口就算完事了,先介紹界面的設計。
看著win低畫質的界面,頓時覺得,Win7下高畫質的界面好多啦~
首先,主界面中有2個Edit編輯控件,一個List控件,兩個按鈕。在list控件中包含了兩個列,目前僅僅對第一個IPAddr進行了使用。在線設備在list控件中會顯示出來其IP地址。
目前僅做了雙擊顯示出來的在線項彈出聊天對話框的功能,其余尚未添加。而且按下空格和Enter鍵會直接退出程序。這個問題屬於還未處理好的鍵盤消息響應。
(1) 當有數據報到來並且完成接收後如何通知子窗口並且顯示出來?
CLANChat構造函數的參數為一個CDialog的指針。而在主對話框程序中,作為顯示的Dialog對話框派生類包含一個CLANChat的對象。在對話框對象啟動的時候,在其構造函數的參數列表中為CLANChat對象傳入this指針,用於其初始化。這樣對於到來的消息,在未彈出窗口的情況下,就能夠根據對話框窗口的句柄使用postmessage函數將消息派發出去。
同時CLANChat類中的在線列表是一個vector模板,作為其類型參數的是專門設計的OnLineNode類,其中包含了指向字聊天對話框窗口的指針。
3 CLANChat類的接口
class CLANChat { private: //local veriable CDialog * m_pDlgFrame;//指向主體窗口 DlgIOCP ** m_pDlgIOCPArray; //socket about static vector<OnLineNode> g_OnLineList;//需要維護的在線列表 static volatile HANDLE hIOCP;//IO完成端口句柄 static ULONG g_IPaddr;//本地IP地址 static SOCKET sListen;//本地SOCKET static sockaddr_in m_local_sa_in;//本地sockaddr private: //Method Lock CLANChat(CLANChat &); CLANChat & operator = (CLANChat &); public: CLANChat(CDialog * pDlg); ~CLANChat(void); void CreateUDPBroadcastThread(LPVOID lpParam); DWORD GetCPUNumber(); int SendInfo(char * pbuf, int length, in_addr nListID); SOCKET GetSocketWithIP(in_addr addr); vector<OnLineNode> * GetOnLineListPtr(); CEvent m_eMainUDPServer; //類線程函數必須是靜態函數,因為靜態函數無this指針 static DWORD WINAPI MainServer(LPVOID lpParam); static DWORD WINAPI UDPServer(LPVOID lpParam); static DWORD WINAPI IOCPTCPSever(LPVOID lpParam); };
上面的這個類就是我代碼中最核心的一個類吧,其中的重要數據並不算多,而且隨著代碼更改,有些變量和函數已經變得沒用或者不合適這種生命方式了。其主要原因還是在於,我初次編寫多線程的程序,對其中的部分內容使用的不熟練,改來改去,這代碼我現在自己看著都有點覺得糟心。唉,如果再讓我寫一遍這個程序,我做的第一件事情肯定是先設計,把框圖什麼的都先設計好,然後再開始動手寫代碼。現在我覺得我寫這個文檔都有點不知道應該怎麼寫了~
這個類最主要的任務就是啟動3中線程:MainServer UDPServer IOCPServer。就這些工作,然後還要負責消息的派送,僅此而已。
在這程序中我最滿意的就是,我比較好的使用了IOCP,這一點我很高興,因為之前在做串口調試助手的時候,曾經在《windows核心編程》這本書中看到過,但是當時沒理解。如今到了Socket下,我用的還不錯,而且算是理解其原理了。
4:CLANChat類的實現:(代碼)
#include "stdafx.h" #include "LANChat.h" #include "afxdialogex.h" #include "LANChat20130815.h" #include <algorithm> DWORD CLANChat::g_IPaddr = 0; SOCKET CLANChat::sListen = 0; HANDLE volatile CLANChat::hIOCP = 0; sockaddr_in CLANChat::m_local_sa_in; vector<OnLineNode> CLANChat::g_OnLineList; using namespace std; CLANChat::CLANChat(CDialog * pDlg):m_pDlgFrame(pDlg) { //線程同步時間初始化 m_eMainUDPServer.ResetEvent(); //sockaddr m_local_sa_in.sin_family = AF_INET; m_local_sa_in.sin_port = htons(4567); m_local_sa_in.sin_addr.S_un.S_addr = INADDR_ANY; //這裡有必要獲取一下本地的IP地址 char szHostName[256]; gethostname(szHostName, 256); hostent * pht = gethostbyname(szHostName); char * pIP = pht->h_addr_list[0];//多網卡設備還暫時無法處理 memcpy(&g_IPaddr, pIP, pht->h_length); //IOCP創建以及sever線程啟動 //根據核心數來創建線程數 int cpunum = GetCPUNumber(); hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, cpunum); m_pDlgIOCPArray = new DlgIOCP * [cpunum]; for(int i = 0;i < cpunum;++ i) { m_pDlgIOCPArray[i] = new DlgIOCP; m_pDlgIOCPArray[i]->hIOCP = hIOCP; m_pDlgIOCPArray[i]->pDlg = pDlg; } //在這裡需要額外創建一個主服務線程,因為在console程序中,能夠使用程序的線程,而這裡無法使用。 CreateThread(0, 0, MainServer, this, 0, 0); //接著創建UDP線程。開始廣播以及准備接受UDP廣播 CreateUDPBroadcastThread(this); //創建IOCP線程,並添加到線程池中 for(int i = 0;i < cpunum;++ i) { CreateThread(NULL, 0, IOCPTCPSever, m_pDlgIOCPArray[i], 0, 0); } } CLANChat::~CLANChat(void) { //清理工作 delete [] m_pDlgIOCPArray; } void CLANChat::CreateUDPBroadcastThread(LPVOID lpParam) { CreateThread(0, 0, UDPServer, lpParam, 0, 0); } DWORD WINAPI CLANChat::MainServer(LPVOID lpParam) { CLANChat * pCLC = (CLANChat *)lpParam; //建立本地TCP連接 SOCKET sListen = socket(AF_INET, SOCK_STREAM, 0); //綁定到一個本地地址 CRITICAL_SECTION cs; InitializeCriticalSection(&cs); if(bind(sListen, (sockaddr *)&m_local_sa_in, sizeof(sockaddr)) == -1) { AfxMessageBox(_T("Bind Fail!!!")); return 0; } listen(sListen, 10);//隨便設個數 //char pBufferRecv[1024]; //這個只要使用一次,就是最初的那次。 pCLC->m_eMainUDPServer.SetEvent(); while(TRUE) { sockaddr_in sa_temp; int nsa_size = sizeof(sockaddr_in); SOCKET snewsocket = accept(sListen, (sockaddr *)&sa_temp, &nsa_size); if(snewsocket == INVALID_SOCKET) { continue; } //在有效的新連接建立後,立刻將其加入到在線列表中 OnLineNode tmpnode; tmpnode.sClient = snewsocket; tmpnode.sa_in = sa_temp; EnterCriticalSection(&cs); g_OnLineList.push_back(tmpnode); LeaveCriticalSection(&cs); //在這裡使用IO完成端口來做連接監聽 //先創建一個key結構 PIOCP_KEY pTempKey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY)); if(pTempKey == NULL) continue; pTempKey->sClient = snewsocket; pTempKey->sa_in = sa_temp; pTempKey->sa_in.sin_port = htons(4567); //將IO完成端口和新創建的套接字連接綁定在一起 CreateIoCompletionPort((HANDLE)snewsocket, pCLC->hIOCP, (ULONG_PTR)pTempKey/*這個參數要用來傳遞socket所屬以及目標地址,z暫時NULL*/, 0); //投遞一個針對目標IO端口的數據接受請求 PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV)); memset(pMyOv, 0, sizeof(MOV)); if(pMyOv == NULL) return 0; pMyOv->wbuf.buf = pMyOv->buf; pMyOv->wbuf.len = 1024; pMyOv->OperateType = 0;//read DWORD flag = 0; //send(snewsocket, "HELLO", 7, 0); WSARecv(snewsocket, &pMyOv->wbuf, 1, &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL); //後面這些都是用來將數據添加到在線列表後,向dialogbox發送消息 PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, snewsocket, sa_temp.sin_addr.S_un.S_addr); //pure test send(snewsocket, "0816", 4, 0); } return 0; } DWORD WINAPI CLANChat::UDPServer(LPVOID lpParam) { CLANChat * pCLC = (CLANChat *)lpParam; sockaddr_in sa_broadcast, sa_local; //broadcast sa_broadcast.sin_addr.S_un.S_addr = inet_addr("255.255.255.255"); sa_broadcast.sin_family = AF_INET; sa_broadcast.sin_port = htons(4567); //local sa_local.sin_addr.S_un.S_addr = INADDR_ANY; sa_local.sin_family = AF_INET; sa_local.sin_port = htons(4567); //socket SOCKET sUDPBroadcast = socket(AF_INET, SOCK_DGRAM, 0); SOCKET sUDPListen = socket(AF_INET, SOCK_DGRAM, 0); if(bind(sUDPListen, (sockaddr *)&sa_local, sizeof(sockaddr)) == -1) { AfxMessageBox(_T("UDP Bind Fail!")); return 0; } //設置廣播套接字模式為廣播通信 BOOL bBroadcast = TRUE; setsockopt(sUDPBroadcast, SOL_SOCKET, SO_BROADCAST, (char *)&bBroadcast, sizeof(BOOL)); //等待MainServer基本完成初始化 WaitForSingleObject(pCLC->m_eMainUDPServer.m_hObject, INFINITE); //AfxMessageBox(_T("Get The Event!")); //廣播通信 sendto(sUDPBroadcast, "C", 1, 0, (sockaddr *)&sa_broadcast, sizeof(sockaddr)); //發送完廣播數據,這個socket就沒什麼用了 closesocket(sUDPBroadcast); //數據接受相關變量 char BUF[3];//不用很大 int nfromlength = sizeof(sockaddr); CRITICAL_SECTION cs; InitializeCriticalSection(&cs); while(TRUE) { //正在等待接收數據 //這裡以後可以改成WSARecvFrom函數,再用一個專門的線程來盡心數據接受處理,效率會更高。 int recvlength = recvfrom(sUDPListen, BUF, 3, 0, (sockaddr *)&sa_broadcast, &nfromlength); //檢測收到的是否為本地IP if(g_IPaddr == sa_broadcast.sin_addr.S_un.S_addr) continue; if(recvlength > 0) { //這一個有效的UDP數據 BUF[recvlength] = 0; #ifdef _DEBUG ; #endif //將該sockaddr結構添加到在線vector容器中,並且通知主控制線程將其添加到顯示列表 //與其建立TCP連接,這樣就能夠很好的,很及時的實現在線列表的更新 //建立一個新的本地TCP套接字 SOCKET sNewConnectSock = socket(AF_INET, SOCK_STREAM, 0); sa_broadcast.sin_port = htons(4567); #ifdef _DEBUG ;//cout << "Connecting IP " << inet_ntoa(sa_broadcast.sin_addr) << endl; #endif if(connect(sNewConnectSock, (sockaddr *)&sa_broadcast, sizeof(sockaddr)) == -1) { #ifdef _DEBUG ;//cout << "UDP Thread Connect Fail!" << endl; #endif //別忘了在連接失敗後釋放資源 closesocket(sNewConnectSock); continue;//等待下一個廣播消息 } //添加到全局列表中 OnLineNode tmpOLN; tmpOLN.sClient = sNewConnectSock; tmpOLN.sa_in = sa_broadcast; EnterCriticalSection(&cs); g_OnLineList.push_back(tmpOLN); EnterCriticalSection(&cs); #ifdef _DEBUG ;//cout << "UDP Thread's TCP Connect Success!" << endl; #endif //在這裡既需要將其加入IO完成端口,還要投遞一個WSARecv消息 PIOCP_KEY ptmpkey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY)); ptmpkey->sClient = sNewConnectSock; ptmpkey->sa_in = sa_broadcast; ptmpkey->sa_in.sin_port = htons(4567); CreateIoCompletionPort((HANDLE)ptmpkey->sClient, pCLC->hIOCP, (ULONG_PTR)ptmpkey, 0); //投遞WSARecv請求 PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV)); memset(pMyOv, 0, sizeof(MOV)); pMyOv->wbuf.buf = pMyOv->buf; pMyOv->wbuf.len = 1024; pMyOv->OperateType = 0;//read DWORD flag = 0; WSARecv(ptmpkey->sClient, &pMyOv->wbuf, 1, &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL); //添加完結構後,向主窗口發送一個更新消息,至於發什麼數據,暫時還沒想好。 PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, tmpOLN.sClient, tmpOLN.sa_in.sin_addr.S_un.S_addr); //pure test send(ptmpkey->sClient, "0816", 4, 0); } else continue; } return 0; } DWORD WINAPI CLANChat::IOCPTCPSever(LPVOID lpParam)//這個參數需要傳入的是IOCP,調用GetQueue……函數時需要使用 { DlgIOCP * DI = (DlgIOCP *)lpParam; HANDLE hCompletion = DI->hIOCP; CDialog * pDlg = DI->pDlg; delete DI;//這個要立刻銷毀,之後沒用的。 PMOV pMyOv; PIOCP_KEY pComKey; DWORD dwRecvNum; DWORD dwflag; BOOL bOK; CRITICAL_SECTION cs; InitializeCriticalSection(&cs); while(TRUE) { dwRecvNum = 0; dwflag = 0; OnLineNode tmpOLN; bOK = GetQueuedCompletionStatus(hCompletion, &dwRecvNum, (PULONG_PTR)&pComKey, (LPOVERLAPPED *)&pMyOv, INFINITE); //不管是什麼消息,都要先獲取其對應於表中的地址 EnterCriticalSection(&cs); tmpOLN.sClient = pComKey->sClient; ASSERT(!g_OnLineList.empty()); vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN); if(bOK == 0) { #ifdef _DEBUG ;//cout << "GetQueuedCompletionStatus Error!!!" << endl; #endif closesocket(pComKey->sClient); //CString tmpstr; //tmpstr = inet_ntoa(pComKey->sa_in.sin_addr); //AfxMessageBox(tmpstr); //刪除列表中的數據 if(it_temp != g_OnLineList.end()) g_OnLineList.erase(it_temp); PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); GlobalFree(pComKey); GlobalFree(pMyOv); LeaveCriticalSection(&cs); continue; } //下面這句的判斷條件是,當收到讀或寫數據後,數據長度卻為0,則表明對方關閉了套接字 if(0 == dwRecvNum && (pMyOv->OperateType == 0 || pMyOv->OperateType == 1)) { #ifdef _DEBUG /*cout << "Socket From IP " << inet_ntoa(pComKey->sa_in.sin_addr) << " Has Closed" << endl;*/ #endif //tmpOLN.sClient = pComKey->sClient; closesocket(pComKey->sClient); //刪除列表中的數據 //CString tmpstr; //tmpstr = inet_ntoa(pComKey->sa_in.sin_addr); //AfxMessageBox(tmpstr); //vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN); if(it_temp != g_OnLineList.end()) g_OnLineList.erase(it_temp); PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); GlobalFree(pComKey); GlobalFree(pMyOv); LeaveCriticalSection(&cs); continue; } ;//cout << "IOCP Server Thread Get A Message!" << endl; CString tmp_str; switch(pMyOv->OperateType) { case 0://讀 WSARecv(pComKey->sClient, &pMyOv->wbuf, 1, &dwRecvNum, &dwflag, (LPOVERLAPPED)pMyOv, NULL); //這裡的數據必須做成Unicode ((TCHAR *)pMyOv->wbuf.buf)[dwRecvNum] = 0; /*這裡的情況是收到了發送到本程序的消息,但是,在在線列表中並不存在 這種情況是不合理的。只有在在線列表中的數據才能收到消息。也就是說,肯能是在push_back 函數的調用次序上出現了問題。 */ if(it_temp == g_OnLineList.end()) { LeaveCriticalSection(&cs); continue; } ;//cout << "Message From" << inet_ntoa(pComKey->sa_in.sin_addr) << endl ;// << "Info :" << pMyOv->wbuf.buf << endl; tmp_str = (TCHAR *)pMyOv->wbuf.buf; tmp_str += "\r\n"; it_temp->vec_str_ChatHistory.push_back(tmp_str); //這裡直接給自己的對話框窗口發消息這還挺復雜的。 //當窗口已經建立的時候,應該直接給自己的窗口發消息。 //當窗口未建立的時候,應該將消息發送給主程序,然後主程序使相應的item閃爍。 if(it_temp->pDlg != NULL)//窗口未建立 { //在這只要把和自己相關聯的在線節點地址值穿過去就OK了~ PostMessage(it_temp->pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&(*it_temp), 0); } else//將消息發送主窗口 { PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); } break; case 1: break; case 2: break; } LeaveCriticalSection(&cs); } return 0; } DWORD CLANChat::GetCPUNumber() { SYSTEM_INFO si; GetSystemInfo(&si); return si.dwNumberOfProcessors; } /* 2013 08 15 17:15 這個函數用來發送數據,參數1為要傳送的字符串,參數2為字符串長度,3為在線列表中的ID號,這個號最好是用IP地址號。 返回值是-1或正常發送的字節數 */ int CLANChat::SendInfo(char * pbuf, int length, in_addr nListID) { SOCKET sSend = GetSocketWithIP(nListID); if(sSend == 0)//表明ID是錯的 { AfxMessageBox(_T("SendInfo Wrong ID")); return -1; } return send(sSend, pbuf, length, 0); } int OnLineNode::operator == (const OnLineNode & OLN) { if(OLN.sClient == sClient) return 1; else return 0; } void OnLineNode::AddtoChatHistory(CString & str) { vec_str_ChatHistory.push_back(str); } void OnLineNode::PostMsg(CString & str) { if(pDlg != NULL) { PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&str, 0); AddtoChatHistory(str); } } SOCKET CLANChat::GetSocketWithIP(in_addr addr) { int VecSize = g_OnLineList.size(); for(int i = 0;i < VecSize;++ i) { if(g_OnLineList[i].sa_in.sin_addr.S_un.S_addr == addr.S_un.S_addr) return g_OnLineList[i].sClient; } return 0; } vector<OnLineNode> * CLANChat::GetOnLineListPtr() { return &g_OnLineList; } OnLineNode::OnLineNode() { sClient = 0; pDlg = NULL; } // CChatDlg dialog IMPLEMENT_DYNAMIC(CChatDlg, CDialogEx) CChatDlg::CChatDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CChatDlg::IDD, pParent) { } CChatDlg::~CChatDlg() { } void CChatDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_RECVEDIT, m_RecvEditCtrl); DDX_Control(pDX, IDC_SENDEDIT, m_EditSend); } BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx) ON_MESSAGE(WM_RECVMESSAGE, &CChatDlg::OnRecvmessage) ON_BN_CLICKED(IDC_BUTTONSEND, &CChatDlg::OnBnClickedButtonsend) END_MESSAGE_MAP() // CChatDlg message handlers //參數1是一個指向和本對話框綁定的節點迭代器 afx_msg LRESULT CChatDlg::OnRecvmessage(WPARAM wParam, LPARAM lParam) { //這裡主要的操作就是對輸出窗口的操作了。 //獲取自己所對應的節點 OnLineNode * pNode = (OnLineNode *)wParam; if(!pNode->vec_str_ChatHistory.empty()) { //這裡每次只添加最後一個即可,第一次的初始化不是在這裡做的。 int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH); m_RecvEditCtrl.SetSel(nLength, nLength); m_RecvEditCtrl.ReplaceSel(pNode->vec_str_ChatHistory[pNode->vec_str_ChatHistory.size() - 1]); } return 0; } void CChatDlg::OnBnClickedButtonsend() { // TODO: Add your control notification handler code here int nLineNumber = m_EditSend.GetLineCount(); if(nLineNumber == 0) return; //CString cstmp; TCHAR buf[256]; int nTxtLength = 0; memset(buf, 0, sizeof(TCHAR) * 256); for(int i = 0;i < nLineNumber;++ i) { nTxtLength += m_EditSend.GetLine(i, buf + nTxtLength, 256); } buf[nTxtLength] = 0; //AfxMessageBox(cstmp); //第N次感慨萬惡的Unicode send(pOLN->sClient, (char *)buf, nTxtLength * sizeof(TCHAR), 0); //數據發送完之後,還要對本地的數據進行更新,也就是聊天窗口上要進行處理。 //主動向聊天記錄列表中添加一個記錄 並且發送消息到本地的接受窗口對顯示數據進行更新。 wcscat(buf, _T("\r\n")); pOLN->vec_str_ChatHistory.push_back(buf); int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH); m_RecvEditCtrl.SetSel(nLength, nLength); m_RecvEditCtrl.ReplaceSel(pOLN->vec_str_ChatHistory[pOLN->vec_str_ChatHistory.size() - 1]); }
作者:cnblogs Matrix_R