1. 程式人生 > >如何基於TCP/IP協議進行MFC Socket網路通訊程式設計

如何基於TCP/IP協議進行MFC Socket網路通訊程式設計

MFC Socket網路通訊程式設計

       最近因為一個專案需要進行區域網絡通訊,向工作單位的軟體工程師請教了一下需要用到哪些知識,然後博主就自學了一遍windows網路通訊程式設計原理,然後就在網上找了一大堆例子,但實際執行效果並不佳,花了大概一週多的時間總算是把網路通訊程式給跑起來了(PS:雖然時間比較長,但對於一個新手程式設計師來說,算是不錯的,哈哈。。。)

宣告:本程式是借鑑於此博文的原始碼(http://blog.csdn.net/lovey_carolin/article/details/6032195)。

TCP流式套接字的程式設計步驟:

伺服器端程式:

1、載入套接字型檔 
2、建立套接字(socket)。 
3、將套接字繫結到一個本地地址和埠上(bind)。 
4、將套接字設為監聽模式,準備接收客戶請求(listen)。 
5、等待客戶請求到來;當請求到來後,接受連線請求,返回一個新的對應於此次連線的套接字(accept)。 
6、用返回的套接字和客戶端進行通訊(send/recv)。 
7、返回,等待另一客戶請求。 
8、關閉套接字。

客戶端程式:

1、載入套接字型檔 
2、建立套接字(socket)。 
3、向伺服器發出連線請求(connect)。 
4、和伺服器端進行通訊(send/recv)。 
5、關閉套接字。

以上是Socket網路程式設計基本步驟,必須熟知!

下面就講講如何在MFC中進行Socket 程式設計。

首先新建一個基於對話方塊的工程取名為sFile,此工程作為伺服器端。刪除對話方塊中的中間程式碼和OK按鈕。在新建的對話方塊中新增一個List Box控制元件作為顯示視窗,並新增一個Edit control控制元件作為輸入視窗,然後增加一個傳送按鈕:IDC_BtnSend。在List Box控制元件上右鍵點選選擇——新增變數,將變數定義為控制元件型別並取名為m_listwords。

然後再建一個客戶端對話方塊取名為cFile,刪除對話方塊中的中間程式碼和OK按鈕。在新建的對話方塊中新增一個List Box控制元件作為顯示視窗,並新增一個Edit control控制元件作為輸入視窗,然後增加兩個按鈕:一個為傳送按鈕IDC_BtnSend,另一個為連線按鈕IDC_BtnConnect。在List Box控制元件上右鍵點選選擇——新增變數,將變數定義為控制元件型別並取名為m_listwords。另外需要新增一個IP Adress control控制元件,然後按新增變數的方法將其關聯為控制元件型別,並取名為m_ip。

伺服器端具體步驟如下:
1、 在sFileDlg.h中新增public:void update(CString s);

                                       private: CEdit* send_edit;

       void CString2Char(CString str, char ch[]);  //此函式為字元格式轉換函式,後面會講到
2、 新建兩個socket套接字: SOCKET listen_sock;
                                                 SOCKET sock;

      在sFileDlg.h中新增 CString IP;  //定義為全域性變數

      並宣告執行緒函式  UINT server_thd(LPVOID p);

3、在OnInitDialog()函式中新增:     

send_edit = (CEdit *)GetDlgItem(IDC_EDIT1);
send_edit->SetFocus();
char name[128];
hostent* pHost;
gethostname(name, 128);//獲得主機名 
pHost = gethostbyname(name);//獲得主機結構 
IP = inet_ntoa(*(in_addr *)pHost->h_addr);
update(_T("本伺服器IP地址:") + IP);
AfxBeginThread(server_thd, NULL);//建立執行緒
4、新增函式update():
  void CSFileDlg::update(CString s)
{
 m_listwords.AddString(s);
}
5、新增執行緒函式server_thd():
  UINT server_thd(LPVOID p)//執行緒要呼叫的函式
{
WSADATA wsaData;
WORD wVersion;
wVersion = MAKEWORD(2, 2);
WSAStartup(wVersion, &wsaData);
// WSAStartup(0x0202, &wsaData);
SOCKADDR_IN local_addr;
SOCKADDR_IN client_addr;
int iaddrSize = sizeof(SOCKADDR_IN);
int res;
char msg[1024];
CsFileDlg * dlg = (CsFileDlg *)AfxGetApp()->GetMainWnd();
char ch_ip[20];
CString2Char(IP, ch_ip);//注意!這裡呼叫了字元格式轉換函式,此函式功能:CString型別轉換為Char型別,實現程式碼在後面新增
//local_addr.sin_addr.s_addr = htonl(INADDR_ANY);//獲取任意IP地址
local_addr.sin_addr.s_addr=inet_addr(ch_ip);
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(8888);
if ((listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)//建立套接字
{
dlg->update(_T("建立監聽失敗"));
}
if (bind(listen_sock, (struct sockaddr*) &local_addr, sizeof(SOCKADDR_IN)))//繫結套接字
{
dlg->update(_T("繫結錯誤"));
}
listen(listen_sock, 1);
if ((sock = accept(listen_sock, (struct sockaddr *)&client_addr, &iaddrSize)) == INVALID_SOCKET)//接收套接字
{
dlg->update(_T("accept 失敗"));
}
else
{
CString port;
port.Format(_T("%d"), int(ntohs(client_addr.sin_port)));
dlg->update(_T("已連線客戶端:") + CString(inet_ntoa(client_addr.sin_addr)) + "  埠:" + port);
}
////////////接收資料
while (1)

if ((res = recv(sock, msg, 1024, 0)) == -1)
{
dlg->update(_T("失去客戶端的連線"));
break;
}
else
{
msg[res] = '\0';
dlg->update(_T("client:") + CString(msg));
}
}
return 0;

6、新增按鈕傳送函式(在對話方塊中右鍵點選剛新增的按鈕彈出選單,然後選擇新增事件響應函式這一欄,將函式命名為OnSending)

     事件響應程式碼如下:

void CsFileDlg::OnSending()
{
// TODO: Add your control notification handler code here
CString s;
char  msg[1024];
send_edit->GetWindowTextW(s);
CString2Char(s, msg); //  注意!這裡呼叫了字元格式轉換函式,此函式功能:CString型別轉換為Char型別,實現程式碼後面新增
if (send(sock, msg, strlen(msg), 0) == SOCKET_ERROR)
{
show_edit->ReplaceSel(_T("傳送失敗"));
m_listwords.SetWindowTextW(_T("傳送失敗"));
}
else if (s == "")
{
MessageBox(_T("請輸入資訊"));
}
else
{
s = msg;
//update(s);//訊息上屏,清空輸入,並重獲焦點
//show_edit->ReplaceSel(_T("server:") + s);//訊息上屏,清空輸入,並重獲焦點
m_listwords.AddString(_T("server:") + s);
send_edit->SetWindowText(_T(""));
m_listwords.SetFocus();
}
}

void CString2Char(CString str, char ch[])//此函式就是字元轉換函式的實現程式碼
{
int i;
char *tmpch;
int wLen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);//得到Char的長度
tmpch = new char[wLen + 1];                                             //分配變數的地址大小
WideCharToMultiByte(CP_ACP, 0, str, -1, tmpch, wLen, NULL, NULL);       //將CString轉換成char*


for (i = 0; tmpch[i] != '\0'; i++) ch[i] = tmpch[i];
ch[i] = '\0';
}

客戶端具體步驟如下:
1、 在cFileDlg.h中新增 public:void update(CString s);

                                       private: CEdit* send_edit;

       void CString2Char(CString str, char ch[]);  
2、 新建一個socket套接字: SOCKET sock;

      宣告執行緒函式  UINT server_thd(LPVOID p);

3、在OnInitDialog()函式中新增:     

send_edit = (CEdit *)GetDlgItem(IDC_EDIT1);
4、新增函式update():
  void CcFileDlg::update(CString s)
{
 m_listwords.AddString(s);
}

5、新增執行緒函式server_thd():

UINT recv_thd(LPVOID p)
{
int res;
char msg[1024];
//CString s;
CcFileDlg * dlg = (CcFileDlg *)AfxGetApp()->GetMainWnd();
////////////接收資料
while (1)
{
if ((res = recv(sock, msg, 1024, 0)) == -1)//接收伺服器的資料
{
dlg->update(_T("失去連線"));
break;
}
else
{
msg[res] = '\0';
dlg->update(_T("server:") + CString(msg));
}
}
//closesocket(sock);
return 0;
}

6、新增連線按鈕事件響應函式:在連線按鈕上右鍵選擇新增事件響應這一欄,將函式取名OnConnecting,如下

void CcFileDlg::OnConnecting()
{
// TODO: Add your control notification handler code here
WSADATA wsaData;
SOCKADDR_IN server_addr;
memset(&server_addr, 0, sizeof(server_addr));
WORD wVersion;
wVersion = MAKEWORD(2, 2);
WSAStartup(wVersion, &wsaData);
// WSAStartup(0x0202, &wsaData);
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
update(_T("create socket error !!!"));
}
//CString ip;
//ip_edit->GetWindowTextW(ip);//取得伺服器的IP地址
//server_addr.sin_addr.s_addr = inet_addr((LPSTR)(LPCSTR)ip.GetBuffer());
BYTE nArrIP[4];
m_ip.GetAddress(nArrIP[0], nArrIP[1], nArrIP[2], nArrIP[3]);
CString str;
str.Format(_T("%d.%d.%d.%d"), nArrIP[0], nArrIP[1], nArrIP[2], nArrIP[3]);
ip_edit->SetWindowTextW(str);
char cp[50];
CString2Char(str, cp);
server_addr.sin_addr.s_addr = inet_addr(cp);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
bind(sock, (SOCKADDR*)&server_addr, sizeof(SOCKADDR));
if (connect(sock, (struct sockaddr *) &server_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
update(_T("連線失敗"));
}
else
{
//show_edit->SetWindowText(_T(""));
update(_T("連線成功"));
btnconn->EnableWindow(FALSE);//按鈕變灰
AfxBeginThread(recv_thd, NULL);
}
}

7、添加發送按鈕事件響應函式:在傳送按鈕上右鍵選擇新增事件響應這一欄,將函式取名OnSending,如下

void CcFileDlg::OnSending()
{
// TODO: Add your control notification handler code here
//CString s;
//char * msg;
//send_edit->GetWindowTextW(s);
//msg = (LPSTR)(LPCTSTR)s;
//CString2Char(s, msg);
CString s;
char msg[1024];
send_edit->GetWindowTextW(s);
CString2Char(s, msg);
if (send(sock, msg, strlen(msg), 0) == SOCKET_ERROR)
{
update(_T("傳送失敗"));
}
else if (s == "")
{
MessageBox(_T("請輸入資訊"));
}
else
{
s = msg;
update(_T("client:") + s);//訊息上屏,清空輸入,並重獲焦點
send_edit->SetWindowText(_T(""));
send_edit->SetFocus();
}
/*CString s;
char * msg;
send_edit->GetWindowText(s);
msg = (char*)s.GetBuffer(s.GetLength());
if (send(sock, msg, strlen(msg), 0) == SOCKET_ERROR)
{
show_edit->ReplaceSel(_T("傳送失敗/r/n"));
}
else if (s == "")
{
MessageBox(_T("請輸入資訊"));
}
else
{
show_edit->ReplaceSel(_T("client:") + s + "/r/n");//訊息上屏,清空輸入,並重獲焦點
send_edit->SetWindowText(_T(""));
send_edit->SetFocus();
}*/
}

8、最後新增字元格式轉換函式
/*
* 函式名: CString2Char
* 引數1: CString str                 待轉換字串
* 引數2: char ch[]                       轉換後將要儲存的位置
* 將Unicode下的CString轉換為char*
*/
void CString2Char(CString str, char ch[])
{
int i;
char *tmpch;
int wLen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);//得到Char的長度
tmpch = new char[wLen + 1];                                             //分配變數的地址大小
WideCharToMultiByte(CP_ACP, 0, str, -1, tmpch, wLen, NULL, NULL);       //將CString轉換成char*


for (i = 0; tmpch[i] != '\0'; i++) ch[i] = tmpch[i];
ch[i] = '\0';
}

實驗結果如圖: