實現客戶端服務器端通信
實驗目的
- 瞭解相關網絡協議的基本原理和工作流程;
- 掌握使用 Socket 進行網絡通信的方法;
- 體會客戶機、服務器交互模式。
實驗任務
1.通過調用 Socket 相關函數實現網絡通信;
2. 實現界面系統和後臺通信系統的協同配合;
3. 回顧常用控件和 GDI 對象的使用方法;
4. 感受 MFC 下多線程的基本用法。
實驗設備
個人 PC,Windows 操作系統,VS2013開發環境。
實驗內容
- 分別建立服務器和客戶機,以單線程/多線程方式實現客戶機和服務器的連
接和通信,從客戶機向服務器傳輸你的個人信息和繪圖信息,在服務器上顯示
你發送的信息並按照傳輸來的信息繪製圖形。服務器和客戶端的界面佈局和功
能效果分別如圖 1 和圖 2 所示。
1.1 功能要求 - 服務器端首先運行並在某一端口(如 9990)監聽客戶端連接請求;
- 在客戶端填寫服務器端的 IP 地址和端口號,連接併發送消息;
- 填寫自己的姓名、學號、專業和繪圖相關信息向服務器發送,服務器收到消息
後,顯示出你的全部消息,並按照所發送消息的內容在繪圖區繪製指定的圖形。
客戶端
一、首先打開VS2013選擇創建一個MFC項目,我們先設計客戶端,按照對應的界面要求設計好後我們就可以添加對應的功能,我們知道,在客戶端我們需要經歷這樣幾個步驟,wsastartup,socket,connect,send。因此我們雙擊發送連接按鈕,在這裏我們做的是隻到連接的工作,具體我們看代碼如下:
void CMFCApplication5Dlg::OnBnClickedButton1()
{
UpdateData(TRUE);
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
s1= "WSAstartup failed!";
AfxMessageBox(s1);
return ;
}
sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sHost)
{
s2= "socket error!";
AfxMessageBox(s2);
WSACleanup();
return ;
}
SOCKADDR_IN addrsServ;
int i = 0;
str.Format(_T("%d"), i);
addrsServ.sin_family = AF_INET;
addrsServ.sin_port = htons(9990);
addrsServ.sin_addr.S_un.S_addr = inet_addr("192.168.191.1");
int sSreveraddlen = sizeof(addrsServ);
UpdateData(false);
retval = connect(sHost, (LPSOCKADDR)&addrsServ, sizeof(addrsServ));
if (SOCKET_ERROR == retval)
{
s3= "connect failed!";
AfxMessageBox(s3);
closesocket(sHost);
WSACleanup();
return ;
}
// TODO: 在此添加控件通知處理程序代碼
}
二、接下來我們要實現向服務端發送信息,而這個就需要我們雙擊發送按鈕,當我們點擊時,實現我們的內容向服務端的發送,因此我們就需要獲取每一個編輯框的內容。
代碼如下:
void CMFCApplication5Dlg::OnBnClickedButton2()
{
int bufsize;
CString str;
CString str1;
CString str2;
CString str3;
CString str4;
CString str5;
CString str6;
CString str7;
CString str8;
CString str9;
CString str10;
CString str11;
CString str12;
if (((CButton*)GetDlgItem(IDC_RADIO1))->GetCheck() == 1)
{
str12 = "R";
}
if (((CButton*)GetDlgItem(IDC_RADIO2))->GetCheck() == 1)
{
str12 = "E";
}
str11 = ";";
GetDlgItemText(IDC_EDIT3, str1);
GetDlgItemText(IDC_EDIT4, str2);
GetDlgItemText(IDC_EDIT5, str3);
GetDlgItemText(IDC_EDIT6, str4);
GetDlgItemText(IDC_EDIT7, str5);
GetDlgItemText(IDC_EDIT8, str6);
GetDlgItemText(IDC_EDIT9, str7);
GetDlgItemText(IDC_EDIT10, str8);
GetDlgItemText(IDC_EDIT11, str9);
GetDlgItemText(IDC_EDIT11, str10);
str = str1 + str11 + str2 + str11 + str3 + str11 + str12 + str11 + str4 + str11 + str5 + str11 + str6 + str11 + str7 + str11 + str8 + str11 + str9 + str11 + str10;
USES_CONVERSION;
char *a = T2A(str.GetBuffer(0));
str.ReleaseBuffer();
sprintf_s(buf, "%s", a);
bufsize = send(sHost, buf, strlen(buf), 0);
if (SOCKET_ERROR == bufsize)
{
s5 = "recv failed!";
AfxMessageBox(s5);
closesocket(sHost);
WSACleanup();
return;
}
// TODO: 在此添加控件通知處理程序代碼
}
這樣我們就完成了客戶端的實現。
服務端
一、我們首先也是設計頁面,其次按照書上的步驟進行操作,一些必備的函數,
調用 WSAStartup()加載 Socket 庫進行初始化;
調用 Socket()函數創建基於 TCP 的流式套接字用於主線程的請求監聽;
設置服務器端的本機地址:
你的 sockaddr_in 結構.sin_family = AF_INET;
你的 sockaddr_in 結構.sin_port = htons(你的監聽端口,比如 9990);
你的 sockaddr_in 結構.s_addr = htonl(INADDR_ANY);
調用 bind()函數將 Socket 與地址進行綁定;
調用 listen()函數設置套接字爲監聽工作狀態。
代碼如下:
void CMFCApplication6Dlg::OnBnClickedButton1()
{
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
s1 = "WSAstartup failed!";
return ;
}
s1 = "WSAstartup successed!";
sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sServer)
{
s2 = "socket error!";
WSACleanup();
return ;
}
s2 = "socket success!";
SOCKADDR_IN addrsServ;
int i = 0;
str.Format(_T("%d"), i);
addrsServ.sin_family = AF_INET;
addrsServ.sin_port = htons(9990);
addrsServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int sSreveraddlen = sizeof(addrsServ);
retval = bind(sServer, (const struct sockaddr *)&addrsServ, sizeof(SOCKADDR_IN));
if (SOCKET_ERROR == retval)
{
s3 = "bind failed!";
closesocket(sServer);
WSACleanup();
return ;
}
s3 = "bind successed!";
retval = listen(sServer, 1);
if (SOCKET_ERROR == retval)
{
s4 = "listen failed!";
closesocket(sServer);
WSACleanup();
return ;
}
s4 = "listen successed!";
editt.SetWindowText(s1+"\r\n"+s2+"\r\n"+s3+"\r\n"+s4 );
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)this, 0, NULL);
if (hThread == NULL)
{
CString ss;
ss="Create Thread Failed!! !";
AfxMessageBox(ss);
closesocket(sServer);
WSACleanup();
return;
}
// TODO: 在此添加控件通知處理程序代碼
}
二、這裏最重要的是添加一個線程,這樣當我們執行accept時我們即使產生阻塞也不會影響我們,因爲我們將這個進程放在線程裏,這樣會提高我們的用戶體驗。
具體我們看代碼:
DWORD WINAPI ClientThread(LPVOID lpParameter)
{
CMFCApplication6Dlg * dlg = (CMFCApplication6Dlg*)lpParameter;
SOCKADDR_IN addrcClient;
int cClientaddlen = sizeof(addrcClient);
sClient = accept(sServer, (struct sockaddr *)&addrcClient, &cClientaddlen);
if (INVALID_SOCKET == sClient)
{
s5 = "accept failed!";
AfxMessageBox(s5);
closesocket(sServer);
closesocket(sClient);
WSACleanup();
return 0;
}
s5 = "accept successed!";
AfxMessageBox(s5);
while (1)
{
memset(buf, 0x00, 100);
int bufsize;
retval = recv(sClient,buf,100,0);
if (SOCKET_ERROR == retval)
{
s5 = "recv failed!";
AfxMessageBox(s5);
closesocket(sServer);
closesocket(sClient);
WSACleanup();
return 0;
}
rec = CString(buf);
SYSTEMTIME tm;
GetLocalTime(&tm);
char sDateTime[30];
sprintf(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond);
CString year(sDateTime);
dlg->editstr = dlg->editstr + s1 + "\r\n" + s2 + "\r\n" + s3 + "\r\n" + s4 + "\r\n" + s5+"\r\n\r\n" +year+ "\r\n\r\n" + rec;
dlg->editt.SetWindowText(dlg->editstr);
int pos;
CString rr;
for (int i = 0; i < 11; i++)
{
pos = rec.Find(';');
rr = rec.Mid(pos + 1, rec.GetLength());
re[i] = rec.Left(pos);
rec = rr;
}
CWnd * pWnd = dlg->GetDlgItem(IDC_STATIC1);
CDC * dc = pWnd->GetDC();//注意這裏獲取了新的 dc
pWnd->Invalidate();
pWnd->UpdateWindow();
CRect rectView;
CRgn rgn;
dlg->GetDlgItem(IDC_STATIC1)->GetClientRect(&rectView);
rgn.CreateRectRgn(rectView.left, rectView.top, rectView.right, rectView.bottom);
dc->SelectClipRgn(&rgn);
CPen pNewPen(PS_SOLID, 2, RGB(0, 0, 0));
CPen *pOldPen;
pOldPen = dc->SelectObject(&pNewPen);
CBrush pNewBrush1(RGB(255, 255, 0));
CBrush *pOldBrush;
if (re[3] == 'R')
{
CRect rect1(_ttoi(re[4]), _ttoi(re[5]), _ttoi(re[6]), _ttoi(re[7]));
CBrush pNewBrush1(RGB(_ttoi(re[8]), _ttoi(re[9]), _ttoi(re[10])));
dc->Rectangle(&rect1);
pOldBrush = dc->SelectObject(&pNewBrush1);
dc->Rectangle(&rect1);
dc->SelectObject(pOldPen);//恢復原有的筆
dc->SelectObject(pOldBrush);
dc->DeleteDC();
}
if (re[3] == 'E')
{
CRect rect1(_ttoi(re[4]), _ttoi(re[5]), _ttoi(re[6]), _ttoi(re[7]));
CBrush pNewBrush1(RGB(_ttoi(re[8]), _ttoi(re[9]), _ttoi(re[10])));
dc->Ellipse(&rect1);
pOldBrush = dc->SelectObject(&pNewBrush1);
dc->Ellipse(&rect1);
dc->SelectObject(pOldPen);//恢復原有的筆
dc->SelectObject(pOldBrush);
dc->DeleteDC();
}
break;
}
return 0;
}
這裏我們不僅實現了從客戶端接受信息還可以將其顯示在我們的對應的編輯框中,其此,我們還可以根據客戶端的請求進行畫圖,這裏請參考本博主的其他文章有詳細介紹關於畫圖及其移動的相關介紹,這裏我們要注意的是我們需要用“;”來當做分隔符,你也可以選擇別的進行這取決你了。
注:本博客純原創,如需轉載請告訴博主哦!