评阅教师评语: | 课程设计成绩 | |
考勤成绩 | ||
实做成绩 | ||
报告成绩 | ||
总评成绩 | ||
指导教师签名: |
设 计 报 告
论文题目: 基于Socket的即时通讯系统
学院(系):
班 级:
学生姓名: 学号
指导教师:
时间: 2011 年 6月 7日 到 2011 年 6 月 17 日 |
基于Socket的即时通讯系统
二、设计目的
通过综合课程设计,使学生能够运用《数字信号处理》、《信号与系统》、《通信原理》、《面向对象的程序设计》、《计算机通信网》、《通信协议开发及应用》等课程的知识来设计一个基于Socket的即时通讯系统,培养学生的动手能力以及分析问题、解决问题的能力。
三、设计要求
(一)基本要求
1.熟练掌握面向对象的程序设计方法;
2.实现点对点通讯,能进行文字对话传输,包括客户端与服务器端;
3.能对系统参数进行配置。
(二)提高要求
1、实现文件、图片传输;
2、语音对话(两人及两人以上);
3、友好的对话界面。
四、 设计原理
Socket原理,大致分为以下几个步骤。
服务器端的步骤如下。
(1)首先,在实用Socket之前,要首先初始化Socket,就是实用AfxSocketInit()函数。
(2)在初始化完成以后,就可以建立服务端的Socket,然后实用该Sokcet开始侦听整个网络中的连接请求。
(3)当检测到来自客户端的连接请求时,向客户端发送收到连接请求的信息,并建立与客户端之间的连接。连接的过程中,在MFC的框架中会触发一个前面创建的服务端Socket的消息响应函数OnAccept(),我们将建立的连接的代码放到该响应函数里面,在建立连接的过程中,会产生一个新的 Socket,我们使用找个Socket来进行数据的通信。
(4)在通信的过程中,服务器端的产生的新的Socket会通过一个消息响应函数OnReceive()来接受到达的数据。数据的发送可以使用Send()来完成
(5)当完成通信后,服务器关闭与客户端的Socket连接。
客户端的步骤如下。
(1)同样的,初始化Socket,并建立客户端的Socket,确定要连接的服务器的主机名和端口。
(2)发送连接请求到服务器(MFC中使用Connect()),并等待服务器的回馈信息。
(3)连接成功后,与服务器进行数据的交互。
(4)数据的读取同服务端一样,也是通过OnReceive()来完成的,数据的发送通过Send()即可。
(5)数据处理完毕后,关闭自身的Socket连接。
5、 软件设计
6、 调试过程
七、 实验结果分析
一,如何更好的检测TCP连接是否正常
这方面问题,我上网查了很久,一般来说比较成熟的有两种方法:
1是在应用层制定协议,发心跳包,这也是C#,JAVA等高级语言比较常用的方法。客户端和服务端制定一个通讯协议,每隔一定时间(一般15秒左右),由一方发起,向对方发送协议包;对方收到这个包后,按指定好的通讯协议回一个。若没收到回复,则判断网络出现问题,服务器可及时的断开连接,客户端也可以及时重连。
2通过TCP协议层发送KeepAlive包。这个方法只需设置好你使用的TCP的KeepAlive项就好,其他的操作系统会帮你完成。操作系统会按时发送KeepAlive包,一发现网络异常,马上断开。我就是使用这个方法,也是重点向大家介绍的。
使用第二种方法的好处,是我们在应用层不需自己定协议,通信的两端,只要有一端设好这个值,两边都能及时检测出TCP连接情况。而且这些都是操作系统帮你自动完成的。
八、 体会和建议
通过这次课程设计,让我加深了对面向对象编程的了解,通过使用dephi编程,掌握了它的的基本应用,也懂得了要想熟练编程不是一朝一夕的事情,一份付出一份收获,只有加强以后的上机编程时间,才能让自己的编程能力的到进一步的提高。此外,利用Delphi 下的Socket 控件,可以很好地解决局域网内的实时通信问题,目前在各中小企事业单位中已经得到广泛应用. 系统具有良好的扩充性,例如可实现对信息及数据的加密,充分保障系统的安全性;可进行多媒体等文件的实时在线传输等,具有较好的应用前景.
九、 参考文献
[ 1] MARCO CANTU ,罗征. Delphi 7 从入门到精通[M] . 北京:电子工业出版社,2003.
[ 2] 方军,吴晓冰,沈金龙. 中间件TCP/ IP 网络接口的实现[J ] . 网络报,1999 , (19) :33234.
[ 3] 王红霞,姚家亮. 网络环境下基于Winsock 的进程通信方法[J ] . 计算机时代,2001 , (10) :26227.
[ 4] 谢希仁.计算机网络[M].第4 版.北京:电子工业出版社,2003.100-113.
[ 5 ] 赵秀英.Delphi 网络高级编程[M].北京:人民邮电出版社?,2001.83-85.
[ 6] 申普兵,行明顺,王兆祥,等.计算机网络与通信[M].北京:人民邮电出版社,2006.9-11.
附录:课程设计中要用到的Winsock函数
WSAStartup | 初始化socket库 |
WSACleanup | 结束socket库的使用 |
socket | 为所要进行的网络通信建立标识符 |
connect | 连接到远程主机 |
closesocket | 结束通信,关闭标识符 |
bind | 将IP地址、TCP端口号与套接字标识符绑定 |
listen | 将接受套接字置于被动模式, 将服务器置于侦听状态,并指定允许的连接数 |
accept | 接受下一个呼入的连接 |
recv | 接收传入的TCP数据 |
recvfrom | 接收传入的UDP数据 |
select | 在指定的套接字集准备好接收数据前一直等待 |
send | 发送TCP数据 |
sendto | 发送UDP数据 |
shutdown | 释放TCP连接 |
getpeername | 从套接字中获取对等方的端口地址 |
setsocketopt | 获取当前套接字的可变选项 |
gethostbyname inet_addr | 把域名转换成网络IP地址 把用点分十进制表示的IP地址转换成网络IP地址 |
getservbyname getprotobyname | 获得服务器的端口号 把TCP、UDP转换成相应的服务号码(interger) |
void CChatRoomDlg::DlgAllInit()
{
CheckRadioButton(IDC_RADIO_CLIENT, IDC_RADIO_SERVER, IDC_RADIO_CLIENT);
SetDlgItemText(IDC_IP_ADDR, _T("127.0.0.1")); // 初始化ip地址为本机地址。
SetDlgItemText(IDC_CONNECT_PORT, _T("5566")); // 初始化端口。
SetDlgItemText(IDC_LISTEN_PORT, _T("5566"));
EnableWindow(IDC_STOP_CLIENT, FALSE);
EnableWindow(IDC_LISTEN_PORT, FALSE);
EnableWindow(IDC_STOP_SERVER, FALSE);
EnableWindow(IDC_START_SERVER, FALSE);
EnableWindow(IDC_STATIC_LISTEN_PORT, FALSE); // 初始化按键启用or禁用。
EnableWindow(IDC_SENDMSG, FALSE);
}
BOOL CChatRoomDlg::EnableWindow(UINT uID, BOOL bEnable)
{
return GetDlgItem(uID)->EnableWindow(bEnable);
}
void CChatRoomDlg::ExtendDiaog(BOOL bShow)
{
static CRect m_DlgRectLarge(0, 0, 0, 0);
static CRect m_DlgRectSmall(0, 0, 0, 0);
static CRect m_GroupRectLarge(0, 0, 0, 0);
static CRect m_GroupRectSmall(0, 0, 0, 0); // 设置 窗口大小
if ( m_DlgRectLarge.IsRectNull() ) {
GetWindowRect(&m_DlgRectLarge);
m_DlgRectSmall = m_DlgRectLarge;
m_DlgRectSmall.right -= 220;
::GetWindowRect(GetDlgItem(IDC_FRAME)->GetSafeHwnd(), &m_GroupRectLarge);
m_GroupRectSmall = m_GroupRectLarge;
m_GroupRectSmall.right -= 220; // 设置 窗口 伸缩大小范围
}
if ( bShow ) {
bShowAll = TRUE;
SetWindowPos(NULL, 0, 0, m_DlgRectLarge.Width(), m_DlgRectLarge.Height(), SWP_NOZORDER | SWP_NOMOVE);
::SetWindowPos(GetDlgItem(IDC_FRAME)->GetSafeHwnd(), NULL, 0, 0, m_GroupRectLarge.Width(), m_GroupRectLarge.Height(), SWP_NOZORDER | SWP_NOMOVE);
}else{
bShowAll = FALSE;
SetWindowPos(NULL, 0, 0, m_DlgRectSmall.Width(), m_DlgRectSmall.Height(), SWP_NOZORDER | SWP_NOMOVE);
::SetWindowPos(GetDlgItem(IDC_FRAME)->GetSafeHwnd(), NULL, 0, 0, m_GroupRectSmall.Width(), m_GroupRectSmall.Height(), SWP_NOZORDER | SWP_NOMOVE);
}
}
void CChatRoomDlg::OnBnClickedNetset()
{
if ( bShowAll ) {
ExtendDiaog(FALSE);
}else{
ExtendDiaog(TRUE); // 设置按键“网络设置”的作用
}
}
void CChatRoomDlg::OnBnClickedStartServer()
{
m_hListenThread = CreateThread(NULL, 0, ListenThreadFunc, this, 0, NULL);
}
void CChatRoomDlg::ShowMsg(CString strMsg)
{
m_MsgEdit.SetSel(-1, -1);
m_MsgEdit.ReplaceSel(strMsg+_T("\\r\\n"));
}
void CChatRoomDlg::RemoveClientFromArray(CClientItem in_Item)
{
for( int idx = 0; idx if ( tItem.m_Socket == in_Item.m_Socket && tItem.hThread == in_Item.hThread && tItem.m_strIp == in_Item.m_strIp ) { m_ClientArray.RemoveAt(idx); } } } void CChatRoomDlg::OnBnClickedSendmsg() { CString strMsg; GetDlgItemText(IDC_INPUT_MSG, strMsg); if ( m_bIsServer == TRUE ) { strMsg = _T("重庆理工大学聊天室的服务器:>") + strMsg; ShowMsg(strMsg); SendClientsMsg(strMsg); }else if (m_bIsServer == FALSE) { CString strTmp = _T("重庆理工大学聊天室的客户端:>") + strMsg; ShowMsg(strTmp); int iSend = send(m_ConnectSock, (char *)strMsg.GetBuffer(), strMsg.GetLength()*sizeof(TCHAR), 0); strMsg.ReleaseBuffer(); }else{ AfxMessageBox(_T("请您先进入聊天室!")); } SetDlgItemText(IDC_INPUT_MSG, _T("")); } // socket 基本应用 void CChatRoomDlg::OnBnClickedStartClient() { m_hConnectThred = CreateThread(NULL, 0, ConnectThreadFunc, this, 0, NULL); } void CChatRoomDlg::SendClientsMsg(CString strMsg, CClientItem *pNotSend) { TCHAR szBuf[MAX_BUF_SIZE] = {0}; _tcscpy_s(szBuf, MAX_BUF_SIZE, strMsg); for( INT_PTR idx = 0; idx < m_ClientArray.GetCount(); idx++ ) { if ( !pNotSend || pNotSend->m_Socket != m_ClientArray.GetAt(idx).m_Socket || pNotSend->hThread != m_ClientArray.GetAt(idx).hThread || pNotSend->m_strIp != m_ClientArray.GetAt(idx).m_strIp) { send(m_ClientArray.GetAt(idx).m_Socket, (char *)szBuf, _tcslen(szBuf)*sizeof(TCHAR), 0); } } } void CChatRoomDlg::OnEnChangeInputMsg() { CString strMsg; GetDlgItemText(IDC_INPUT_MSG, strMsg); if ( strMsg.IsEmpty() ) { EnableWindow(IDC_SENDMSG, FALSE); }else{ EnableWindow(IDC_SENDMSG); } } void CChatRoomDlg::StopClient() { bShutDown = TRUE; DWORD dwRet = WaitForSingleObject(m_hConnectThred, 1000); if ( dwRet != WAIT_OBJECT_0 ) { TerminateThread(m_hConnectThred, -1); closesocket(m_ConnectSock); } m_hConnectThred = NULL; m_ConnectSock = INVALID_SOCKET; m_bIsServer = -1; bShutDown = FALSE; } void CChatRoomDlg::StopServer() { UINT nCount = m_ClientArray.GetCount(); HANDLE *m_pHandles = new HANDLE[nCount+1]; m_pHandles[0] = m_hListenThread; for( int idx = 0; idx < nCount; idx++ ) { m_pHandles[idx+1] = m_ClientArray.GetAt(idx).hThread; } bShutDown = TRUE; DWORD dwRet = WaitForMultipleObjects(nCount+1, m_pHandles, TRUE, 1000); if ( dwRet != WAIT_OBJECT_0 ) { for( INT_PTR i = 0; i < m_ClientArray.GetCount(); i++ ) { TerminateThread(m_ClientArray.GetAt(i).hThread, -1); closesocket(m_ClientArray.GetAt(i).m_Socket); } TerminateThread(m_hListenThread, -1); closesocket(m_ListenSock); } delete [] m_pHandles; m_hListenThread = NULL; m_ListenSock = INVALID_SOCKET; m_bIsServer = -1; bShutDown = FALSE; } void CChatRoomDlg::OnBnClickedStopClient() { StopClient(); ShowMsg(_T("停止客户端成功!")); EnableWindow(IDC_START_CLIENT); EnableWindow(IDC_STOP_CLIENT, FALSE); } void CChatRoomDlg::OnBnClickedStopServer() { StopServer(); ShowMsg(_T("停止服务器成功!")); EnableWindow(IDC_START_SERVER); EnableWindow(IDC_STOP_SERVER, FALSE); } void CChatRoomDlg::OnBnClickedRadioClient() { int iRet = -1; if ( m_bIsServer == TRUE ) { int iRet = MessageBox(_T("您是聊天室的服务器端,如果您退出,所有的客户端都将掉线!\\r\\n您确定退出吗?"), _T("提示"), MB_OKCANCEL | MB_ICONWARNING); if ( iRet == IDOK ) { StopServer(); }else{ CheckRadioButton(IDC_RADIO_CLIENT, IDC_RADIO_SERVER, IDC_RADIO_SERVER); } } if ( iRet == IDOK || m_bIsServer == -1 ) { EnableWindow(IDC_IP_ADDR); EnableWindow(IDC_CONNECT_PORT); EnableWindow(IDC_STATIC_SERVER_IP); EnableWindow(IDC_STATIC_SERVER_PORT); EnableWindow(IDC_START_CLIENT); EnableWindow(IDC_STOP_CLIENT, FALSE); EnableWindow(IDC_LISTEN_PORT, FALSE); EnableWindow(IDC_STOP_SERVER, FALSE); EnableWindow(IDC_START_SERVER, FALSE); EnableWindow(IDC_STATIC_LISTEN_PORT, FALSE); } } void CChatRoomDlg::OnBnClickedRadioServer() { int iRet = -1; if ( m_bIsServer == FALSE ) { int iRet = MessageBox(_T("您正在聊天室中,确定退出吗?"), _T("提示"), MB_OKCANCEL | MB_ICONWARNING); if ( iRet == IDOK ) { StopClient(); }else{ CheckRadioButton(IDC_RADIO_CLIENT, IDC_RADIO_SERVER, IDC_RADIO_CLIENT); } } if ( iRet == IDOK || m_bIsServer == -1) { EnableWindow(IDC_LISTEN_PORT); EnableWindow(IDC_STOP_SERVER, FALSE); EnableWindow(IDC_START_SERVER); EnableWindow(IDC_STATIC_LISTEN_PORT); EnableWindow(IDC_IP_ADDR, FALSE); EnableWindow(IDC_CONNECT_PORT, FALSE); EnableWindow(IDC_STATIC_SERVER_IP, FALSE); EnableWindow(IDC_STATIC_SERVER_PORT, FALSE); EnableWindow(IDC_START_CLIENT, FALSE); EnableWindow(IDC_STOP_CLIENT, FALSE); } } void CChatRoomDlg::OnBnClickedCancel() { if ( m_bIsServer == TRUE ) { StopServer(); }else if ( m_bIsServer == FALSE ) { StopClient(); } OnCancel(); } void CChatRoomDlg::OnBnClickedOther() { CPoint pt; CRect mRect; CMenu mMenu, *pMenu = NULL; GetDlgItem(IDC_OTHER)->GetWindowRect(&mRect); pt = mRect.BottomRight(); pt.y = mRect.top+10; mMenu.LoadMenu(IDR_MENU1); pMenu = mMenu.GetSubMenu(0); pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); } BOOL CChatRoomDlg::TrayMyIcon(BOOL bAdd) { BOOL bRet = FALSE; NOTIFYICONDATA tnd; tnd.cbSize = sizeof(NOTIFYICONDATA); tnd.hWnd = GetSafeHwnd(); tnd.uID = IDR_MAINFRAME; if ( bAdd == TRUE ) { tnd.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; tnd.uCallbackMessage = WM_TRAYICON_MSG; tnd.hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)); _tcscpy_s(tnd.szTip, sizeof(tnd.szTip), _T("聊天室v1.0")); ShowWindow(SW_MINIMIZE); ShowWindow(SW_HIDE); bRet = Shell_NotifyIcon(NIM_ADD, &tnd); }else{ ShowWindow(SW_SHOWNA); SetForegroundWindow(); bRet = Shell_NotifyIcon(NIM_DELETE, &tnd); } return bRet; } void CChatRoomDlg::OnMenuTrayinco() { TrayMyIcon(); } LRESULT CChatRoomDlg::OnTrayCallBackMsg(WPARAM wparam, LPARAM lparam) { switch(lparam) { case WM_RBUTTONUP: { CMenu mMenu, *pMenu = NULL; CPoint pt; mMenu.LoadMenu(IDR_MENU2); pMenu = mMenu.GetSubMenu(0); GetCursorPos(&pt); SetForegroundWindow(); pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); break; } case WM_LBUTTONDBLCLK: ShowWindow(SW_RESTORE); SetForegroundWindow(); TrayMyIcon(FALSE); break;