基于Socket的即时通讯系统.docx
《基于Socket的即时通讯系统.docx》由会员分享,可在线阅读,更多相关《基于Socket的即时通讯系统.docx(19页珍藏版)》请在冰豆网上搜索。
基于Socket的即时通讯系统
一、设计目的
通过综合课程设计,使学生能够运用《数字信号处理》、《信号与系统》、《通信原理》、《面向对象的程序设计》、《计算机通信网》、《通信协议开发及应用》等课程的知识来设计一个基于Socket的即时通讯系统,培养学生的动手能力以及分析问题、解决问题的能力。
二、设计内容
一个基于Socket的即时通讯系统的多人聊天软件
三、设计要求
(一)基本要求
1.熟练掌握面向对象的程序设计方法;
2.实现点对点通讯,能进行文字对话传输,包括客户端与服务器端;
3.能对系统参数进行配置。
(二)提高要求
1、实现文件、图片传输;
2、语音对话(两人及两人以上);
3、友好的对话界面。
四、设计原理
1TCP/IP简介
TCP/IP的历史要追溯到70年代中期,当时ARPA为了实现异种网之间的互连(interconnection)与互通(intercommunication),大力资助网间网技术的研究和开发,于1977年到1979年推出目前形式的TCP/IP体系结构和协议规范.到今天,TCP/IP技术以及Internet网间网已经为广大计算机工作者,机算机厂商和机算机用户所接受.据统计,到1990年,Internet以包含遍布欧美的五千个活动网络,超过三十万台机算机.作为一种事实上的工标准,TCP/IP技术方兴未艾.
2TCP/IP的网络分层结构
对TCP/IP协议来说,TCP提供传输层服务,IP提供网络层服务.TCP/IP协议组(或Internet协议组)的分层结构及其与OSI模型的对应关系如图4所示.图中有关协议的名称及其基本含义如下:
(1)TCP. 为传输控制协议(TransmissionControlProtocol).它是提供给用户进程的一个可靠的全双工字节流的面向连接的协议.大多数Internet应用程序使用TCP.因为TCP使用IP,所以整个Internet协议组也常称为TCP/IP协议组.
(2)UDP.为用户数据报协议(UserDatagramProtocol).
(3)ICMP.为网间报文控制协议(InternetControlMessageProtocol).
(4)IP.网间协议(InternetProtocol).IP协议是为TCP,UDP和ICMP提供分组发送服务协议.
(5)ARP.地址转换协议.
(6)RARP.反向地址转换协议.
3Socket编程界面
(1)Socket原理
Socket编程界面由4BSDUNIX首先提出,目的是解决网间网进程通信问题.Socket接口为进程间通信提供了一种新的手段,它不但能用于同一机器中的进程之间的通信,而且支持网络通信功能.Socket具有类型,反应了对用户透明的通信特性.
一个完整的Socket连接用一个相关描述:
{协议,本地地址,本地端口,远地地址,远地端口}
Socket是面向客户-服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用.
(2)Socket系统调用
不管Socket内部机制如何,它提供给应用程序员的最终界面是一组系统功能调用.下面,我们一一给出重要的Socket系统调用.
1.创建Socket-----socket()
调用格式如下:
sockid=socket(af,type,protocol)
af:
地址族,指本socket所用地址类型.
type:
类型,指创建socket的应用程序所希望的通信服务器类型.
protocol:
协议,指本socket请求的协议.
2.指定本地地址----bind()调用
bind()将本地socket地址与所创建的socket联系起来,即将本socket地址赋予socket,以指定本地半相关.bind()的作用相当于给socket命名,调用格式为:
bind(sockid,localaddr,addrlen)
sockid:
socket号.
localaddr:
本地socket地址.
addrlen:
地址长度.
3.建立socket连接----connect()与accept()调用
这两个系统调用用于完成整个相关的建立.其中connect用于建立连接.调用格为:
connect(sockid,destaddr,addrlen)
destaddr:
指向对方socket地址(信宿地址)结构的指针.
accept:
用于面向连接的服务器,其调用格式为:
newsock=accept(sockid,clientaddr,paddrlen)
clientaddr:
指向客户socket地址指针.
paddrlen:
客户socket地址长度.
4.listen()调用
此调用用于面向连接服务器,表明它愿意接收连接,listen()在accept()之前调用,格式为:
listen(sockid,quelen)
quelen:
请求队列长度.
5.发送数据----write(),writev(),send()与sendto(),sendmsg()
用于socket数据发送的系统调用一共有五个,其中三个,write(),writev()和send()用于面向连接传输,其余两个用于无连接传输.面向连接的调用可以不指定信宿地址,而无连接的调用必须指定.假如无连接socket的双方均调用过connect(),可以认为是建立有连接的socket,也可以面向连接调用发送数据.
三个面向连接调用三者的格式大致相同:
write(sockid,buff,bufflen):
缓冲发送
writev(sockid,iovector,vectorlen):
集中发送
send(sockid,buff,bufflen,flags):
可控缓冲发送
其中buff指向发送缓冲区的指针,bufflen是发送缓冲区大小.
用于无连接数据发送的调用有两个:
sendto(sockid,buff,bufflen,flags,dsadd,addrlen)
sendmsg(sockid,message,flags):
可控集中无连接发送.
6.接收数据----read(),readv(),recv()与recvfrom(),recvmsg()
接收数据与发送数据系统调用是一一对应的,两者参数的最大区别是,前者buffer是一个指针,其所指单元初值为欲读数据长度,调用后的值是实际读出的值.
4客户--服务器模型的Socket实现框架
1)客户--服务器模型时序图
下图是面向连接客户--服务器模型的典型时序图
服务器 客户
socket() socket()
bind() bind()
listen()
accept() 等待客户连接请求
阻塞 <━━━━━━━━━━━━━━━connect()
read()<━━━━━━━━━━━━━━━>write()
2)服务器socket地址的确定
在客户--服务器模型中,所有的作用者都是客户首先发起的(如连接请求,服务请求等),因此客户必须要知道服务器socket地址,另外,客户调用服务器之前,可以在命令行中给出服务器所在主机的域名,根据域名可以获得服务器主机的地址,系统调用为:
hp=gethostbyname(host).其中host可以是服务器主机域名,返回hp是一个指向主机地址结构的指针。
五、软件设计(附程序流程图、源程序清单)
1.程序流程图
2.源程序清单
voidCChatRoomDlg:
:
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);
}
BOOLCChatRoomDlg:
:
EnableWindow(UINTuID,BOOLbEnable)
{
returnGetDlgItem(uID)->EnableWindow(bEnable);
}
voidCChatRoomDlg:
:
ExtendDiaog(BOOLbShow)
{
staticCRectm_DlgRectLarge(0,0,0,0);
staticCRectm_DlgRectSmall(0,0,0,0);
staticCRectm_GroupRectLarge(0,0,0,0);
staticCRectm_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);
}
}
voidCChatRoomDlg:
:
OnBnClickedNetset()
{
if(bShowAll){
ExtendDiaog(FALSE);
}else{
ExtendDiaog(TRUE);//设置按键“网络设置”的作用
}
}
voidCChatRoomDlg:
:
OnBnClickedStartServer()
{
m_hListenThread=CreateThread(NULL,0,ListenThreadFunc,this,0,NULL);
}
voidCChatRoomDlg:
:
ShowMsg(CStringstrMsg)
{
m_MsgEdit.SetSel(-1,-1);
m_MsgEdit.ReplaceSel(strMsg+_T("\r\n"));
}
voidCChatRoomDlg:
:
RemoveClientFromArray(CClientItemin_Item)
{
for(intidx=0;idxCClientItemtItem=m_ClientArray.GetAt(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);
}
}
}
voidCChatRoomDlg:
:
OnBnClickedSendmsg()
{
CStringstrMsg;
GetDlgItemText(IDC_INPUT_MSG,strMsg);
if(m_bIsServer==TRUE){
strMsg=_T("张智超的服务器:
>")+strMsg;
ShowMsg(strMsg);
SendClientsMsg(strMsg);
}elseif(m_bIsServer==FALSE){
CStringstrTmp=_T("张智超的客户端:
>")+strMsg;
ShowMsg(strTmp);
intiSend=send(m_ConnectSock,(char*)strMsg.GetBuffer(),strMsg.GetLength()*sizeof(TCHAR),0);
strMsg.ReleaseBuffer();
}else{
AfxMessageBox(_T("请您先进入聊天室!
"));
}
SetDlgItemText(IDC_INPUT_MSG,_T(""));
}//socket基本应用
voidCChatRoomDlg:
:
OnBnClickedStartClient()
{
m_hConnectThred=CreateThread(NULL,0,ConnectThreadFunc,this,0,NULL);
}
voidCChatRoomDlg:
:
SendClientsMsg(CStringstrMsg,CClientItem*pNotSend)
{
TCHARszBuf[MAX_BUF_SIZE]={0};
_tcscpy_s(szBuf,MAX_BUF_SIZE,strMsg);
for(INT_PTRidx=0;idxif(!
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);
}
}
}
voidCChatRoomDlg:
:
OnEnChangeInputMsg()
{
CStringstrMsg;
GetDlgItemText(IDC_INPUT_MSG,strMsg);
if(strMsg.IsEmpty()){
EnableWindow(IDC_SENDMSG,FALSE);
}else{
EnableWindow(IDC_SENDMSG);
}
}
voidCChatRoomDlg:
:
StopClient()
{
bShutDown=TRUE;
DWORDdwRet=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;
}
voidCChatRoomDlg:
:
StopServer()
{
UINTnCount=m_ClientArray.GetCount();
HANDLE*m_pHandles=newHANDLE[nCount+1];
m_pHandles[0]=m_hListenThread;
for(intidx=0;idxm_pHandles[idx+1]=m_ClientArray.GetAt(idx).hThread;
}
bShutDown=TRUE;
DWORDdwRet=WaitForMultipleObjects(nCount+1,m_pHandles,TRUE,1000);
if(dwRet!
=WAIT_OBJECT_0){
for(INT_PTRi=0;iTerminateThread(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;
}
voidCChatRoomDlg:
:
OnBnClickedStopClient()
{
StopClient();
ShowMsg(_T("停止客户端成功!
"));
EnableWindow(IDC_START_CLIENT);
EnableWindow(IDC_STOP_CLIENT,FALSE);
}
voidCChatRoomDlg:
:
OnBnClickedStopServer()
{
StopServer();
ShowMsg(_T("停止服务器成功!
"));
EnableWindow(IDC_START_SERVER);
EnableWindow(IDC_STOP_SERVER,FALSE);
}
voidCChatRoomDlg:
:
OnBnClickedRadioClient()
{
intiRet=-1;
if(m_bIsServer==TRUE){
intiRet=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);
}
}
voidCChatRoomDlg:
:
OnBnClickedRadioServer()
{
intiRet=-1;
if(m_bIsServer==FALSE){
intiRet=MessageBox(_T("您正在聊天室中,确定退出吗?
"),_T("提示"),MB_OKCANCEL|MB_ICONWARN