注意:本篇文章源代码贴得很多,所以有点长,如果觉得阅读不方便,可以直接复制源代码到你的程序里去,也可以直接下载源码。后面整合了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//正常的客户端发过

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

1138

被折叠的 条评论
为什么被折叠?



