基於socket通訊的,利用MFC實現TCP通訊的C/S架構程式
1.程式說明
開發環境為VS2012,基於TCP連線的客戶端與服務端的通訊程式,服務端IP為本地網絡卡ip地址或127.0.0.1,預設埠為1234(在程式編寫過程中連線埠要大於1000,否則容易與計算機中某些程式埠衝突導致無法通訊)。
2.socket簡介
MFC類庫中,幾乎封裝了Windows Sockets的全部功能,在微軟基礎類庫中有兩個基礎類:CAsyncSocket類封裝了非同步套接字(非阻塞模式)的基本功能;CSocket類派生於CAsyncSocket類,具有序列化功能(阻塞模式)。
CAsyncSocket類實現資料傳輸步驟如下:
(1)呼叫建構函式建立套接字物件;
(2)如果建立伺服器套接字,則呼叫Bind()函式繫結本地IP和埠,然後呼叫Listen()函式監聽客戶端請求。如果請求到來,則呼叫Accept()函式響應該請求。如果建立客戶端套接字,則直接呼叫函式Connect()連線伺服器即可;
(3)呼叫Send()等功能函式進行資料傳輸與處理;
(4)關閉或銷燬套接字物件。
CSocket類實現資料傳輸步驟如下:
(1)建立CSocket類物件;
(2)如果建立伺服器端套接字,則呼叫Bind()函式繫結本地IP和埠,然後呼叫Listen()函式監聽客戶端請求。如果請求到來,則呼叫Accept()函式響應該請求。如果建立客戶端套接字,則直接呼叫函式Connect()連線伺服器即可;
(3)建立與CSocket類物件相關聯的CSocketFile類物件;
(4)建立與CSocket類物件相關聯的CArchive類物件;
(5)使用CArchive類物件在客戶端和伺服器之間進行資料傳輸;
(6)關閉或銷燬CSocket類,CSocketFile類,CArchive類三個物件。
上述兩種步驟均為使用微軟類庫進行開發流程,使用MFC通訊開發呼叫MFC封裝類庫即可。
3.相關函式簡介
(1)struct sockaddr_in
{
short sin_family;//指定地址家族即地址格式,預設為AF_INET,就是TCP/IP協議
unsigned short sin_port;//埠號
struct in_addr sin_addr;//ip地址
char sin_zero[8];//需要指定為0
}
(2)BOOL Bind(const SOCKADDR* lpSockAddr,int nSockAddrLen);
lpSockAddr指定將要繫結的伺服器地址結構
nSockAddrLen地址結構的長度
(3)BOOL Listen(int nConnectionBacklog=5);
最大監聽客戶端數,預設為5
(4)BOOL Connect(const SOCKADDR* lpSockAddr,int nSockAddrLen);
lpSockAddr 伺服器地址結構
nSockAddrLen 地址結構長度
(5)virtual int Send(const void* lpBuf,int nBufLen,int nFlags=0);
lpBuf 待發送資料地址
nBufLen 待發送資料大小
nFlags 傳送標誌
(6)virtual int Receive(const void* lpBuf,int nBufLen,int nFlags=0);
lpBuf 接收資料地址
nBufLen 接收資料大小
nFlags 標誌
4.關鍵程式碼(1)伺服器端程式碼
#define WM_SOCKET_SERVER WM_USER+1000 // CSocket_Connect_ServerDlg 對話方塊 class CSocket_Connect_ServerDlg : public CDialogEx { // 構造 public: CSocket_Connect_ServerDlg(CWnd* pParent = NULL); // 標準建構函式 // 對話方塊資料 enum { IDD = IDD_SOCKET_CONNECT_SERVER_DIALOG }; SOCKET s,s1;//定義套接字控制代碼 sockaddr_in addr,add1;//定義套接字地址結構變數 int n; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支援 // 實現 protected: HICON m_hIcon; // 生成的訊息對映函式 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() afx_msg LRESULT OnSocketServer(WPARAM wParam, LPARAM lParam); public: afx_msg void OnClickedButton1(); };
BOOL CSocket_Connect_ServerDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 將“關於...”選單項新增到系統選單中。 // IDM_ABOUTBOX 必須在系統命令範圍內。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // 設定此對話方塊的圖示。當應用程式主視窗不是對話方塊時,框架將自動 // 執行此操作 SetIcon(m_hIcon, TRUE); // 設定大圖示 SetIcon(m_hIcon, FALSE); // 設定小圖示 // TODO: 在此新增額外的初始化程式碼 n=0; addr.sin_family=AF_INET; addr.sin_port=htons(1234); addr.sin_addr.S_un.S_addr=INADDR_ANY; s=::socket(AF_INET,SOCK_STREAM,0); ::bind(s,(sockaddr*)&addr,sizeof(addr)); ::listen(s,5); GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE); GetDlgItem(IDC_STATIC)->SetWindowTextA("伺服器監聽已經啟動!"); ::WSAAsyncSelect(s,this->m_hWnd,WM_SOCKET_SERVER,FD_ACCEPT|FD_READ|FD_CONNECT);//設定非同步套接字 return TRUE; // 除非將焦點設定到控制元件,否則返回 TRUE }
afx_msg LRESULT CSocket_Connect_ServerDlg::OnSocketServer(WPARAM wParam, LPARAM lParam)
{
CString str13;
char cs[100] = {0};
//int err_log=listen(s,10);
//if (err_log != 0)
//{
// perror("listen");
// //close(s);
// exit(-1);
//}
switch (lParam)
{
case FD_ACCEPT: //連線事件
{
int lenth=sizeof(add1);
s1=::accept(s,(sockaddr*)&add1,&lenth);
n=n+1;
str13.Format("有%d客戶已經連線上了\n",n);
str13+=::inet_ntoa(add1.sin_addr);
str13+="\r\n登入\r\n";
GetDlgItem(IDC_EDIT1)->SetWindowTextA(str13);
}
case FD_READ: //讀取事件
{
CString num="";
::recv(s1,cs,100,0);
GetDlgItem(IDC_EDIT1)->GetWindowTextA(num);
num += "\r\n";
num += (LPTSTR)::inet_ntoa(add1.sin_addr);
num += "對您說:";
num += (LPTSTR)cs;
GetDlgItem(IDC_EDIT1)->SetWindowTextA(num);
}
default:
break;
}
return 0;
}
void CSocket_Connect_ServerDlg::OnClickedButton1()
{
// TODO: 在此新增控制元件通知處理程式程式碼
CString str="";
GetDlgItem(IDC_EDIT2)->GetWindowTextA(str);
if (str=="")
{
MessageBox("訊息不能為空!");
}
else
{
if (::send(s1,str.GetBuffer(1),str.GetLength(),0)!= SOCKET_ERROR)
{
GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
str += "\r\n訊息已經發送到客戶端!\r\n";
GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
}
else
{
GetDlgItem(IDC_EDIT1)->SetWindowTextA("訊息傳送失敗!\r\n");
}
}
}
(2)客戶端程式碼
#define WM_SOCKET WM_USER+100
// CSocket_ConnectDlg 對話方塊
class CSocket_ConnectDlg : public CDialogEx
{
// 構造
public:
CSocket_ConnectDlg(CWnd* pParent = NULL); // 標準建構函式
// 對話方塊資料
enum { IDD = IDD_SOCKET_CONNECT_DIALOG };
SOCKET s;
sockaddr_in addr;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支援
// 實現
protected:
HICON m_hIcon;
// 生成的訊息對映函式
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg LRESULT OnSocket(WPARAM wParam,LPARAM lParam);
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnClickedButton1();
afx_msg void OnClickedButton2();
};
BOOL CSocket_ConnectDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 將“關於...”選單項新增到系統選單中。 // IDM_ABOUTBOX 必須在系統命令範圍內。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // 設定此對話方塊的圖示。當應用程式主視窗不是對話方塊時,框架將自動 // 執行此操作 SetIcon(m_hIcon, TRUE); // 設定大圖示 SetIcon(m_hIcon, FALSE); // 設定小圖示 // TODO: 在此新增額外的初始化程式碼 this->GetDlgItem(IDC_EDIT3)->EnableWindow(FALSE); this->GetDlgItem(IDC_EDIT4)->EnableWindow(FALSE); this->GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE); s=::socket(AF_INET,SOCK_STREAM,0); //建立套接字 ::WSAAsyncSelect(s,this->m_hWnd,WM_SOCKET,FD_READ);//將套接字設定為非同步模式 return TRUE; // 除非將焦點設定到控制元件,否則返回 TRUE } void CSocket_ConnectDlg::OnClickedButton1() { // TODO: 在此新增控制元件通知處理程式程式碼 CString str,str1; int port; GetDlgItem(IDC_EDIT1)->GetWindowTextA(str); GetDlgItem(IDC_EDIT2)->GetWindowTextA(str1); if (str==""||str1=="") { MessageBox("伺服器地址或埠號不能為空"); } else { port=atoi(str1.GetBuffer(1));//將埠字串轉換為數字 addr.sin_family=AF_INET; addr.sin_addr.S_un.S_addr=inet_addr(str.GetBuffer(1)); //轉換伺服器IP地址 addr.sin_port=ntohs(port); GetDlgItem(IDC_EDIT3)->SetWindowTextA("正在連線伺服器......\r\n"); //提示使用者正在連線伺服器 if (::connect(s,(sockaddr *)&addr,sizeof(addr))) { GetDlgItem(IDC_EDIT3)->GetWindowTextA(str); str +="連線伺服器成功!\r\n"; GetDlgItem(IDC_EDIT3)->SetWindowTextA(str); GetDlgItem(IDC_EDIT4)->EnableWindow(TRUE); GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE); GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE); GetDlgItem(IDC_EDIT2)->EnableWindow(FALSE); } else { GetDlgItem(IDC_EDIT3)->GetWindowTextA(str); str += "連線伺服器失敗!請重試\r\n"; GetDlgItem(IDC_EDIT3)->SetWindowTextA(str); } } } void CSocket_ConnectDlg::OnClickedButton2() { // TODO: 在此新增控制元件通知處理程式程式碼 CString str,str1; GetDlgItem(IDC_EDIT4)->GetWindowTextA(str); if (str=="") { GetDlgItem(IDC_EDIT3)->GetWindowTextA(str1); str1 += "\r\n"; str1 += "訊息不能為空\r\n"; GetDlgItem(IDC_EDIT3)->SetWindowTextA(str1); } else { ::send(s,str.GetBuffer(1),str.GetLength(),0); //傳送訊息到指定伺服器 GetDlgItem(IDC_EDIT3)->GetWindowTextA(str1); str1 += "\r\n"; str1 += str; GetDlgItem(IDC_EDIT3)->SetWindowTextA(str1);//將傳送內容輸出到訊息框 } } LRESULT CSocket_ConnectDlg::OnSocket(WPARAM wParam,LPARAM lParam) { char cs[100]=""; //定義資料緩衝區 if (lParam==FD_READ) //如果是套接字讀取時間 { CString num=""; //定義字串變數 recv(s,cs,100,NULL); //接受資料 GetDlgItem(IDC_EDIT3)->GetWindowText(num); num += "\r\n"; num += (LPTSTR)cs; //將接收到的資料轉換為字串 GetDlgItem(IDC_EDIT3)->SetWindowText(num);//設定輸出訊息內容 } return 0; }