中南民族大学
软件课程设计报告
电子信息工程学院09级电子工程专业
题目: 简易网络聊天系统
学生:吴雪 学号:09071002
指导教师:王锦程
20##年4月25日
简易网络聊天系统
摘要:计算机网络通信技术已经深入我们的生活,并给我们即使通信带来了很大的方
随着网络的日益普及,各种聊天工具也层出不穷。本课程设计就是实现个简易的网络聊天程序。利用MFC为开发工具,实现基本的通信功能,以Windows为开发平台,VC6.0为开发环境,程序运行平台为WindowsXP。
关键字:网络通信,MFC,SOCKET嵌套字,客户端,服务器
第一章 引言
1.1 课程设计的背景:在互联网非常普及的今天,网络聊天已经成为我们日常生活中不可缺少的一种通信工具,聊天程序也应该是一种基础的程序。一个简单的聊天程序,就是在多个I/O端点之间实现多对多的通信。基于SOCKET嵌套字的网通信是一种灵活的,易于实现的方法。在SOCKET API的帮助下,开发基于SOCKET的局域网通信软件是易于实现的。
1.2 课程设计的目的:本设计的目标是用C++语言设计一个实时聊天系统的程序。程序写完后最终生成一个客户端,它由client和server两部分组成,client和server之间的通信基于TCP协议。实现数据的收发。输入聊天室使用的昵称及目的主机的IP,连接到目的主机,客户端与客户端之间即可实现简易的聊天和传输文件的功能.
第二章设计依据及框图
2.1 设计平台:本课程设计的基本的网络编程都是建立在Winsock基础上的。Winsock是90年代初,为了方便网络编程,由Microsoft联合了其他几家公司共同制定的一套WINDOWS下的网络编程接口,它是通过C语言的动态链接库方式提供给用户及软件开发者的,主要由winsock.h头文件和动态链接库winsock.dll组成,目前有两个版本:Winsock1.1和Winsock2.0。作为网络编程接口,Winsock屏蔽了网络底层的复杂的协议和数据结构,使得编程人员对网络的操作变得非常简单,因此,在Win32平台上,访问众多的基层网络协议,Winsock是首选接口。开发平台我选用了VC6.0,因为一直以来都使用VC6进行学习,对这个IDE最为熟悉,再者VC同样是由微软开发的系统,与其操作系统,网络接口具有最为密切的契合优点,所以选择了VC6.0。
2.2 设计思想、设计框图及数据库表结构
(1)系统总体设计思想:通常的通信工具,都采用客户机/服务器(C/S)体系结构,C/S结构是这样的一种结构:它包括一个客户机(或前端),一个服务器(或称后端),客户机的作用是访问和处理远程服务器上的数据,服务器的作用是接收和处理客户机的数据请求。有时,可能有多个客户向同一个服务器同时请求服务,这就需要服务器决定怎样处理这些请求。Client/Server结构是当前数据库应用程序中极为流行的一种方式。尤其是网络技术的发展,使得当前很多系统都采用这种方式进行构造,其最大的优点是将计算机工作任务分别由客户端和服务器端来共同完成,这样有利于充分合理的利用系统资源。另外它的服务器端还可以将信息集中起来,任何客户机都可以通过访问服务器而获得所需的信息。Client/Server模型最终可归结为一种“请求/应答”关系。一个请求总是首先被客户发出,然后服务器总是被动地接收请求,返回客户需要的结果。在客户发出一个请求之前,服务进程一直处于休眠状态。一个客户提出请求后,服务进程被“唤醒”并且为客户提供服务,对客户的请求做出所需要的应答。在客户端启动后,客户端计算得到本地网络的广播地址,进行广播查找服务器端,服务器接收到客户端的广播信息后返回服务器地址,则客户端接收、验证信息并记录服务器端地址,然后客户端启动定时期,定时发送信息到服务器,以告知服务器自己在线,然后服务器返回在线用户列表,服务器依靠客户端发送的信息来更新维护在线用户列表。在客户端与服务器尽心数据交换,拥有了在线用户列表后,就可以选择IP进行客户端之间的点对点信息交流了。如果服务器不在线,则客户端会提示用户退出,在一定的时间后自动退出。
(2)设计框图
图(1)设计思路框图
(3)数据库表结构及表间关系
图(2)数据库表结构及表间关系
第三章各模块功能及主要模块的流程图
3.1 各模块功能简介
1.Connect():int connect ( SOCKET s, const struct sockaddr FAR* name, int namelen);
【使用说明】
与通信对象建立连接,主要用在客户端。其中s、name和namelen的含义与使用方法和bind()相同。如果连接失败,该函数会返回SOCKET_ERROR。
2.listen():int listen (SOCKET s,int backlog);
【使用说明】
对于服务器端程序,当申请到Socket,并指定通信对象为INADDR_ANY之后,就应该等待一个客户端程序的连接。当没有连接请求时,就进入等待状态,直至有一个请求到达为止。其中:
s<输入>:是socket()创建的socket。
backlog<输入>:等待连接的队列长度,可取1~5。如果当某个客户程序要求连接之时,服务器已与其他客户程序连接,则后来的连接请求会被放在队列中,等待服务器空闲的时候再与之连接。当队列达到指定长度(backlog的值)时,再来的连接请求都将被拒绝。
3.accept():SOCKET accept (SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);
【使用说明】
对与服务器端程序,在接收到一个连接请求之后,要为这个连接建立一个新的socket,这个任务由accept()函数来完成,并把它作为返回值。新建的Socket与原来的Socket有相同的特性,包括端口号。原来的Socket用于继续等待其他的连接请求,而新生成的Socket才是与客户端进行通信的实际Socket。一般将参数中的SOCKET称做“监听”Socket,它只负责接受连接,不负责通话;而accept 返回的SOCKET则称为“会话”Socket,它只负责与客户端通话。参数中的指针addr和addrlen用来返回客户机的sockaddr_in结构体,通过addr可得到客户机的IP地址和连接端口。使用方法则与bind()中的name和namelen相同。
4.recv()/send():
建立连接后,用来接收和发送数据。其中:
s<输入>:是连接用的socket。
buf、len和flags的含义与作用方法与recvfrom()/connect()中的相同,分别表示接收和发送的数据包字符串的地址、长度和标志。
3.2 主要流程图(四号宋体加粗)
图(3)分别为服务器端和客户端流程图
第四章软件调试分析
图(4)服务器端显示界面
图(5)客户端显示界面
图(6)几个同学聊天记录客户端显示
图(7)聊天时的服务器端
第五章结语
5.1 结论与讨论:用C语言编写Windows应用程序有两种方式:一种是Windows C编程方式,另一种是Visual C++编程方式。在一般情况下,Visual C++编程方式编写的程序源代码量小、开发时的工作量小、工作难度也较小,但编译后的代码量较大,运行速度略低;而Windows C编程方式编写的程序源代码量虽然较大,但可执行代码效率高。随着技术的进步,Visual C++编程方式已被广泛采用,但象网络编程等一些对速度要求高、对硬件操作较多的程序,大多数还是用Windows C编程方式开发的。
在上面的程序中,大家注意到,客户端程序,我并没有把要发送的信息直接显示在ID编辑框中,而是在发送后,由服务器端再发给各个进入聊天室的客户,客户端程序连接完成,就开始接收服务器发送的信息,这样的客户端程序,事实上就是一个简单的端口扫描程序,有兴趣的读者可以试试连接不同IP地址机器的不同端口,就可以根据返回的的信息,判断这台机器都开启了哪些网络服务程序了。通过聊天室程序的编写,可以基本了解Windows Sockets API编程的基本过程和精要之处。本程序在VC++6.0下编译通过,在使用windows 98/2000/XP/NT的局域网里运行良好。
参考文献
[1]侯俊杰.深入浅出MFC[M].华中科技大学出版社.2001
[2]孙鑫.VC++深入详解[M].电子工业出版社.ISBN 7-121-02530-2
[3]黄强.WINDOWS网络编程[M].人民邮电出版社.ISBN 978-7-115-10961-3
附录
// ChatRoomDlg.cpp : implementation file
//
#include "stdafx.h"
#include "ChatRoom.h"
#include "ChatRoomDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CChatRoomDlg dialog
CChatRoomDlg::CChatRoomDlg(CClientSocket *tmp,CWnd* pParent /*=NULL*/)
: CDialog(CChatRoomDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CChatRoomDlg)
m_IDC_EDIT_MESSAGE = _T("");
m_IDC_EDIT_ADDRESS = _T("127.0.0.1");
m_IDC_EDIT_NIKENAME = _T("");
m_SL = FALSE;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
myServerSocket=tmp;
}
void CChatRoomDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CChatRoomDlg)
DDX_Control(pDX, Exit_BUTTON, m_BNExit);
DDX_Control(pDX, Send_BUTTON, m_BNSend);
DDX_Control(pDX, IDC_LIST1, m_IDC_LIST_CHATBOX_CONTROL);
DDX_Control(pDX, IDC_LIST3, m_USER);
DDX_Text(pDX, IDC_EDIT1, m_IDC_EDIT_MESSAGE);
DDX_Text(pDX, IDC_EDIT3, m_IDC_EDIT_ADDRESS);
DDX_Text(pDX, IDC_EDIT2, m_IDC_EDIT_NIKENAME);
DDX_Check(pDX, IDC_CHECK1, m_SL);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CChatRoomDlg, CDialog)
//{{AFX_MSG_MAP(CChatRoomDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(Exit_BUTTON, OnButton2)
ON_BN_CLICKED(Send_BUTTON, OnButton1)
ON_BN_CLICKED(Connect_BUTTON, OnConnectButton)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CChatRoomDlg message handlers
BOOL CChatRoomDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// m_STATIC_NIKENAME.SetWindowText(myServerSocket->NikeName);
//*********************发送昵称
/* char message2[20];
strcpy(message2,"NEW_USER:");
strcat(message2,myServerSocket->NikeName);
if (myServerSocket->Send(message2,20))
{
}
else
{
AfxMessageBox("网络传输错误!!!");
}
*/
//xmj
m_BNSend.EnableWindow(FALSE); //"发送信息"按钮状态为无效
m_BNExit.EnableWindow(FALSE); //"离开聊天室"按钮状态为无效
return TRUE; // return TRUE unless you set the focus to a control
}
void CChatRoomDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CChatRoomDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CChatRoomDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void CChatRoomDlg::OnButton1() //发送按钮的函数
{
// TODO: Add your control notification handler code here
int n;
char message[1000];
CString to_name;
UpdateData(TRUE);
if(m_SL==TRUE) //私聊
{
if(m_USER.GetCurSel()>=0)
{
m_USER.GetText(m_USER.GetCurSel(),to_name);
m_IDC_EDIT_MESSAGE=myServerSocket->NikeName+"悄悄对"+to_name+"说"+m_IDC_EDIT_MESSAGE;
m_IDC_LIST_CHATBOX_CONTROL.AddString(m_IDC_EDIT_MESSAGE);
m_IDC_EDIT_MESSAGE="PrivChat|"+to_name+"|"+m_IDC_EDIT_MESSAGE;
n=m_IDC_EDIT_MESSAGE.GetLength();
sprintf(message,"%s",m_IDC_EDIT_MESSAGE.GetBuffer(n));
message[n]=0;
}
else
{
AfxMessageBox("没有选择私聊对象!");
return ;
}
}
else
{
m_IDC_EDIT_MESSAGE=myServerSocket->NikeName+"对大家说: "+m_IDC_EDIT_MESSAGE;
n=m_IDC_EDIT_MESSAGE.GetLength();
sprintf(message,"%s",m_IDC_EDIT_MESSAGE.GetBuffer(n));
message[n]=0;
}
if (myServerSocket->Send(message,n+1))
{
m_IDC_EDIT_MESSAGE="";
UpdateData(FALSE);
}
else
{
AfxMessageBox("网络传输错误!");
}
}
void CChatRoomDlg::OnButton2() //离开按钮的函数
{
// TODO: Add your control notification handler code here
//发送断开信息
char message2[20];
strcpy(message2,"CLOSEUSE:");
strcat(message2,myServerSocket->NikeName);
if (myServerSocket->Send(message2,20))
{
}
else
{
AfxMessageBox("网络传输错误!!!");
}
myServerSocket->Close();
CDialog::OnOK();
}
BOOL CChatRoomDlg::GetMessage()//显示聊天信息
{
char buff[2000];
char name[20];
int count;
count=myServerSocket->Receive(buff,2000);
buff[count]=0;
char Flag[10];
for(int i=0;i<8;i++) Flag[i]=buff[i];
Flag[8]=0;
if(strcmp(Flag,"USERLIST")==0)//新用户昵称列表
{
m_USER.ResetContent();
CString sTemp;
int j=8;
for(i=8;buff[i]!=0;i++)
{
if(buff[i]=='|')//昵称分割符号
{
for(int m=0;j<i;j++,m++)
name[m]=buff[j];
name[m]='\0';
sTemp.Format("%s",name);//昵称
m_USER.AddString(sTemp);
j=i+1;
}
}
}
else //聊天信息
{
m_IDC_LIST_CHATBOX_CONTROL.AddString(buff);
}
return true;
}
void CChatRoomDlg::OnConnectButton()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
char *nikename,*address;
int n;
if (!myServerSocket->Create())
{
myServerSocket->Close();
AfxMessageBox("网络创建错误!!");
return;
}
n=m_IDC_EDIT_ADDRESS.GetLength();
address=new char(n+1);
sprintf(address,"%s",m_IDC_EDIT_ADDRESS.GetBuffer(n));
address[n]=0;
n=m_IDC_EDIT_NIKENAME.GetLength();
nikename=new char(n+1);
sprintf(nikename,"%s",m_IDC_EDIT_NIKENAME.GetBuffer(n));
nikename[n]=0;
if (!myServerSocket->Connect(address,6767))
{
myServerSocket->Close();
AfxMessageBox("网络连接错误,请检查服务器地址。");
return;
}
m_BNSend.EnableWindow(TRUE); //"发送信息"按钮状态为有效
m_BNExit.EnableWindow(TRUE); //"离开聊天室"按钮状态为有效
myServerSocket->NikeName=nikename;
//************发送新用户昵称
char message2[20];
strcpy(message2,"NEW_USER:");
strcat(message2,myServerSocket->NikeName);
if (myServerSocket->Send(message2,20))
{
}
else
{
AfxMessageBox("网络传输错误!!!");
}
}
// ChatRoomServer.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "ChatRoomServer.h"
#include "ChatRoomServerDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CChatRoomServerApp
CClientSocket curSocket; //全局CClientSocket
BEGIN_MESSAGE_MAP(CChatRoomServerApp, CWinApp)
//{{AFX_MSG_MAP(CChatRoomServerApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CChatRoomServerApp construction
CChatRoomServerApp::CChatRoomServerApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CChatRoomServerApp object
CChatRoomServerApp theApp;
/////////////////////////////////////////////////////////////////////////////
// CChatRoomServerApp initialization
BOOL CChatRoomServerApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
CChatRoomServerDlg dlg;
m_pMainWnd = &dlg;
curSocket.SetDlg(&dlg); //调用SetDlg()
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
致谢
首先要感谢王老师,是她在整个课程设计过程中给我提供了设计所需要的资料,帮助解答设计中遇到的问题。其次要感谢学校实验室,为我准备好了设计的学习环境,再次是要感谢和我一起做课程设计的同学们,正是有了他们,我们才在遇到问题时,相互鼓励,最终解决了问题,圆满地完成了课程设计的各项工作。