目录
源码地址:MFC:实现TCP通信,基础短信、文件传输功能
一、先导
来看效果:
项目以PC作为服务器中转信息交流,适用于小型局域网内信息传输。
数据包的封装以及基础的套接字功能实现,请参考文章。
包括CListenSocket及CClientSocket类的封装,关联主窗口。
数据包作出调整:
//StdPack.h
#pragma once
struct NETPACKAGE
{
int len;
int type;
char buf[50000];
};
struct MESSAGEPACK
{
int send;
int recv;
char message[49992];
};
当调用SendData(int len, int type, char * buf)函数后,
消息将发送至mRecvData(CClientSocket * pC),此时进行消息处理。
二、实现
服务端
1)新建项目,选择Window套接字功能
2)在项目属性,选择使用多字节字符集
// M_ServerDlg.h: 头文件
//
#pragma once
class CListenSocket; //监听
class CClientSocket; //传输
// CMServerDlg 对话框
class CMServerDlg : public CDialogEx
{
// 构造
public:
CMServerDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_M_SERVER_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
void mAccept(CListenSocket*pL); //接受连接时
void mRecvData(CClientSocket*pC); //接收数据时
CListenSocket*pListen; //监听
CClientSocket*pClient; //客户端
public:
CList<CClientSocket*>list; //存储CClientSocket列表
public:
int m_port; //端口号
int m_max_listen; //最大监听数
afx_msg void OnBnClickedStart();
afx_msg void OnBnClickedClose();
CListCtrl m_list; //客户端连接列表
int FindPC(int recv); //查找客户端(CClientSocket)
int GetPC(int recv); //获取客户端
int SetId(); //生成初始Id
CFile *file; //缓存文件
CString fpath; //文件目录
CString m_tpath; //缓存目录
};
void CMServerDlg::mAccept(CListenSocket * pL)
{
//接受连接
pClient = new CClientSocket;
pClient->SetDlg(this);
pL->Accept(*pClient);
//添加进客户端列表
list.AddTail(pClient);
int n = list.GetCount();
CString str;
str.Format(_T("%d"), n);
m_list.InsertItem(n - 1, str);
//生成并发送Id
CString id;
id.Format(_T("%d"), SetId());
m_list.SetItemText(n - 1, 1, id);
pClient->SendData(100, 1, (char*)id.GetBuffer(100));
}
void CMServerDlg::mRecvData(CClientSocket * pC)
{
UpdateData(TRUE);
NETPACKAGE pack;
pC->GetPackage((char*)&pack);
MESSAGEPACK mspk;
memcpy(&mspk, pack.buf, sizeof(mspk));
CString str;
switch (pack.type)
{
case 1: //信息转发
{
str.Format(_T("%d"), mspk.recv);
int n = GetPC(_ttoi(str));
if (n != -1)
{
//发送数据包至目标客户端
pClient->SendData(50000, 2, (char*)&mspk);
//通知原始客户端已发送
pC->SendData(100, 3, "");
return;
}
//目标客户端不存在
pC->SendData(100, 4, "");
break;
}
case 2: //文件传输预处理
{
str.Format(_T("%d"), mspk.recv);
int n = GetPC(_ttoi(str));
if (n == -1)
{
pC->SendData(100, 4, "");
}
if (file != NULL)
{
pC->SendData(100, 5, "");
}
pC->SendData(100, 6, "");
//通知目标准备接收文件
pClient->SendData(10000, 7, (char*)&mspk);
break;
}
case 3: //准备接收文件
{
str.Format(_T("%s"), pack.buf);
fpath = m_tpath + str;
file = new CFile;
file->Open(fpath, CFile::modeCreate
| CFile::modeNoTruncate
| CFile::modeWrite);
break;
}
case 4: //接收文件
file->Write(pack.buf, pack.len);
break;
case 5: //结束接收文件
{
file->Close();
delete file;
//读取缓存文件,
//发送至客户端
file = new CFile;
file->Open(fpath, CFile::modeCreate
| CFile::modeNoTruncate
| CFile::modeRead);
int len = 0;
char *buf = new char[50000];
do
{
len = file->Read(buf, 50000);
pClient->SendData(len, 8, buf);
} while (len > 0);
pClient->SendData(100, 9, "");
file->Close();
delete file;
delete buf;
file = NULL;
break;
}
}
UpdateData(FALSE);
}
int CMServerDlg::FindPC(int recv)
{
CString str;
str.Format(_T("%d"), recv);
for (int i = 0; i < list.GetCount(); i++)
{
if (str == m_list.GetItemText(i, 1))
{
return i;
}
}
return -1;
}
int CMServerDlg::GetPC(int recv)
{
int i = FindPC(recv);
if (i == -1)
{
return -1;
}
pClient = list.GetAt(list.FindIndex(i));
return 0;
}
int CMServerDlg::SetId()
{
//生成四位伪随机数
int rd = rand()*(9999 - 1000) / 32767 + 1000;
if (FindPC(rd) != -1)
{
SetId();
}
return rd;
}
客户端
布局及变量名参考:
// M_ClientDlg.h: 头文件
//
#pragma once
class CClientSocket;
// CMClientDlg 对话框
class CMClientDlg : public CDialogEx
{
// 构造
public:
CMClientDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_M_CLIENT_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
void mRecvData(CClientSocket*pC);
CClientSocket*pClient;
public:
int AddintoList(int recv);
public:
CString m_ipaddr;
int m_port;
afx_msg void OnBnClickedConnect();
CListBox m_cust_list;
int m_crt_id;
int m_tag_id;
CString m_space;
CString m_ed_send;
afx_msg void OnBnClickedSend();
public:
CString m_fpath; //文件目录
CString m_fname; //文件名
CFile *file; //缓存文件
CString m_tpath; //文件缓存目录
afx_msg void OnBnClickedBrowse();
afx_msg void OnBnClickedSendfile();
void SendFile();
public: //优化
afx_msg void OnEnUpdateSpace();
afx_msg void OnLbnDblclkCustList();
virtual void OnOK();
};
void CMClientDlg::mRecvData(CClientSocket * pC)
{
UpdateData(TRUE);
NETPACKAGE pack;
pC->GetPackage((char*)&pack);
MESSAGEPACK mspk;
memcpy(&mspk, pack.buf, sizeof(mspk));
CString str;
switch (pack.type)
{
case 1: //接收并设置Id
{
str.Format(_T("%s"), pack.buf);
m_crt_id = _ttoi(str);
break;
}
case 2: //接收文本信息
{
AddintoList(mspk.send);
str.Format(_T("[%d]%s"), mspk.send, mspk.message);
m_space += str;
m_space += _T("\r\n");
break;
}
case 3: //信息发送已完成
{
AddintoList(m_tag_id);
m_space += m_ed_send;
m_space += _T("\r\n");
m_ed_send = _T("");
break;
}
case 4:
MessageBox(_T("目标不存在"));
break;
case 5:
MessageBox(_T("线路忙,请稍后重试"));
break;
case 6:
SendFile();
break;
case 7: //准备接收文件
{
AddintoList(mspk.send);
str.Format(_T("[%d][接收文件]%s"), mspk.send, mspk.message);
m_space += str;
m_space += _T("\r\n");
str.Format(_T("%s"), mspk.message);
file = new CFile;
file->Open(m_tpath + str, CFile::modeCreate
| CFile::modeNoTruncate
| CFile::modeWrite);
break;
}
case 8: //接收文件
file->Write(pack.buf, pack.len);
break;
case 9: //结束接收
file->Close();
delete file;
file = NULL;
break;
}
UpdateData(FALSE);
OnEnUpdateSpace();
}
void CMClientDlg::OnBnClickedSend()
{
UpdateData(TRUE);
if (pClient == NULL)
{
MessageBox(_T("未连上服务器"));
return;
}
if (m_tag_id < 1000 || m_tag_id>9999)
{
MessageBox(_T("目标不正确"));
return;
}
MESSAGEPACK mspk;
mspk.send = m_crt_id;
mspk.recv = m_tag_id;
memcpy(mspk.message, (char*)m_ed_send.GetBuffer(49992), 49992);
pClient->SendData(50000, 1, (char*)&mspk);
UpdateData(FALSE);
}
void CMClientDlg::OnBnClickedBrowse()
{
UpdateData(TRUE);
CFileDialog dlg(TRUE);
if (dlg.DoModal() == IDOK)
{
m_fpath = dlg.GetPathName();
m_fname = dlg.GetFileName();
}
UpdateData(FALSE);
}
void CMClientDlg::OnBnClickedSendfile()
{
UpdateData(TRUE);
if (m_fpath == _T(""))
{
MessageBox(_T("文件不为空"));
return;
}
MESSAGEPACK mspk;
mspk.send = m_crt_id;
mspk.recv = m_tag_id;
memcpy(mspk.message, (char*)m_fname.GetBuffer(9000), 9000);
pClient->SendData(10000, 2, (char*)&mspk);
AddintoList(m_tag_id);
}
void CMClientDlg::SendFile()
{
pClient->SendData(100, 3, (char*)m_fname.GetBuffer(9000));
file = new CFile;
if (FALSE == file->Open(m_fpath, CFile::modeCreate
| CFile::modeNoTruncate
| CFile::modeRead))
{
MessageBox(_T("文件打开失败"));
return;
}
int len = 0;
char *buf = new char[50000];
do
{
len = file->Read(buf, 50000);
pClient->SendData(len, 4, buf);
} while (len > 0);
pClient->SendData(100, 5, "");
CString str;
str.Format(_T("[发送文件]%s"), m_fpath);
m_space += str;
m_space += _T("\r\n");
UpdateData(FALSE);
file->Close();
delete file;
delete buf;
}
项目优化
void CMClientDlg::OnEnUpdateSpace()
{
//当m_space更新后保持滚动条在底部
CEdit *pEdit = (CEdit*)GetDlgItem(IDC_SPACE);
pEdit->PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
}
void CMClientDlg::OnLbnDblclkCustList()
{
//双击m_cust_list更新至m_tag_id
int n = m_cust_list.GetCurSel();
CString str;
m_cust_list.GetText(n, str);
m_tag_id = _ttoi(str);
UpdateData(FALSE);
}
void CMClientDlg::OnOK()
{
//回车发送消息
if (GetDlgItem(IDC_ED_SEND)->GetSafeHwnd() == ::GetFocus())
{
OnBnClickedSend();
}
//CDialogEx::OnOK();
}