MFC 基于SOCKET 实现服务端客户端一对多

这是一个关于C++编程的示例,详细介绍了如何创建一个能够处理多个客户端连接的服务器,并且客户端可以与服务器进行数据交换。代码实现了基于socket的异步非阻塞通信,同时处理了UNICODE和多字符集的字符转换问题,确保在不同字符集环境下数据传输的正确性。

注意:本篇文章源代码贴得很多,所以有点长,如果觉得阅读不方便,可以直接复制源代码到你的程序里去,也可以直接下载源码。后面整合了socket方法写了一个类,源码下载里没有。

源码下载:https://download.csdn.net/download/ya4599/15653688

1.程序说明

开发环境是VS2008 ,基于socket实现一个服务端和多个客户端通信。

服务端:

指定端口,关闭和开启服务器监听;

将已连接客户端显示在客户端CListBox列表;

监听FD_ACCEPT|FD_READ|FD_CLOSE事件;

可选择和特定的已连接客户端通信;

通信数据和系统信息滚动显示。

客户端:

指定服务器IP、端口,关闭和连接服务器;

监听FD_CONNECT|FD_READ|FD_CLOSE事件,

顺便做了一个测试程序,测试程序作用是循环打开指定个数的客户端和关闭客户端。

服务端界面截图如下:

 

客户端界面截图如下:

 

测试程序截图如下:

2.服务端主要代码:

 MServerDlg.h 

// MServerDlg.h : 头文件


#pragma once
#include "afxwin.h"
#include <string>
using namespace std;

public:
	CListBox mClientList;//显示 客户端主机名——IP:PORT
	SOCKET socket_server;//服务器套接字
	SOCKET sClient;		//当前连接的客户端套接字
	sockaddr_in sAddrin_server;
	sockaddr_in sAddrin_client;
	BOOL isServerStart;//服务器是否启动
	CMap<SOCKET, UINT,CString, LPCTSTR> clientMap;//客户端集合  key SCOKET value 主机名__IP:PORT
	int iClientCount;
	// 显示信息
	CString mMsg;
	LRESULT OnSocket(WPARAM wParam, LPARAM lParam);
	// 有客户端连接
	void OnAccept(SOCKET s);
	void OnRead(SOCKET s);
	void OnCloseConnect(SOCKET s);
	void SendMsgToClient(SOCKET s,CString msg_unicode);
	UINT uPort;      //服务器端口
	CString strHostIp; //本机IP
	CString strHostName;//本机名
	BOOL firstTimeRead;//某个套接字第一次收到信息
	afx_msg void OnBnClickedButtonSendmsgtoclient();//发送信息到客户端
	afx_msg void OnBnClickedButtonClose();//断开某个连接
	// UNICEDE 字符集下CString (w_char*)转char*
	string Wchar_tToChar_UnicodeToAnsi(CString unicodeStr);
	// UNICODE字符集下char*转CString(w_char)
	CString CharToWchar_t_AnsiToUnicode(char* ansiChar);
	// 关闭客户端操作
	int CloseClient(SOCKET s, int itemIndex);
	//显示信息并让Edit滚动到内容末尾
	void SetEditShowLastedLine(CString sMsg,int iSize);
	void SetEditShowLastedLine(CString sMsg);

	CEdit mMsgEditCtrl;//信息显示CEdit句柄
	afx_msg void OnLbnSelchangeListClient();//ListBox选择项改变
	// 获取本机IP
	CString GetMyHostIP(void);
	afx_msg void OnBnClickedButtonStart();//启动、关闭服务器
	afx_msg void OnBnClickedButtonClean();//清空信息
};

MServerDlg.cpp

// MServerDlg.cpp : 实现文件
//头文件包含
#include "stdafx.h"
#include "MServer.h"
#include "MServerDlg.h"
#include <windows.h> //一定要包含该头文件
#include  < locale.h > 
#pragma comment(lib, "WS2_32.lib")  //windwows下的socket编程函数库
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_SOCKET  WM_USER+1  //自定义消息
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
#define WSAGETSELECTERRO(lParam) HIWORD(lParam)
#define MAX_CLIENT 121


BEGIN_MESSAGE_MAP(CMClientDlg, CDialog)
	
	ON_MESSAGE(WM_SOCKET,CMClientDlg::OnSocket)//自定义消息,绑定WSAAsyncSelect
	
END_MESSAGE_MAP()


//启动、关闭服务器
void CMServerDlg::OnBnClickedButtonStart()
{
	// TODO: 在此添加控件通知处理程序代码
	GetDlgItemTextW(IDC_EDIT_IP,strHostIp);//获取服务器IP
	uPort=GetDlgItemInt(IDC_EDIT_PORT);//获取服务器端口
	if(!isServerStart)//服务器未启动,进行启动操作
	{
		WSADATA wsaData;
		WORD wVersion=MAKEWORD(2,2);
		if(WSAStartup(wVersion,&wsaData)!=0)
		{
			SetEditShowLastedLine(_T("系统:初始化winsock失败!\r\n"));
			return ;
		}
		sAddrin_server.sin_family=AF_INET;
		sAddrin_server.sin_port=htons(uPort);
		string sIp=Wchar_tToChar_UnicodeToAnsi(strHostIp);//IP  CString UNICODE 转ANSI string
		sAddrin_server.sin_addr.S_un.S_addr=inet_addr(sIp.c_str());//string转char*
		socket_server=::socket(AF_INET,SOCK_STREAM,0);
		::bind(socket_server,(sockaddr*)&sAddrin_server,sizeof(sAddrin_server));//端口绑定
		SetEditShowLastedLine(_T("系统:服务器监听启动!\r\n"));
		::listen(socket_server,5);//启动监听
		::WSAAsyncSelect(socket_server,this->m_hWnd,WM_SOCKET,FD_ACCEPT);//非阻塞模式,监听连接事件
		SetDlgItemTextW(IDC_BUTTON_START,_T("关闭服务器"));
		isServerStart=TRUE;
	}else//服务器已经启动,进行关闭操作
	{
		closesocket(socket_server);//关闭服务器套接字
		::WSACleanup();
		//关闭所有客户端
		POSITION pos = clientMap.GetStartPosition();
		while(pos!=NULL)
		{
			UINT uKey;
			CString sVal;
			clientMap.GetNextAssoc(pos,uKey,sVal);
			closesocket(uKey);
		}
		isServerStart=FALSE;
		clientMap.RemoveAll();//清空CMap
		mClientList.ResetContent();//清空CListBox
		iClientCount=0;
		SetDlgItemInt(IDC_EDIT_CLIENTCOUNT,iClientCount);//客户端连接数量显示
		SetEditShowLastedLine(_T("系统:服务器监听关闭!\r\n"));
		SetDlgItemTextW(IDC_BUTTON_START,_T("启动服务器"));//服务器启动、关闭按钮
	}
}

//监听套接字感兴趣的事件,WM_USER+1 消息函数
LRESULT CMServerDlg::OnSocket(WPARAM wParam, LPARAM lParam)
{
	if(WSAGETSELECTERRO(lParam))
	{
		//删除clientMap对应套接字
		OnCloseConnect(wParam);
		CString strErr;
		clientMap.Lookup(wParam,strErr);
		strErr.Format(_T("系统:%s异常断开,错误码=%d\r\n"),strErr,WSAGETSELECTERRO(lParam));
		SetEditShowLastedLine(strErr);
		return -1;

	}
	TRACE(_T("wParam=%d\n"),wParam);
	switch(WSAGETSELECTEVENT(lParam))
	{
	case FD_ACCEPT:
		OnAccept(wParam);
		break;
	case FD_READ:
		OnRead(wParam);
		break;
	case FD_CLOSE:
		OnCloseConnect(wParam);
		break;
	case FD_WRITE:
		break;
	default:
		break;
	
	}
	return 0;
}



// 有客户端连接  当接收连接请求时触发事件
void CMServerDlg::OnAccept(SOCKET s)//参数为服务器本地套接字
{
	firstTimeRead=TRUE;//第一次接受数据,客户端连接成功后会立马发送客户端主机名给服务器
	int len=sizeof(sAddrin_client);
	sClient=accept(s,(sockaddr*)&sAddrin_client,&len);//获取客户端套接字,ip,端口   
	::WSAAsyncSelect(sClient,this->m_hWnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);//重新设置该客户端监听的消息
	CString str;
	char* p_cIp=::inet_ntoa(sAddrin_client.sin_addr);
	str.Format(_T("%s,%s:%d"),
		strHostName,//服务器主机名
		CharToWchar_t_AnsiToUnicode(p_cIp),//客户端ip
		sAddrin_client.sin_port);//客户端端口 
	
	//将服务器主机名,客户端ip,端口  套接字值发送给客户端,方便客户端获取主机名和自己的端口
	//为了不显示在信息里,所以没用SendMsgToClient(SOCKET s,CString msg_unicode)
	int slen=(str.GetLength() + 1) * sizeof(TCHAR);
	::send(sClient,(const char *) (LPCTSTR) str, slen,0);
}

/************************
作用:发送数据给客户端
SOCKET s  对应客户端套接字
CString msg_unicode 用户输入的信息
******************************/
void CMServerDlg::SendMsgToClient(SOCKET s,CString msg_unicode)
{
	int len=(msg_unicode.GetLength() + 1) * sizeof(TCHAR);
	if (::send(s,(const char *) (LPCTSTR) msg_unicode, len,0)!= SOCKET_ERROR)
	{
		CString str;
		str.Format(_T("发送:%s\r\n"),msg_unicode);
		SetEditShowLastedLine(str,len);
	}
	else
	{
		SetEditShowLastedLine(_T("系统:消息发送失败!\r\n"));
	}
}


// 当套接字中有数据需要读取时触发事件
void CMServerDlg::OnRead(SOCKET s)//参数为对应客户端套接字
{
	//通过套接字从clientMap获取客户端主机名和IP端口
	//然后使对应客户端在clientList并使其能看见
	CString sClient;
	if(clientMap.Lookup(s,sClient)==TRUE)
	{
		int index=mClientList.FindString(0,sClient);
		mClientList.SetTopIndex(index);//可见
	}
	void * buff=0;
	int size =0;
	while(TRUE)
	{
		char rcvBuff[128];
		int realLen=::recv(s,rcvBuff,123,0);
		if(realLen<=0)
		{
			break;
		}else
		{
			int new_size=size+realLen;
			buff=realloc(buff,new_size);
			memcpy((void*)(((char*)buff)+size),rcvBuff,realLen);
			size=new_size;
		}
	}
	if(size==0)//没接收到内容
	{
		return;
	}
	CString mstr;
	if(firstTimeRead)//第一次接收的肯定是对应客户端主机名
	{
		firstTimeRead=FALSE;
		char* p_cIp=::inet_ntoa(sAddrin_client.sin_addr);
		mstr.Format(_T("%s__%s:%d"),
					((LPCTSTR)buff),CharToWchar_t_AnsiToUnicode(p_cIp),sAddrin_client.sin_port);;
		mClientList.AddString(mstr);
		mClientList.SetItemData(iClientCount,s);//将SOCKET设置为客户端列表项的itemdata,
		mClientList.SetCurSel(iClientCount++);//高亮IDC_EDIT_CLIENTCOUNT
		SetDlgItemInt(IDC_EDIT_CLIENTCOUNT,iClientCount);
		//listbox滚动
		clientMap.SetAt(s,mstr);
		mClientList.SetTopIndex(iClientCount>13?iClientCount-13:iClientCount);
		SetEditShowLastedLine(_T("系统:")+mstr+_T("已连接\r\n"));
		
	}else//正常的客户端发过
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值