以前已經寫過了TCP服務器原理及經過visual studio 驗證 SOCKET編程:搭建一個TCP服務器,這裏咱們搭建一個TCP客戶端界面並對各功能進行實現。設計效果以下:html
新建一個工程,環境有問題的請參考visual2017專業版MFC編程環境搭建及第一個MFC程序的建立編程
在資源文件——TCPclient.rc服務器
中打開Dialog,雙擊IDD_TCPCLIENT_DIALOG能夠看到對話框異步
刪除掉對話框上原有的控件,添加兩個靜態文本(STATIC),四個編輯框(EDIT)、兩個命令按鈕(BUTTON),佈局以下:socket
鼠標選中控件,在屬性對話框中設置其屬性,以下表所示函數
修改完成後界面如圖所示:佈局
首先,在TCPclientDlg.cpp下的BOOL CTCPclientApp::InitInstance()中添加以下代碼:post
BOOL CTCPclientDlg::OnInitDialog() { CDialogEx::OnInitDialog(); ... // TODO: 在此添加額外的初始化代碼 GetDlgItem(IDC_SENDTEXT)->EnableWindow(false); GetDlgItem(IDC_SEND)->EnableWindow(false); GetDlgItem(IDC_TEXT)->EnableWindow(false); return TRUE; // 除非將焦點設置到控件,不然返回 TRUE }
屏蔽發送消息的功能,避免錯誤的發生。this
而後,對套接字進行初始化,在TCPclientDlg.h中的class CTCPclientDlg : public CDialogEx的public添加定義url
SOCKET s; sockaddr_in addr;
在TCPclientDlg.cpp下的BOOL CTCPclientApp::InitInstance()中添加
s = ::socket(AF_INET, SOCK_STREAM, 0);
完成套接字初始化
功能實現,爲鏈接按鈕添加消息響應函數
並編寫事件處理函數以下所示
void CTCPclientDlg::OnConnect() { // TODO: 在此添加控件通知處理程序代碼 CString str, str1; int port; GetDlgItem(IDC_ADDR)->GetWindowText(str); GetDlgItem(IDC_PORT)->GetWindowText(str1); if (str == "" || str1 == "") { MessageBox("服務器地址或端口不能爲NULL"); } else { port = atoi(str1.GetBuffer(1)); addr.sin_family = AF_INET; addr.sin_addr.S_un.S_addr = inet_addr(str.GetBuffer(1)); addr.sin_port = ntohs(port); GetDlgItem(IDC_TEXT)->SetWindowText("正在鏈接服務器......\r\n"); if (::connect(s, (sockaddr*)&addr, sizeof(addr))) { GetDlgItem(IDC_TEXT)->GetWindowText(str); str += "鏈接服務器成功!\r\n"; GetDlgItem(IDC_TEXT)->SetWindowText(str); GetDlgItem(IDC_SENDTEXT)->EnableWindow(true); GetDlgItem(IDC_SEND)->EnableWindow(true); GetDlgItem(IDC_ADDR)->EnableWindow(false); GetDlgItem(IDC_PORT)->EnableWindow(false); } else { GetDlgItem(IDC_TEXT)->GetWindowText(str); str += "鏈接服務器失敗!請重試\r\n"; GetDlgItem(IDC_TEXT)->SetWindowText(str); } } }
當客戶端與服務器鏈接成功以後,用戶便可發送消息到服務器了,爲發送按鈕添加消息訪問函數。
void CTCPclientDlg::OnSend() { // TODO: 在此添加控件通知處理程序代碼 // TODO: Add your control notification handler code here CString str, str1; GetDlgItem(IDC_SENDTEXT)->GetWindowText(str); if (str == "") { GetDlgItem(IDC_TEXT)->GetWindowText(str1); //str1+="\r\n"; str1 += "消息不能爲空\r\n"; GetDlgItem(IDC_TEXT)->SetWindowText(str1); } else { ::send(s, str.GetBuffer(1), sizeof(str), 0); GetDlgItem(IDC_TEXT)->GetWindowText(str1); str1 += "\r\n"; str1 += str; GetDlgItem(IDC_TEXT)->SetWindowText(str1); } }
代碼中,經過調用send將消息發送到指定服務器,並將改消息顯示在本地顯示框中,運行結果如圖所示:
做爲客戶端,還應具備接受並顯示服務器所發送消息的功能,,這裏採用異步套接字模式實現該功能。在初始化函數中調用WSAAsyncSelect()函數以下:
::WSAAsyncSelect(s, this->m_hWnd, WM_SOCKET, FD_READ);
其中WM_SOCKET定義爲
#define WM_SOCKET WM_USER+100
並在class CTCPclientDlg : public CDialogEx的Protect中添加
afx_msg LRESULT OnSocket(WPARAM wParam, LPARAM lParam);
添加消息響應函數成功後,還須要在消息映射表中將消息與響應相關聯。在TCPclientDlg.cpp的BEGIN_MESSAGE_MAP中添加代碼以下
ON_MESSAGE(WM_SOCKET, &CTCPclientDlg::OnSocket)
最後編寫自定義消息響應函數OnSocket(),實現套接字事件處理:
LRESULT CTCPclientDlg::OnSocket(WPARAM wParam, LPARAM lParam) { char cs[100] = { 0 }; if (lParam == FD_READ) { CString num = ""; recv(s, cs, 100, 0); GetDlgItem(IDC_TEXT)->GetWindowText(num); num += "\r\n服務器說:"; num += (LPTSTR)cs; GetDlgItem(IDC_TEXT)->SetWindowText(num); } return 0; }
因爲本實例,僅處理套接字的讀取事件,因此使用if (lParam == FD_READ)。若是須要處理的套接字事件比較多,,那麼應該在代碼上進行分類判斷。
至此,基本上完成了客戶端應有的功能。在客戶端程序中,須要注意鏈接服務器以前,必定要知道服務器的IP地址等信息,不然,程序將沒法正確鏈接到服務器。