网络编程聊天程序设计文档.docx
《网络编程聊天程序设计文档.docx》由会员分享,可在线阅读,更多相关《网络编程聊天程序设计文档.docx(46页珍藏版)》请在冰豆网上搜索。
网络编程聊天程序设计文档
此文档为网络编程课程设计文档。
课程设计题目:
网络编程——聊天程序
程序运行环境:
UbuntuLinux
编程语言:
C语言
使用图形界面技术:
GTK+2.0
本人是大学本科生,所学知识有限,在此班门弄虎了,希望与大家在技术上多多交流。
此文所有权归本人所有,所以请勿抄袭。
网络编程
聊天程序设计报告
学院:
计算机与电子信息学院
专业名称:
网络工程
姓名:
网名:
书箱子
时间:
2011年1月
聊天程序设计任务书
1.设计题目
自己设计应用层协议,实现聊天程序,要求能够实现服务器能够同时处理多个客户的连接请求,实现客户端通过服务器的转发通信。
2.实验设计
2.1实验环境
1)虚拟机:
OracleVMVirtualBox
2)操作系统:
Ubuntu-10.10-desktop
2.2通信功能分析
实现聊天程序,并要考虑服务器能够同时处理多个客户的连接请求,实现客户端通过服务器的转发通信。
则通信过程分析如下:
服务器端:
1)建立socket绑定监听端口;
2)等待客户端的连接;
3)当客户与服务器建立连接后,服务器记录客户所有信息,如ID号;
4)当客户消息到达服务器后,服务器分析客户信息:
a.若为转发消息,根据消息格式提供的转发ID号,服务器立即查找转发列表,转发消息,若查找后发现需要转发的客户ID不存在则返回错误信息给原客户,提示不存在要发送的客户端ID号;
b.若为询问消息,服务器返回当前登录到服务器的所有客户的ID号;
c.若为退出消息,服务器清楚当前客户的所有信息,更新客户列表;
5)以上2-4步都是随时进行的:
a.任何新进客户端都能随时连接服务器;
b.任何以连接客户端都能发送消息,并且服务器能同时处理多个同时到来的消息,实现并发的处理客户端消息。
6)当服务器停止工作时,关闭监听套接字。
客户端:
1)建立连接到服务器的socket;
2)发送登录信息,信息中需要包含自身的ID号,使服务器能识别本机;
3)使用信号机制,随时能接收服务器传来的消息,不阻塞,及时的显示到来信息,并记录到来信息,以方便用户查看聊天记录;
4)能随时向服务器发送消息,有三种消息:
a.询问消息,询问服务器当前已连接到服务器的客户列表;
b.转发消息,告知服务器需要转发的ID号和信息,通过服务器进行聊天;
c.退出消息,告知服务器本客户端要退出聊天;
5)要获取当前以登录服务器的客户列表,则会发送询问消息,得到返回结果后更新本地的用户登录列表;
6)当想退出时,向服务器端发送退出消息后,关闭套接字,关闭程序。
2.3设计协议
通过以上服务器与客户端功能的分析,要完成以上功能,需要设计一个通信协议。
1)通信消息协议格式
协议格式中需要包含的信息有:
用户的ID号、接收者的ID号、退出标记、查询标记、转发标记、信息内容。
所以构造如下:
a.协议使用基于文本的格式:
chat#####
b.相应使用到的结构体:
structChatMsg{
charID[20];//自己的ID号
chartoID[20];//需要发送的ID号
boolisQuit;//发送的是退出消息时为true
boolisInquiry;//发送的是询问消息时为true
charmsg[128];//需要发送的信息内容
};
c.字符串“chat”,是魔术字符串,因为不能确定消息在传送过程中是否会出现问题,所以使用一个字符串测试消息的正确性;
d.字符串“”,表明发送消息的客户端自己的ID号;
e.字符串“”,当要转发消息时,此字符串表明要转发的目的客户端ID号;
f.字符串“”,当被标识为‘q’时,表示退出,标识为‘i’时,表示询问,标识为‘u’时,表示默认转发,‘q’与‘i’可以同时放在一起,因为如果是询问消息,则不可能是退出消息,反之如果是退出消息就不可能是询问消息;
g.字符串“”,表示要发送的信息内容;
h.字符串协议中,使用‘#’字符表示分隔符,用以分隔各个字段。
2)通信消息协议实现
通过此协议格式,可以实现三种消息的通信(如客户ID:
wang,消息发送到ID:
zhao):
a.询问消息:
chat#wang#server#i##
b.转发消息:
chat#wang#zhao#u#hello.#
c.退出消息:
chat#wang#server#q##
消息协议的实现使用Coder.c实现消息的编码,此文件中有如下两个函数:
/*实现消息的编码,输入消息ChatMsg结构体,返回消息字符串与长度*/
size_tEncode(structChatMsg*cmsg,uint8_t*outBuf,size_tbufSize);
/*实现消息的解码,输入消息字符串,返回消息ChatMsg结构体与消息长度*/
intDecode(uint8_t*inBuf,size_tmSize,structChatMsg*cmsg);
2.4程序所使用技术
根据对通信功能的分析,需要用到相应的技术解决通信过程中需要完成的功能,经过分析,,发现需要完成的主要功能有四项:
1)基本消息传输;2)不阻塞接收消息;3)服务器并发处理多客户消息;4)程序界面易操作性。
本实验对应不同的功能,提供不同的技术解决方案,各项功能实现技术描述如下。
2.4.1基本消息传输
1)采用UDP协议传送消息,采用此技术传送消息主要考虑到其简单性,容易在程序中实现,并且不需要考虑面向连接所要解决的问题,如何建立与何时断开连接都是在用TCP时需要考虑的问题,还有就是消息的成帧,在TCP中需要考虑成帧的问题,在TCP中有两种解决办法,一直是以定界符的方式,另一种是以固定长度的方法,而在UDP中并不需要考虑这个问题,因为使用UDP传送时,消息的边界已经是确定的了,而无需再去指定,所以使用UDP只需要考虑如何编码协议即可,但采用UDP也有很大的问题,那就是UDP本身固有的缺点,消息传送的不安全性,无重传的机制,无法知道消息是否到达。
2)UDP技术的实现:
a.服务器端建立UDPsocket并绑定监听socket:
(核心代码)
server_socket=socket(servAddr->ai_family,servAddr->ai_socktype,
servAddr->ai_protocol);
bind(server_socket,servAddr->ai_addr,servAddr->ai_addrlen);
b.客户端建立UDPsocket并通过指定端口连接到服务器:
(核心代码)
my_socket=socket(servAddr->ai_family,servAddr->ai_socktype,
servAddr->ai_protocol);
2.4.2不阻塞接收消息
1)采用信号量机制,实现不阻塞接收消息,当有I/O信号到来的时候才调用相应的函数处理到来的消息,这样可以让出CPU去执行其他的代码,提高了程序的执行效率,这样作的好处显而易见。
在此程序中使用了GTK+2.0的图形界面编程技术,所以在实现上与书本所介绍的方法并不一致。
2)服务器端与客户端都实现了采用信号量机制的消息接收方式:
a.服务器端采用信号量机制接收信息:
(核心代码)
/*监听以创建的socket端口是否有输入信号准备好;
*若信号已准备好则调用线程处理函数ThreadHandle()处理到来消息*/
gdk_input_add(server_socket,GDK_INPUT_READ,ThreadHandle,NULL);
b.客户端采用信号量机制接收信息:
(核心代码)
/*监听以创建的socket端口是否有输入信号准备好;
*若信号已准备好则调用函数SIGReceive()处理到来消息*/
gdk_input_add(my_socket,GDK_INPUT_READ,SIGReceive,NULL);
2.4.3服务器并发处理多客户消息
1)服务器需要并发的处理多个客户同时发送信息的要求,考虑使用多线程技术,实现并发的处理信息,因为没有使用TCP协议来实现此程序,所以不能使用accpet函数接收客户端的连接,并单独的为每位客户端创建一个线程来处理消息的转发。
但在UDP的实现中一样需要用到线程技术来解决并发的处理客户消息,因为不同客户的消息可能会同时到达,若不采用多线程技术,则会出现如下这样的情况:
先前到达的消息还没处理完又有新的消息到达,而新到达的消息则会等待,若还有其它消息的到达,也都会等待,当客户端的数量达到一定程度时,如不使用线程技术处理到来的消息,则会使整个通信过程产生很大的时延,有可能还会使服务器瘫痪(如一个消息在处理过程中出错,程序暂停执行)。
所以考虑使用多线程技术实现客户端消息的并发处理。
2)服务器端多线程技术的实现,因为使用了GTK+2.0图形界面编程,所以在实现多线程上与课本上的实现不太相同:
(核心代码)
/*首先使用信号量技术,监听以创建的socket端口是否有输入信号准备好;
*若信号已准备好则调用线程处理函数“ThreadHandle()“处理到来消息*/
gdk_input_add(server_socket,GDK_INPUT_READ,ThreadHandle,NULL);
/*以下为线程处理函数,当有I/O信号到来时会调用此函数创建一个线程,
*实现消息的并发处理。
此线程完成对每个客户消息的处理,调HandleClient()函数*/
voidThreadHandle(){
g_thread_create((GThreadFunc)HandleClient,NULL,FALSE,NULL);
}
2.4.4程序界面易操作性
考虑到此实验实现起来的复杂性,并不是太困难,所以考虑使用图形界面的方式呈现此聊天程序,使程序有更好的用户体验,而不是面对一个由一堆字符组成的终端界面。
同时因为个人喜好问题,所以选择了Linux下的图形界面设计,采用GTK+进行图形界面设计,此处就不对GTK的编程细节进行描述,在实验运行结果图中会展示使用GTK+所创建的图形界面。
3、实验运行结果图
3.1实验准备
1)服务器监听端口号:
3693;服务器地址:
127.0.0.1、192.168.10.3、(在/etc/hosts中设置,因为程序使用了getaddrinfo()函数可以识别此地址)
2)客户端需要获得:
服务器的端口号与IP地址;客户端准备自己的ID号唯一识别一台主机,此实验中开始三个客户端分别为:
wang1客户、wang2客户、wang3客户。
3)实验所用程序名:
服务器端为ServerWindow,客户端为ClientWindow
3.2实验拓扑图
3.3通信过程截图
1)点击“Start“按钮,启动服务器,监听3693端口
图1服务器端界面
2)客户wang1,启动客户端程序,输入服务器IP地址127.0.0.1,端口号3693,客户ID为wang1,点击”Connect”按钮连接服务器,连接成功后会弹出对话框,通知连接成功。
图2客户端界面,wang1客户
3)客户wang2,启动客户端程序,输入服务器地址,端口号3693,客户ID为wang2,点击”Connect”按钮连接服务器,连接成功后会弹出对话框,通知连接成功。
(如图3)
4)客户wang3,启动客户端程序,输入服务器地址192.168.10.3,端口号3693,客户ID为wang3,点击”Connect”按钮连接服务器,连接成功后会弹出对话框,通知连接成功。
(如图4)
图3wang2客户图4wang3客户
5)当三个客户端都连接上服务器后,服务器列表增加了三个客户信息,同时客户点击刷新按钮“Refreshonlineusers”时,会获取服务器当前以登录客户端的客户ID
6)wang2与wang3向wang1发送消息如图7图8
图7wang2向wang1发送消息图8wang3向wang1发送消息
7)wang1接收到wang2与wang3发来的消息,消息会直接显示,查看聊天记录使用按钮“front”与“next”,如图9图10
图9按front按钮查看wang2的消息图10按next按钮查看wang3的消息
8)wang1客户端向一个不存在的客户端(如wang4)发送信息时,服务器会返回消息提示不存在此客户,如图11图12
图11向不存在客户端(wang4)发信息图12服务器返回信息,提示wang4不在线
9)wang1点击按钮“Close”退出聊天室,服务器刷新客户列表去除wang1用户,同时使用wang2向wang1发送信息,提示wang1不在线,如图13图14
图13服务器列表中已没有wang1用户图14向wang1发送信息提示wang1不在线
10)测试结果显示,服务器和客户端能完成聊天任务,实验成功。
4、实验程序文件与源代码
4.1实验所使用的文件
1)服务器端使用文件:
ChatComponent.hCoder.cMessageDialog.cServerWindow.c
2)客户端使用文件:
ChatComponent.hCoder.cMessageDialog.cSendAndReceive.c
ClientWindow.c
3)各文件说明:
a.ChatComponent.h此头文件中包含有:
对应本程序协议的结构体,与功能函数的声明,客户端与服务器端的这个文件并不完全相同;
b.Coder.c编码文件,使用本程序设计的协议进行编码与解码;
c.MessageDialog.c消息提示组件,用于弹出提示对话框,因使用频繁所以单独写成文件;
d.ServerWindow.c服务器端文件,存放服务器端程序原代码;
e.SendAndReceive.c客户端使用的文件,使用此文件进行信息的发送与接收;
f.ClientWindow.c客户端文件,存放客户端程序源代码;
4)编译所使用的命令:
服务器端编译命令:
gcc`pkg-configgtk+-2.0--cflags--libsgthread-2.0`-std=gnu99-o
ServerWindowServerWindow.cChatComponent.hCoder.cMessageDialog.c
服务器端程序名:
ServerWindow
客户端编译命令:
gcc`pkg-config--cflags--libsgtk+-2.0`-std=gnu99-oClientWindow
ClientWindow.cChatComponent.hCoder.cSendAndReceive.cMessageDialog.c
客户端程序名:
ClientWindow
4.2实验文件源代码
1)ChatComponent.h客户端
#ifndefCHATCOMPONENT_H_
#defineCHATCOMPONENT_H_
#include
#include
#include
#include
#include
#include
#include
//最大发送字符
#defineMAXBUF256
//最大记录信息条数
#defineMAXMSGNUM30
//消息协议所对应的结构体
structChatMsg{
charID[20];//idfromself
chartoID[20];//idfromanother
boolisQuit;//telltheserverquit
boolisInquiry;//inquiryserveronlineusers
charmsg[128];//chatmessage
};
//聊天历史记录
structRecvMsg{
charfromID[20];
charmsg[128];
};
//记录聊天历史记录的队列,使用数字队列
structQueueIndex{
intfront;
intrear;
};
//对话框消息
intSystemInfo(char*from,char*msg);
//消息的编码与解码函数
size_tEncode(structChatMsg*cmsg,uint8_t*outBuf,size_tbufSize);
intDecode(uint8_t*inBuf,size_tmSize,structChatMsg*cmsg);
//方便客户端对消息的发送与接收
intSendMsg(structaddrinfo*servAddr,intsocket,structChatMsg*cmsg);
intReceiveMsg(structaddrinfo*servAddr,intsocket,structChatMsg*cmsg);
#endif//CHATCOMPONENT_H_
2)ChatComponent.h服务器端
#ifndefCHATCOMPONENT_H_
#defineCHATCOMPONENT_H_
#include
#include
#include
#include
#include
#include
#include
#include
//最大发送字符
#defineMAXBUF256
//最大连接客户数
#defineMAXCONNECT30
//消息协议所对应的结构体
structChatMsg{
charID[20];//idfromself
chartoID[20];//idfromanother
boolisQuit;//telltheserverquit
boolisInquiry;//inquiryserveronlineusers
charmsg[128];//chatmessage
};
//记录连接客户的信息
structRecordClient{
charclntID[20];
structsockaddr_storageclntAddr;
socklen_tclntAddrLen;
};
//对话框消息
intSystemInfo(char*from,char*msg);
//消息的编码与解码函数
size_tEncode(structChatMsg*cmsg,uint8_t*outBuf,size_tbufSize);
intDecode(uint8_t*inBuf,size_tmSize,structChatMsg*cmsg);
#endif//CHATCOMPONENT_H_
3)Coder.c
#include
#include
#include
#include
#include
#include"ChatComponent.h"
/*消息采用文本编码方式.
*文本编码格式:
"chat####"*/
staticconstchar*MAGIC="chat";
staticconstchar*QUIT="q";
staticconstchar*INQUIRY="i";
staticconstchar*DELIMSTR="#";
/*编码函数,输入消息结构体变量,返回字符串*/
size_tEncode(structChatMsg*cmsg,uint8_t*outBuf,size_tbufSize){
uint8_t*bufPtr=outBuf;
longsize=(size_t)bufSize;
intrv=snprintf((char*)bufPtr,size,"%s#%s#%s#%s#%s#",MAGIC,cmsg->ID,cmsg->toID,
(cmsg->isQuit?
QUIT:
(cmsg->isInquiry?
INQUIRY:
"u")),cmsg->msg);
bufPtr+=rv;
return(size_t)(bufPtr-outBuf);
}
/*解码函数,输入字符串,返回消息结构体变量*/
intDecode(uint8_t*inBuf,size_tmSize,structChatMsg*cmsg){
char*token;
token=strtok((char*)inBuf,DELIMSTR);
//Checkformagic
if(token==NULL||strcmp(token,MAGIC)!
=0)return-1;
//GetID
token=strtok(NULL,DELIMSTR);
if(token==NULL)return-1;
strcpy(cmsg->ID,token);
strcat(cmsg->ID,"\0");
//GettoID
token=strtok(NULL,DELIMSTR);
if(token==NULL)return-1;
strcpy(cmsg->toID,token);
strcat(cmsg->toID,"\0");
//GetisQuitorisInquiry
token=strtok(NULL,DELIMSTR);
if(token==NULL)return-1;
if(strcmp(token,QUIT)==0){
cmsg->isQuit=true;
cmsg->isInquiry=false;
}elseif(strcmp(token,INQUIRY)==0){
cmsg->isQuit=false;
cmsg->isInquiry=true;
}else{
cmsg->isQuit=false;
cms