多线程通讯-TCP聊天程序-VC++

来源:互联网 发布:cd projekt red知乎 编辑:程序博客网 时间:2024/06/04 18:45

操作系统课程设计——多线程通讯-TCP聊天程序-VC++

操作系统课程设计任务书


一、设计题目:多机进程通信

应用Socket进程通信技术编写聊天室程序,实现基于服务器的并发多机信息转发。如果各客户端在线则可以实时聊天、发送接收文件,如果某客户端离线,则发送给他的消息可由服务器端进行内容缓存,待重新连线后可以自动接收通过服务器转发的信息或文件。缓存与转发的控制算法可参考操作系统课程中生产者消费者进程同步方法、缓冲池技术等相关理论。
二、 设计思路和功能的详细描述
采用TCP协议,所以属于客户机/服务器模式,因此需要聊天服务器端和聊天客户端两个程序,实现的功能是:任意一台装有客户端程序的计算机都可以通过服务器端的IP地址与服务器相连,然后进入聊天室与连接到服务器的其他客户进行聊天。当客户聊天结束时,可以点断开与服务器断开连接,以释放进程让其他等待的客户进入聊天室,本聊天室最大同时支持50个客户端的连接,如果服务器配置较高可以修改程序来增加同时连接数。
三、采用的方法、技术、运行环境及其配置
本聊天程序采用TCP协议,用VC++编写,属于客户机/服务器模式。采用了多线程的机制。其中使用windows Sockets实现多台计算机(多个进程)间的通信,SOCKET实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个SOCKET接口来实现。客户机/服务器模式中客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务即对客户的请求作出适当的反应。本聊天程序就是基于这中思想实现的,程序分为两大部分:TCP聊天服务器端和TCP聊天客户端。两者都拥有各自的SOCKET接口,其中服务器端SOCKET接口需要绑定到固定地址上(实现语句:ock=Socket(AF_INET,SOCK_STREAM,0);),等待客户端的连接(实现语句:listen(sock,5);)。等待客户端的连接的过程就是通过多进程机制来实现的。
聊天程序是在VISUAL C++6.0上编译实现的,在WINDOWS2000,XP上测试运行成功。
对客户计算机配置无特殊要求,由于所设置的最大连接进程为50,所以对服务器要求也不高。
四、关键源程序及其详细的注释
<>、服务器端
1、Socket初始化
//初始化对话框
BOOL CCSocketDlg::OnInitDialog()
{

count=0;

m_list.InsertColumn(0,"消息");

m_list.SetColumnWidth(0,435);

m_edit.SetLimitText(99);

for (int i=0;i<50;i++)//初始化SOCKET数组


msgsock
=NULL;

serv.sin_addr.s_addr=htonl(INADDR_ANY); //设定地址

serv.sin_family=AF_INET;

serv.sin_port=5000;//htons(5000);

addlen=sizeof(serv);

m_button.EnableWindow(FALSE);

sock=socket(AF_INET,SOCK_STREAM,0); //创建socket

if (bind(sock,(sockaddr*)&serv,addlen)) //绑定

{


m_edit.SetWindowText("绑定错误");

}else

{

m_edit.SetWindowText("服务器创建成功");//显示提示信息,表示服务器创建成功

listen(sock,5); //开始侦听     


AfxBeginThread(&thread,0); //调用线程

}


return TRUE;

}
2、接收线程
//服务器线程
UINT thread(LPVOID p)
{

char buff[100];//定义缓冲区

CSize size;

size.cx=0;

size.cy=30;

int s=1,msgcount,loop=1,flag=0;
CCSocketDlg *dlg=(CCSocketDlg*)AfxGetApp()->GetMainWnd();//获得当前运行对话框句柄

msgcount=dlg->getcount();//获取还没有被占用的数组序号

if (msgcount==-1)//如果不等于-1,则表示,还有空缺SOCKET


loop=0;

if(loop)

{

s=1;

dlg->msgsock[msgcount]=accept(dlg->sock,(sockaddr*)&(dlg->serv),&(dlg->addlen));//
用空缺的SOCKET等待客户连接

if (dlg->msgsock[msgcount]==INVALID_SOCKET)

{


dlg->m_edit.SetWindowText("Error accept");//如果返回错误,则提示错误

}

else

{


AfxBeginThread(thread,0);//如果和客户端连接成功,则再次启动一个线程


dlg->SetForegroundWindow();//显示连机成功信息


dlg->m_list.InsertItem(dlg->count++,"连接成功");


dlg->m_list.InsertItem(dlg->count++,inet_ntoa(dlg->serv.sin_addr));


dlg->m_list.Scroll(size);


dlg->m_button.EnableWindow(TRUE);




while(s!=SOCKET_ERROR)//如果没有发生错误,则一直循环等待数据的到来

{


s=recv(dlg->msgsock[msgcount],buff,100,0); //循环接收数据


dlg->SetForegroundWindow();


if (s!=SOCKET_ERROR)如果接收成功,则显示接收到的数据


{



dlg->m_list.InsertItem(dlg->count++,buff);



dlg->m_list.Scroll(size);


dlg->sendtoall(dlg->msgsock[msgcount],buff);//发送信息到所有的客户端


}

}


send(dlg->msgsock[msgcount],"Disconnected",100,0);//如果发生错误,发送连接中断消息到客户端


dlg->m_list.InsertItem(dlg->count++,"Disconnected");//显示相关信息


dlg->m_list.Scroll(size);


dlg->msgsock[msgcount]=NULL;//将该SOCKET设定为空


for (int i=0;i<50;i++)//判断是否还有其他客户在保持连接


if (dlg->msgsock
!=NULL)


flag=1;

if (flag!=1)//如果没有客户连接了,则将发送按钮设置为不可用


dlg->m_button.EnableWindow(FALSE);


closesocket(dlg->msgsock[msgcount]);//关闭连接

}

}


AfxEndThread(0); //终止线程

return 0;
}
3、数据发送
当用户在文本框中输入要发送的信息后,然后单击“发送”按钮,则执行以下代码。
//发送数据
void CCSocketDlg::OnButton1()
{

char buff[100];

m_edit.GetWindowText(buff,99);//获得当前文本框中的信息

m_edit.SetWindowText("");//清空文本框的信息

m_list.InsertItem(count++,buff);//向列表框中插入要发送的数据

CSize size;

size.cx=0;

size.cy=30;

m_list.Scroll(size);

for (int i=0;i<50;i++)//循环向所有客户发送信息

{

if (msgsock
!=NULL)


send(msgsock
,buff,100,0);

}
}
<>、客户端
1、
连接服务器
连接服务器的程序是在“连接”按扭的单击事件中处理的。
//连接服务器,连接按扭处理事件
void CCSocketcliDlg::OnButton2()
{

char ipaddress[35];//定义标量以保存服务器地址

m_edit2.GetWindowText(ipaddress,30);//获得服务器地址

cli.sin_addr.s_addr=inet_addr(ipaddress);//设定SOCKET需要连接的地址

cli.sin_family=AF_INET;

cli.sin_port=5000;//htons(5000);//设定服务器端口

clisock=socket(AF_INET,SOCK_STREAM,0);
//创建socket

ee=1;

AfxBeginThread(thread,0); //启动线程
}
2、
接收数据线程
当用户单击“连接”按钮以后,程序进行相关设定,最后调用了“AfxBeginThread(thread,0);”启动了该线程。
UINT thread(LPVOID v)
{

char buff[100];

char array[25][30]=//定义数组用来存放一些IP地址

{"192.168.0.3",

…(这里省略了部分IP)


"192.168.0.30"};

CSize size;

size.cx=0;

size.cy=30;

int s=1,addcount=0;

CCSocketcliDlg *dlg=(CCSocketcliDlg*) AfxGetApp()->GetMainWnd();//获得对话框

dlg->m_connect.EnableWindow(FALSE);

dlg->m_disconnect.EnableWindow(TRUE);

while(connect(dlg->clisock,(sockaddr*)&(dlg->cli),sizeof(dlg->cli)) && dlg->ee!=0) //连接到服务器

{


dlg->m_edit.SetWindowText("等待.....");

for (int i=0;i<=65000;i++)//空循环


for(int j=0;j<=200;j++);

if (addcount==25)


addcount=0;


dlg->cli.sin_addr.s_addr=inet_addr(array[addcount++]);//如果连接不成功,则连接下一个地址

}
             

if (dlg->ee==1)//如果连接成功,则显示相关信息


dlg->m_list.InsertItem(dlg->count++,"连接成功");


dlg->m_button1.EnableWindow(TRUE);//设定发送按钮为可用状态     


dlg->SetForegroundWindow();


while(s!=SOCKET_ERROR && dlg->ee!=0) //循环获得数据

{


s=recv(dlg->clisock,buff,100,0); //调用recv函数接收数据


dlg->SetForegroundWindow();

if (s!=SOCKET_ERROR && dlg->ee!=0)//如果没有发生错误,同时没有断开连接,则显示接收到的数据


dlg->m_list.InsertItem(dlg->count++,buff);


dlg->m_list.Scroll(size);

}

send(dlg->clisock,"Disconnected",100,0);//如果发生错误,发送断开命令

dlg->m_button1.EnableWindow(FALSE);//设定相关控件属性

dlg->m_connect.EnableWindow(TRUE);

dlg->m_disconnect.EnableWindow(FALSE);

closesocket(dlg->clisock);//关闭SOCKET

AfxEndThread(0);//终止该线程

return 0;
}
3、
数据发送
数据发送就是客户端向服务器及其他客户端发送信息,其中向其他用户发送信息是通过服务器实现的,因此,客户只需要将消息发送到服务器就可以了。
//用户单击发送按扭,发送信息
void CCSocketcliDlg::OnButton1()
{

char buff[100];

CSize size;

size.cx=0;

size.cy=30;

m_edit.GetWindowText(buff,99); //获得发送信息


m_edit.SetWindowText("");

m_list.InsertItem(count++,buff);

m_list.Scroll(size);

send(clisock,buff,100,0); //发送数据
}
五、执行结果及其分析。
1、开启服务器端聊天程序。

 


 附件: 您所在的用户组无法下载或查看附件


图一:创建服务器端(提示:服务器创建成功)


服务器端创建成功,并自动绑定了服务器的IP。等待客户端的连接请求,此时的发送按扭是不可用的,当有客户端连接上时发送按扭就变为可用。在下面的编辑框里输入信息就可以发送了。

 


 附件: 您所在的用户组无法下载或查看附件


图二:有客户端连接成功(IP地址为服务器地址)


 


 附件: 您所在的用户组无法下载或查看附件


图三:服务器端发送“你好”并接受“客户端回复”两条信息


 


 附件: 您所在的用户组无法下载或查看附件


图四:有客户端断开连接(提示:Disconnected)


2、客户端程序运行。
客户端程序中输入服务器端所在的IP,然后点连接,如果成功就会提示连接成功,然后在下面的编辑框里输入信息就可以发送了。如果连接不成功就会提示等待,此时有可能是服务器IP输入错误或是服务器端达到了最大的进程。客户端程序可以在程序源代码中预先设置一批服务器的IP,然后点连接,如果当前显示的服务器不能连接成功,则程序自动连接预先设置的第二个IP,一直循环测试连接,当与一台服务器连接成功为时,提示连接成功,并停止循环。

 


 附件: 您所在的用户组无法下载或查看附件


图五:客户端运行(服务器地址显示的为预先设置的IP)


 


 附件: 您所在的用户组无法下载或查看附件

图六:客户端连接成功


 附件: 您所在的用户组无法下载或查看附件


 


图七:客户端发送“你好!”并接受“服务器端回复”两条信息


六、心得体会
经过本次课程设计,基本掌握了Winsock编程的应用,对windows API函数的调用有了一定的了解,面对当前网上流行各种聊天室有了一定的认识,不再是那么神秘。另外就是对基于服务器的并发多机信息转发技术的了解。在聊天室程序中是这样实现的:当某一客户机向其它客户机发送信息时,不是直接发给它,而是先发给服务器,然后由服务器分发给其它所有连接到服务器的客户机。还有就是对VC++的学习,通过这次课设让我对VC++有了更深的认识,也了解到了它的功能的强大。最后最重要的是将操作系统课程中所学到了理论知识应用到了实践,对其有了更深刻的认识。
七、主要参考资料:
1、计算机操作系统教程;
2、Visual c++网络通讯协议分析与应用实现(人民邮电出版社),汪晓平 钟军 编著;
3、MSDN参考。
转至:http://www.youzaiyouzai.cn/showtopic.aspx?forumid=56&topicid=1570&go=next
原创粉丝点击