当前位置: 首页 > news >正文

基于C++实现(WinForm) LAN 的即时通信软件

基于 LAN 的即时通信软件的设计

一、概述

1.1 设计目的:

设计一个基于 LAN 的即时通信软件,实现在局域网下可靠的、稳定的即时通信功能以及其从属的附加功能。

1.2 设计内容:

1.2.1 功能设计:

·实现一对一的单播通信,包括消息发送与接收以及文件的发送与接收;

·实现一对一的单播通信,包括消息发送与接收以及文件的发送与接收;

·附加功能:实现登陆、注册、获取当前在线情况等功能;

1.2.2 界面设计:

·客户端的交互界面设计。

1.2.3 客户端、服务器设计:

·客户端需要完成的功能;

·服务器需要完成的功能;

·客户端、服务器的交互设计;

1.3 设计要求:

结合《计算机网络》课程所学的知识以及查阅相应的资料完成相应的设计内容,且需要保证设计的质量以及程序的可靠性和稳定性。

二、设计任务分析

2.1 功能设计分析:

·实现一对一的单播、多播通信:

主要运用消息转发技术,需要服务器来处理消息的解析和转发;其中消息的解析包括获取消息的发送者、接收者、类别;针对不同的解析结果需要做出不同的响应。

·实现附加功能:实现登陆、注册、获取当前在线情况等功能;将客户端对附加功能的调用当作特殊

的请求消息发送给服务器,服务器解析后做出不同的响应。

2.2 界面设计:

客户端界面需要有较好的交互性,因此需要设计:

·登陆、注册对话框:包括用户名输入框、登陆和注册按钮;

·主界面对话框:包括消息发送编辑框、消息接收显示区、好友在线情况显示区、发送按钮、以及登陆按钮;

2.3 客户端、服务器设计:

2.3.1 客户端设计:

·获取客户所发送的消息内容;

·根据客户要求封装消息并发送消息;

·接收服务器发来的消息;

·解析接收的消息并执行对应响应的功能;

2.3.2 服务器端设计:

·获取客户端发来的消息

·解析消息并执行对应的处理

·将处理结果封装成消息发送给指定客户

三、总体设计

3.1 界面设计结果

3.1.1 登陆、注册对话框

3.1.2 主界面对话框

说明:

编辑框 1:消息接收窗口编辑框 2:消息发送编辑框编辑框 3:消息接收者编辑框编辑框 4:当前在线用户显示框

“发送”按钮:发送编辑框 2 中的内容给编辑框 3 内对应的客户;

“文件”按钮:发送编辑框 2 中对应的文件给编辑框 3 内对应的客户;

“登陆”按钮:打开登陆、注册对话框;

3.2 客户端程序处理流程图:登陆、注册处理:

普通消息发送、文件发送、以及消息接受

3.3 服务器端程序处理流程图

四、程序实现

4.1 消息结构体:

4.1.1 消息结构体
structmes//消息结构体
{char from[32];//发送者char to[32];//接收者char content[MAX_PATH];//消息内容
};
4.1.2 消息模板:

4.1.3 解释:

客户端和服务器会根据消息的三部分内容进行消息解析,不同的解析结果对应不同的消息处理。

4.2 客户端程序实现:

4.2.1 客户端自定义套接字类(继承 MFC 抽象类 CSOCKET)实现:
class CMySocket : public CSocket
{// Attributespublic:CLanMessageDlg *dlg;//主对话框指针// Operationspublic:CMySocket();virtual ~CMySocket();void OnReceive(int n);//消息接收响应// Overridespublic:// ClassWizard generated virtual function overrides//{{AFX_VIRTUAL(CMySocket)//}}AFX_VIRTUAL// Generated message map functions//{{AFX_MSG(CMySocket)// NOTE - the ClassWizard will add and remove member functions here.//}}AFX_MSG// Implementationprotected:
};
void CMySocket::OnReceive(int n)//消息响应处理
{if(dlg->online)//在线才进行响应{mes trans;if(!Receive((void*)&trans,sizeof(trans))){return;}//获取服务器消息CString txt1=trans.from,txt2="在线用户";if(txt1==txt2)//如果是"在线客户"消息{CString show;show.Format("当前用户:%s\r\n%s:%s",dlg->me,trans.from,trans.content);dlg->m_online=show;dlg->UpdateData(false);//刷新主界面}else if(trans.from[0]=='_')//如果是文件消息{CStdioFile put;CString name=trans.from;name=name.Mid(1);//获取文件名if(!put.Open(name,CFile::modeCreate|CFile::modeWrite))//将文件内容存储{	AfxMessageBox("创建文件失败!");return;}else put.WriteString(trans.content);put.Close();}else {//如果是普通消息CString show;show.Format("From %s:%s\r\n",trans.from,trans.content);dlg->m_show+=show;dlg->UpdateData(false);//刷新主界面}}
}
4.2.2 登陆、注册对话框实现:
class CLanMessageDlg : public CDialog
{// Constructionpublic:CString me;//客户名字CLanMessageDlg(CWnd* pParent = NULL);	// standard constructorbool online;//是否在线CSocket *client;//套接字指针// Dialog Data//{{AFX_DATA(CLanMessageDlg)enum { IDD = IDD_LANMESSAGE_DIALOG };CString	m_online;//在线情况CString	m_to;//消息接收者CString	m_message;//要发送的消息CString	m_show;//接收到的消息CString	m_tips;//}}AFX_DATA// ClassWizard generated virtual function overrides//{{AFX_VIRTUAL(CLanMessageDlg)protected:virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support//}}AFX_VIRTUAL// Implementationprotected:HICON m_hIcon;// Generated message map functions//{{AFX_MSG(CLanMessageDlg)virtual BOOL OnInitDialog();afx_msg void OnSysCommand(UINT nID, LPARAM lParam);afx_msg void OnPaint();afx_msg HCURSOR OnQueryDragIcon();afx_msg void OnButtonlogin();afx_msg void OnButtonsend();afx_msg void OnButtonfile();//}}AFX_MSGDECLARE_MESSAGE_MAP()};
void CLOGINDLG::OnButtonlogin() //向服务端申请登陆
{// TODO: Add your control notification handler code hereUpdateData(true);//刷新获取用户名mes a;sprintf(a.from,"%s",m_name);sprintf(a.content,"登陆");//消息封装//消息发送if(!dlg->client->Send((void*)&a,sizeof(a))) {MessageBox("错误发送");return;}dlg->me=m_name;mes trans;//获取登陆结果if(!dlg->client->Receive((void*)&trans,sizeof(trans))){return;}CString txt1="您已登陆成功!",txt2=trans.content;if(txt1==txt2) //如果登陆成功{dlg->online=true;GetDlgItem(IDC_BUTTONLOGIN)->EnableWindow(false);//禁用登陆和注册按钮GetDlgItem(IDC_BUTTONREGIST)->EnableWindow(false);}else{MessageBox(trans.content);//打印登陆失败原因}}void CLOGINDLG::OnButtonregist()//向服务端申请注册
{// TODO: Add your control notification handler code hereUpdateData(true);//获取注册用户名mes a;sprintf(a.from,"%s",m_name);sprintf(a.content,"注册");//封装注册消息//发送消息if(!dlg->client->Send((void*)&a,sizeof(a))) {MessageBox("错误发送");return;}mes trans;//接收注册结果if(!dlg->client->Receive((void*)&trans,sizeof(trans))){return;}CString txt1="注册成功!",txt2=trans.content;if(txt1==txt2) //如果注册成功{MessageBox(trans.content);}
}
4.2.3 主对话框实现:
void CMySocket::OnReceive(int n)//消息响应处理
{if(dlg->online)//在线才进行响应{mes trans;if(!Receive((void*)&trans,sizeof(trans))){return;}//获取服务器消息CString txt1=trans.from,txt2="在线用户";if(txt1==txt2)//如果是"在线客户"消息{CString show;show.Format("当前用户:%s\r\n%s:%s",dlg->me,trans.from,trans.content);dlg->m_online=show;dlg->UpdateData(false);//刷新主界面}else if(trans.from[0]=='_')//如果是文件消息{CStdioFile put;CString name=trans.from;name=name.Mid(1);//获取文件名if(!put.Open(name,CFile::modeCreate|CFile::modeWrite))//将文件内容存储{	AfxMessageBox("创建文件失败!");return;}else put.WriteString(trans.content);put.Close();}else {//如果是普通消息CString show;show.Format("From %s:%s\r\n",trans.from,trans.content);dlg->m_show+=show;dlg->UpdateData(false);//刷新主界面}}
}

void CLanMessageDlg::OnButtonlogin() //登陆按钮实现
{// TODO: Add your control notification handler code hereCLOGINDLG *dlg;dlg=new CLOGINDLG();dlg->dlg=this;dlg->DoModal();//显示登陆、注册对话框//登陆成功才可发送if(online) {GetDlgItem(IDC_BUTTONSEND)->EnableWindow(true);GetDlgItem(IDC_BUTTONFILE)->EnableWindow(true);}
}void CLanMessageDlg::OnButtonsend() //普通消息发送
{UpdateData(true);mes send;sprintf(send.from,"%s",me);sprintf(send.content,"%s",m_message);sprintf(send.to,"%s",m_to);//封装普通消息if(!client->Send((void*)&send,sizeof(send))) {MessageBox("错误发送");return;}m_show+="To"+m_to+":"+m_message+"\r\n";m_message="";UpdateData(false);//刷新屏幕// TODO: Add your control notification handler code here}void CLanMessageDlg::OnButtonfile() //发送文件
{// TODO: Add your control notification handler code hereUpdateData(true);CStdioFile get;if(!get.Open(m_message,CFile::typeText,NULL)) {MessageBox("无效文件!");return;}//如果打开文件成功CString txt,temp;while(get.ReadString(temp))txt+=temp+"\r\n";MessageBox(txt);mes send;//通知对方有文件传来sprintf(send.from,"%s",me);sprintf(send.content,"发送文件");sprintf(send.to,"%s",m_to);if(!client->Send((void*)&send,sizeof(send))) {MessageBox("错误发送");return;}sprintf(send.from,"_%s",get.GetFileName());//封装文件内容sprintf(send.content,"%s",txt);sprintf(send.to,"%s",m_to);//将文件发送过去if(!client->Send((void*)&send,sizeof(send))) {MessageBox("错误发送");return;}get.Close();}

4.3 服务器程序实现:

# include <iostream>
# include <string>
# include <winsock2.h>
# include <vector>
# include <fstream>
using namespace std;
# pragma comment(lib, "ws2_32.lib")
# define PORT 3000//服务器ip和端口号
# define IP_ADDRESS "127.0.0.1"
struct mes//消息结构体
{char from[32];//发送者char to[32];//接收者char content[MAX_PATH];//内容
};
struct user//用户结构体
{string name;//用户名SOCKET socket;//客户宿主套接字bool online;//是否在线
};
string login="登陆",regist="注册";
static vector<user> client;//客户动态数组
inline void updateonline()//更新在线人数
{string txt;for(int i=0;i<client.size();i++){if(client[i].online) {txt+="\r\n"+client[i].name;}}mes a;sprintf(a.content,"%s",txt.c_str());sprintf(a.from,"在线用户");for(i=0;i<client.size();i++)//将情况反馈给在线用户{if(client[i].online) {send(client[i].socket,(char*)&a,sizeof(a),0);}}
}
user*  handlelogin(mes rec,user *who)//处理登陆
{bool log_error=true;for(int i=0;i<client.size();i++){if(rec.from==client[i].name&&client[i].online==false) //注册且不在线,则登陆{client[i].socket=who->socket;//获取宿主套接字client[i].online=true;//设置在线cout<<rec.from<<"登陆成功"<<endl;mes a;sprintf(a.from,"server");sprintf(a.content,"您已登陆成功!");send(client[i].socket,(char*)&a, sizeof(a), 0);//反馈给客户端who->name=client[i].name;log_error=false;return &client[i];}if(rec.from==client[i].name&&client[i].online)//如果注册且在线,提示已经登陆{mes a;sprintf(a.from,"server");sprintf(a.content,"该用户已经登陆!");send(who->socket,(char*)&a, sizeof(a), 0);return who;}}if(log_error) //未注册则提醒注册{mes a;sprintf(a.from,"server");sprintf(a.content,"该用户未注册!");send(who->socket,(char*)&a, sizeof(a), 0);return who;//如果登陆失败}}
void transmit(mes rec,user *who)//消息转发
{string name=rec.to;if(name=="群发")//群发消息{for(int i=0;i<client.size();i++){if(rec.from!=client[i].name&&client[i].online)send(client[i].socket,(char*)&rec, sizeof(rec), 0);}}else//将接收到的消息转发给指定的客户{for(int i=0;i<client.size();i++){if(name==client[i].name&&client[i].online) send(client[i].socket,(char*)&rec, sizeof(rec), 0);}}}
void handleregist(mes rec,user *who)//处理注册
{user _user;_user.name=rec.from;_user.online=false;client.push_back(_user);//存入动态数组ofstream out;out.open("user.txt",ios_base::app);out<<" "<<_user.name;out.close();//存进数据文件mes a;sprintf(a.from,"server");sprintf(a.content,"注册成功!");//反馈send(who->socket,(char*)&a, sizeof(a), 0);
}DWORD WINAPI ClientThread(LPVOID lpParameter)//交互线程
{struct sockaddr_in ClientAddr;int AddrLen = sizeof(ClientAddr);SOCKET CientSocket =(SOCKET)lpParameter;//获取连接的套接字int Ret = 0;user *who=new user;who->socket=(SOCKET)lpParameter;who->online=false;mes  rec;//while ( true ){//recvRet = recv(CientSocket, (char*)&rec, sizeof(rec), 0);getpeername(CientSocket, (struct sockaddr *)&ClientAddr, &AddrLen);if ( Ret == 0 || Ret == SOCKET_ERROR ) //客户端退出、下线处理{cout << "客户" << inet_ntoa(ClientAddr.sin_addr) << " ; " << ClientAddr.sin_port << " quit! " << endl;//客户端退出cout<<who->name<<"下线"<<endl;who->online=false;updateonline();break;}cout << "客户" << inet_ntoa(ClientAddr.sin_addr) << " ; " << ClientAddr.sin_port <<"---say : " <<rec.from<<":"<< rec.content<< endl;//输出服务端收到的信息string handle=rec.content;//信息处理选项if(handle==login) //处理登陆{who=handlelogin(rec,who);updateonline();}else if(handle==regist) handleregist(rec,who);//处理注册else transmit(rec,who);//消息转发}return 0;
}
void readuser()//读取用户
{ifstream in;in.open("user.txt");if (in){while(!in.eof()){user _user;in>>_user.name;_user.online=false;client.push_back(_user);}in.close();}
}
int run()//创建服务器套接字并开始监听
{WSADATA  Ws;SOCKET ServerSocket,clientsocket;struct sockaddr_in LocalAddr, ClientAddr;int Ret = 0;int AddrLen = 0;HANDLE hThread = NULL;//1.初始化windows套接字if ( WSAStartup(MAKEWORD(2, 2), &Ws) != 0 ){cout<<"Init Windows Socket Failed:"<<GetLastError()<<endl;return -1;}//2.创建套接字ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if ( ServerSocket == INVALID_SOCKET ){cout<<"Create Socket Failed:"<<GetLastError()<<endl;return -1;}LocalAddr.sin_family = AF_INET;LocalAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);LocalAddr.sin_port = htons(PORT);memset(LocalAddr.sin_zero, 0x00, 8);//3.绑定套接字Ret = bind(ServerSocket, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr));if ( Ret != 0 ){cout<<"Bind Socket Failed:"<<GetLastError()<<endl;return -1;}//4.监听用户,最大用户量为10Ret = listen(ServerSocket, 10);if ( Ret != 0 ){cout<<"listen Socket Failed:"<<GetLastError()<<endl;return -1;}cout<<"服务端已经启动"<<endl;//开始运转while(true){//接受连接AddrLen = sizeof(ClientAddr);clientsocket = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &AddrLen);if (clientsocket == INVALID_SOCKET ){cout<<"Accept Failed::"<<GetLastError()<<endl;break;}//打印客户端连接cout<<"客户端连接: ip.addr : "<<inet_ntoa(ClientAddr.sin_addr)<<" ; port: "<<ClientAddr.sin_port<<endl;//将信息交换转接给线程执行hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)clientsocket, 0 , NULL);if ( hThread == NULL ){cout<<"Create Thread Failed!"<<endl;break;}CloseHandle(hThread);}closesocket(ServerSocket);WSACleanup();
}
int main()
{readuser();//读取用户run();return 0;}

五、运行结果:

5.1 登陆、注册:

5.1.1“张三”登陆

5.1.2“张三”登陆成功:显示用户名及当前在线用户

5.1.3 登陆在线用户:显示用户已经登陆,登录失败

5.1.4 登陆未注册用户:显示用户未注册,提示注册

5.1.5 用户注册:提示注册成功

5.2 消息发送:

5.2.1 单发消息:

5.2.2 群发消息:

5.3 文件发送:

小方向张三发送一个文件,消息盒子显示文件内容

文件被存储到桌面,用记事本打开文件:

六、心得与体会

6.1 遇到的问题及解决方案

6.1.1 服务器如何与多个客户端进行交互?

解决方案:服务器主线程负责监听客户端的连接,每连接一个客户就启动一个线程,把客户端套接字传给线程,线程来处理与客户端的交互。总之,接受了多少个客户的连接就需要建立多少个处理线程。

实现方法:

6.1.2:如何实现客户端消息的非阻塞式接收?

解决方案:客户端的是图形界面程序,故不太适合循环阻塞式消息接收,需要利用 MFC 类库提供的消息响应机制来实现非阻塞式接收。本程序使用了 MFC 类库中 CSocket 类提供的 OnReceive()接口函数来实现非阻塞式接收。

实现方法:

6.2 心得与体会:

本次《计算机网络》课程设计我选择了“基于 Lan 的即时通信软件设计”这个题目。选择这个题目的原因主要有两个:其一是它比较贴近真实的生活应用场景,其二是想借此机会对”Winsock”进行更深层次的了解,以便日后更好的进行网络程序设计。

在刚开始翻看简单的 Winsock 编程案例时,代码的实现让人一头雾水,情形和刚开始接触 MFC 编程的时侯是一模一样的,Window 系统提供的函数接口复杂多样,在没有仔细了解 Winsock 的用法和原理情况下,我花了很长的时间也没有读懂案例的程序代码。为此,我去图书馆借阅了 windows 网络编程相关的书籍。接着一边看案例,一边看书、编代码,最终弄懂了 Winsock 的基本用法。而剩下的消息处理工作对我来说就相对容易,我很快地就将它们完成了。

最后,希望在今后的实践中自己能够养成多查阅资料、多翻看编程实例并且多动手的好习惯,也希望自己能够多吸取他人的编程经验并提高自己的编程效率。

参考文献:

[1]曹衍龙,刘海英,VisualC++ 网络通信编程实用案例精选.北京:人民邮电出版社,2006[2]谢希仁,计算机网络(第七版).北京:电子工业出版社,2017.1.

http://www.xdnf.cn/news/868357.html

相关文章:

  • 【笔记】PyCharm 使用问题反馈与官方进展速览
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-function_tool(二)
  • IDEA中微服务指定端口启动
  • java31
  • Spring Boot 从Socket 到Netty网络编程(下):Netty基本开发与改进【心跳、粘包与拆包、闲置连接】
  • React组件基础
  • Python 2.7 退役始末:代码架构缺陷与社区演进路线图
  • Linux-linux和windows创建新进程的区别以及posix_spawn
  • 爬虫学习记录day1
  • Git Github Gitee GitLab
  • [特殊字符] 深度剖析 n8n 与 Dify:使用场景、优劣势及技术选型建议
  • 常用的Docker命令
  • 得物GO面试题及参考答案
  • Quick UI 组件加载到 Axure
  • [杰理]蓝牙状态机设计与实现详解
  • Android 3D球形水平圆形旋转,旋转动态更换图片
  • (2025)Windows修改JupyterNotebook的字体,使用JetBrains Mono
  • 【计算机网络】第3章:传输层—TCP 拥塞控制
  • MaskSearch:提升智能体搜索能力的新框架
  • Qwen3与MCP协议:重塑大气科学的智能研究范式
  • 文献分析指令
  • SSM spring Bean基础配置
  • 代理ip的原理,代理ip的类型有哪些?
  • Vue全局事件总线
  • 【Cursor】开发chrome插件,实现网页tab根据域名分组插件
  • 区块链+AI融合实战:智能合约如何结合机器学习优化DeFi风控?
  • 使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作
  • Moticon智能鞋垫传感器OpenGo如何提升神经病学步态分析的精准性
  • 比较运算符:==、!=、>、<、>=、<=
  • 机器学习与深度学习10-支持向量机02