1. 程式人生 > >基於socket通訊的,利用MFC實現TCP通訊的C/S架構程式

基於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;
 }