MFC实现的Socket通信

    过五一准备休息两天,正好有朋友要来西安,我也能在西安周边转转,毕竟在这上大学居然还没有好好逛过。所以放假之前的一个晚上我发篇文章。

    我觉得MFC的东西挺难说的,好像代码没有一个头,也没有一个尾,不知道从何讲起。所以我只说说socket的部分,当然包括服务端和客户端。

    这是服务端的样子:

    点击查看原图

    大家先去 件中 把代码下下来,否则看文章是看不懂的。服务端代码,打开类视图,找到ListenThreadFunc函数。这是我们的线程函数,socket代码在该线程里执行。

DWORD WINAPI ListenThreadFunc(LPVOID Lparam)
{
	Cxads_PCServerDlg * pServer = (Cxads_PCServerDlg *)Lparam;
	if (INVALID_SOCKET == (pServer->m_SockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)))
	{
		AfxMessageBox(_T("建立socket失败"));
		return 0;
	}
	sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_addr.s_addr = htonl(INADDR_ANY);
	service.sin_port = htons(pServer->m_ServicePort);
	if (0 != bind(pServer->m_SockListen,(sockaddr *)&service,sizeof(sockaddr_in)))
	{
		AfxMessageBox(_T("绑定端口失败"));
		return 0;
	}
	if (0 != listen(pServer->m_SockListen,5))
	{
		AfxMessageBox(_T("监听端口失败"));
		return 0;
	}

	//提示建立socket成功
	pServer->EnableWindow(IDC_BUTTONEND,TRUE);
//	pServer->EnableWindow(IDC_BUTTONSEND,TRUE);
	pServer->EnableWindow(IDC_BUTTONSTART,FALSE);
	pServer->SetRevBoxText(_T("服务启动成功,开始监听端口"));
	//进入循环,监听端口
	while (TRUE)
	{
		if (socket_Select(pServer->m_SockListen,100,TRUE))
		{
			sockaddr_in clientAddr;
			int iLen = sizeof(sockaddr_in);
			SOCKET accSock = accept(pServer->m_SockListen,(struct sockaddr *)&clientAddr,&iLen);
			if (accSock == INVALID_SOCKET)
			{
				continue;
			}
			//将节点加入链中
			CClientItem tItem;
			tItem.m_ClientSocket = accSock;
			tItem.m_strIp = inet_ntoa(clientAddr.sin_addr); //IP地址
			tItem.m_pMainWnd = pServer;
			int idx = pServer->m_ClientArray.Add(tItem); //idx是第x个连接的客户端
			tItem.m_hThread = CreateThread(NULL,0,ClientThreadProc, //创建一个线程并挂起:CREATE_SUSPENDED
				&(pServer->m_ClientArray.GetAt(idx)),CREATE_SUSPENDED,NULL); 
			pServer->m_ClientArray.GetAt(idx).m_hThread = tItem.m_hThread; 
			//等把hThread加入了节点,才开始执行线程,如下
			ResumeThread(tItem.m_hThread);
			pServer->SetRevBoxText(tItem.m_strIp + _T("上线"));
			Sleep(100);
		}
	}
}

    Cxads_PCServerDlg是我们对话框程序的类,我们传进来的参数就是这个类对象。pServer指向该对象。

    1.pServer->m_SockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))

    socket函数初始化一个socket。socket是什么?socket相当于通信的一个管道,m_SockListen是一个监听的socket。我们一会就要监听这根"管道",看是否有客户端连接。socket()中的参数像我这样写,代表这是个TCP连接。TCP连接和UDP连接的区别就是,TCP是要双方建立连接后才能通信,就想打电话;而UDP是单方面就能发送信息,就想发短信。

    2.bind(pServer->m_SockListen,(sockaddr *)&service,sizeof(sockaddr_in))

    bind函数是绑定一个端口和IP地址。第一个参数是刚才新建的监听socket,第二个参数是一个sockaddr_in结构体,里面保存着IP地址和端口。因为我们这是服务端,所以保存的IP地址和端口是自己的,htonl(INADDR_ANY)就是将IP地址绑定为任意,这样你的IP可以是127.0.0.1,也可以是192.168.x.x,也可以是你的外网IP。htons(pServer->m_ServicePort)是要监听的IP,我这里是从输入框获取的,你也可以使用固定的比如htons(8260)。

    3.listen(pServer->m_SockListen,5)

    开始监听了。5代表等待连接队伍的最大长度,一般都是5就行。

    4.

    进入while循环,大家可以看到这是个死循环。while(TRUE)永远不会退出去。while中间只有个if语句,socket_Select(pServer->m_SockListen,100,TRUE)是一个select选择模型,作用是向socket中看一眼,如果有信息则返回TRUE,如果没有信息则返回FALSE。有信息代表有客户端来连接了,于是我们进入if语句。

    5.SOCKET accSock = accept(pServer->m_SockListen,(struct sockaddr *)&clientAddr,&iLen);

    大家可以看到我又建了一个socket,不是刚才那个了。accept函数就返回一个socket,这个socket就是和该客户端通信的"管道"。传入的参数和bind类似,只是第二个变成得到客户端的IP与端口了。

    6.

    之后有一个将节点加入链表的过程。因为我们连接服务端的客户端不止一个,所以我们要将每一个客户端的IP、SOCKET和相关信息加入链表,以供以后使用。而且大家可以看到,我们这个while里面又新开了一个线程,这个线程的作用就是和该客户端通信。

    7.

    休息100毫秒后while循环继续执行,等待下一个客户端连接。


    这就是服务端socket的代码,通信的部分我客户端里介绍,服务端和客户端基本是一样的,大家可以自己看代码比较。

    打开客户端,找到ConnectSocket方法,这是我们从socket中获取信息的一个方法。

BOOL Cxads_PCClientDlg::ConnectSocket(Cxads_PCClientDlg * pClient)
{
	m_ClientSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (NULL == m_ClientSock)
	{
		MessageBox(_T("创建socket失败"));
		return FALSE;
	}
	sockaddr_in sa;
	sa.sin_family = AF_INET;
	CString strIp;
	DWORD dPort = GetDlgItemInt(IDC_EDITPORT);
	GetDlgItemText(IDC_IPADDRESS,strIp);
	if (strIp == _T("0.0.0.0") || (dPort >= 65535 && dPort < 1024) || dPort == 0)
	{
		MessageBox(_T("请输入正确IP地址或端口"));
		return FALSE;
	}
	sa.sin_port = htons(dPort);
	char szIpAdd[32];
	USES_CONVERSION; //定义后才能使用T2A
	sprintf_s(szIpAdd,32,"%s",T2A(strIp));
	sa.sin_addr.S_un.S_addr = inet_addr(szIpAdd);
	if (SOCKET_ERROR == connect(m_ClientSock,(sockaddr *)&sa,sizeof(sa)))
	{
		MessageBox(_T("连接客户端错误,请检查你填写的IP和端口是否错误"));
		return FALSE;
	}

	pClient->SetRevBoxText(_T("连接服务器成功"));
	pClient->EnableWindow(IDC_BUTTONSTOP,TRUE);
	pClient->EnableWindow(IDC_BUTTONCONNECT,FALSE);
	isServerOn = TRUE;
	OnEnChangeEditsendbox();

	CString strMsg;
	while (TRUE)
	{
		if (socket_Select(m_ClientSock,100,TRUE))
		{
			char szMsg[MAX_BUFF] = {0};
			int iRead = recv(m_ClientSock,szMsg,MAX_BUFF,0);
			if (iRead > 0)
			{
				strMsg = szMsg;
				pClient->SetRevBoxText(strIp + _T(">>") + strMsg);
			} 
			else
			{
				pClient->SetRevBoxText(_T("已断线,请重新连接"));
				isServerOn = FALSE;
				return TRUE;
			}
		}
	}
	return TRUE;
}

    1.

    同服务端一样,调用socket()初始化socket.

    2.connect(m_ClientSock,(sockaddr *)&sa,sizeof(sa))

    连接服务端。和服务端的accept函数类似,第二个参数传入sockaddr_in结构体,里面保存着服务端的IP地址和端口。如果连接成功,则返回0。

    3.

    进入一个while循环。这个while虽然也是while(TRUE),但不是一个死循环,中间有break可以使while循环退出。while中仍然用到socket_Select()函数,作用也是向socket中看一眼,如果有信息则返回T,否则返回F。有信息代表从服务端发送来了内容。

    4.int iRead = recv(m_ClientSock,szMsg,MAX_BUFF,0);

    recv()函数接受信息,第一个参数传入socket,第二个参数传入接受的信息保存的字符串数组,第三个传入数组长度,第四个填0即可。返回值是接受信息的字节数,如果=0说明对方不在线了。


    这个方法告一段落。有人问了,那怎么发送信息呢?别急,在类视图中找到OnBnClickedButtonsend()方法。

void Cxads_PCClientDlg::OnBnClickedButtonsend()
{
	// TODO: 在此添加控件通知处理程序代码
	USES_CONVERSION;
	char szBuf[256] = {0};
	CString strGetMsg;
	int iWrite;
	GetDlgItemText(IDC_EDITSENDBOX,strGetMsg);
	strcpy_s(szBuf,T2A(strGetMsg));
	iWrite = send(m_ClientSock,szBuf,256,0);
	if(SOCKET_ERROR == iWrite){
		SetRevBoxText(_T("发送错误"));
	}
	SetRevBoxText(_T("我自己 >>") + strGetMsg);
	SetDlgItemText(IDC_EDITSENDBOX,_T(""));
	return; 
}

    这是点击了发送按钮执行的代码。只有一个点,就是send()函数。

    iWrite = send(m_ClientSock,szBuf,256,0);

    与recv类似,发送保存在szBuf中的内容到m_ClientSock。


    我只讲了这两个程序中很少的代码,跳过了一些很重要的内容:异步IO模型、多线程等,大家自己看看程序,以后我再把这些内容完整的讲一下。

    附件:MFC Socket.rar

赞赏

喜欢这篇文章?打赏1元

评论

回复

感谢作者

agui 回复

谢谢好心人

55yicheng 回复

万分感谢楼主,自己都愁死了工作要做这个做不来 刚接触c++

a 回复

确实厉害...楼主有没有c++网络编程方面的书推荐一下

嘻嘻嘻 回复

想问一下 这个是TCP通信还是UDP?

胖子 回复

你好,那个EnableWindow和SendClientMsg显示无法解析的外部符号,我在依赖项里加了user32.lib还不行,请问是怎么回事

寂静山林0521 回复

麻烦楼主分享一下源代码,非常感谢!809553343@qq.com

寂静山林0521 回复

找到了找到了,谢谢楼主 !

Wuliven 回复

看了楼主的程序,觉得学习了。但是我想问问楼主如果在这个基础上想实现文件传输应该怎么做了,我试着写了函数但是并不成功,每次传输的时候都会直接显示在editbox里面,所以想请教楼主传文件的思想是什么,总觉得自己的理解有偏差

wrong answer 回复

如何实现链接外网。socket

asdasd 回复

为什么客户端2发送给服务器的消息客户端1也会收到。
初学者不知道怎么改为客户端只能和服务器进行交流

David 回复

附件在哪里?没看到

huazai 回复

我建立的基于dialog的MFC里面都没有
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;

phithon 回复

@huazai:那是我自己写的类,你创建的项目里肯定没有。

huazai 回复

错误 1 error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 C:\Users\Administrator\Desktop\MFC Socket\xads_PCClient\xads_PCClient\LINK
这是怎么回事啊!球快速解答。。。。

phithon 回复

@huazai:常见错误,自行百度。

WGC575 回复

谢谢分享,我自己再加上服务器信息的多客户端同步就可以完成作业了。

kiki 回复

学习下

captcha