LINUX下网络即时聊天程序.docx
《LINUX下网络即时聊天程序.docx》由会员分享,可在线阅读,更多相关《LINUX下网络即时聊天程序.docx(23页珍藏版)》请在冰点文库上搜索。
LINUX下网络即时聊天程序
基于TCP协议的简易网络聊天程序
一、设计原理:
即时通信(IM)是指能够即时发送和接收互联网消息等的业务。
自1998年面世以来,特别是近几年的迅速发展,即时通信的功能日益丰富,逐渐集成了电子邮件、博客、音乐、电视、游戏和搜索等多种功能。
即时通信不再是一个单纯的聊天工具,它已经发展成集交流、资讯、娱乐、搜索、电子商务、办公协作和企业客户服务等为一体的综合化信息平台。
本课题实现简单的及时通讯中的聊天服务。
问题定义:
本课题要解决的问题是提供用户自由向另外一个不同的用户发消息的同时接收来自其他用户的消息。
2.可行性研究:
要实现即时通讯的聊天模块,可以在LINUX下搭建服务器,在提供给用户客户端程序。
客户端和服务器之间通过TCP协议实现在线聊天。
需求分析:
(1)为了实现即时通讯的聊天服务,聊天服务器必须能够同时接入多个用户,所以要支持并发,允许同时在线客户端数量至少大于3个。
(2)要求服务器能接收多个用户的接入请求同时处理已经建立连接的用户的操作。
(3)接收用户发过来的信息。
(3)正确转发信息到达正确的用户。
(4)提供简单的用户操作指令比如显示实时在线的用户。
(5).来自不同用户的信息的转发的同步控制(6)。
给每个用户一个唯一的ID。
4.总体设计
首先我们应该在设计LINUX平台设计服务器并且C语言编程。
在实现并行处理时可以使用多进程也可以使用多线程,多线程可以方便的实现不同连接间简易的通信,系统开销小所以这里选用它。
在连接的协议选择上,因为传送数据量小,这里选择面向连接可靠传输的TCP协议,相比将套接字嵌入FILE留种,这里使用调用常用的tcp的套接字API(send,recv)读写缓冲区实现连接的方法.
5.详细设计
客户端:
首先用户提供服务器IP和端口,然后创建套接字并连接到服务器,连接成功给予操作界面。
设计用户登陆接口函数,发送名字用于登陆处理。
主进程挂载随时接收用户键盘输入,并调用SEND()函数处理发送和指令操作。
创立一个线程用于挂载阻塞的recv();将收到的信息打印。
和端口,主进程监听和循环接IP服务器:
服务器创建通用的服务器套接字绑定
受客户端接入请求。
接入用户后创建线程传入套接字,并接收用户的登陆。
登陆处理后讲该线程绑定一个用户ID,并创建一个线程用于挂载RECV();第一个线程处理要发给本连接绑定用户的转发服务。
同时设计在线用户的更新和打印处理。
使用互斥锁来解决写入的同步控制问题,在更改全局变量时候锁住,当转发完毕时,解锁。
6.编码和单元测试
编码见代码和流程图。
测试没有截图,提列测试出现的部分问题:
(1)段错误(核心已转储),原因:
字符串处理函数字符串数组与字符串指针的调用问题。
改进:
数组统一换字符串指针,分配空间。
(2)初始值设定不是常量,原因初始化在main()开始前执行。
改进:
结构体分配在主函数里分配空间。
(3)当recv()从缓冲器COPY的数据产生粘包。
原因:
没有消息边界,解决:
每次收发大小固定住。
(4)连接关闭处理。
解决:
设置套接字收发的标志位获取退出原因,友好退出时套接字函数返回0。
二、运行方法说明、运行结果
运行方法:
linux下编译服务器gccserver.cdie.c-lpthread-oserver.out
Linux下编译客户端gccclient.cdie.c-lpthread-oclient.out
服务器运行server.out<端口号/服务名称>
客户端运行client.out<服务器地址><端口号/服务名称>
服务器运行成功后有提示,客户端运行成功并登陆成功后有欢迎界面。
用户可以输入LIST命令来获得在线用户列表。
用户可以输入CLOSE来退出程序。
运行结果:
能达到设计要求,运行速度正常。
三、流程图
客户端流程图
开始
创建、设置套接字
Connect();
创建、设置套接字
Nameland();
Pthread_creat();
执行threadmain()
接受键盘输定长数Recv(
打印信是否输close
yes
no
定长数Send()
Close(sock)
结束
服务器流程图
开始
创建、设置套接字
Bamd();
Listrren();
threadmain();执行Accept()
id传入套接字和
While
(1)
threadmain();执行id
线程指定
传入套接字创建clientsock
While
(1)
用户名登陆处理Pthread_craet();
Recv()定长数据
id
线程指If(recv!
=0
serversock)Clos
更新用户名列结
判断收到的Pthread_crreat();息根据不同容进行封
While
(1)
封装信息ID=id
If(ID==id)
Closclientsock)
更新用户列
Send(转发信
服务器
#include
#include
#include
#include
#include
#include
#include
#include
#includePractical.h
pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;//定义一个静态锁
char*rmessage;//当前受到来自客户端的源信息
intcurrentID=6;
char*clientlist[5];//用户组姓名
void*ThreadMain(void*arg);//挂send
void*ThreadMain2(void*arg);//挂阻塞rcvd
typedefstructThreadArgs{
intclntSock;//线程要处理的连接
intnameID;//用户在用户组的位置
}threadMessage;
intmain(intargc,char*argv[]){
if(argc!
=2)//Testforcorrectnumberofarguments
DieWithUserMessage(Parameter(s),);
char*servPort=argv[1];//Firstarg:
localport
rmessage=(char*)malloc(120);
memset(rmessage,0,120);
intservSock=SetupTCPServerSocket(servPort);
if(servSock<0)
DieWithUserMessage(SetupTCPServerSocket()failed,%unabletoestablish);
for(;;){//Runforever
intclntSock=AcceptTCPConnection(servSock);
threadMessage*threadArgs=(threadMessage*)malloc(
sizeof(threadMessage));
if(threadArgs==NULL)
DieWithSystemMessage(malloc()failed);
threadArgs->clntSock=clntSock;
pthread_tthreadID;
intreturnValue=pthread_create(&threadID,NULL,ThreadMain,threadArgs);
if(returnValue!
=0)
DieWithUserMessage(pthread_create()failed,strerror(returnValue));
//printf(withthread:
%ld\n,(longint)threadID);
}
free(rmessage);
close(servSock);
return0;
}
void*ThreadMain(void*threadArgs){
char*sname=(char*)malloc(20);
charrongname[]=henameexisted;
charrightname[]=landsuccess;
inti=0;
memset(sname,0,20);
pthread_detach(pthread_self());
intclntSock=((threadMessage*)threadArgs)->clntSock;
while(i!
=5){//判断用户名是否存在
ssize_tnameRcvd=recv(clntSock,sname,20,0);
if(nameRcvd<=0)
DieWithSystemMessage(
ecv()failed);
*(sname+nameRcvd)='\0';
for(i=0;i<5;i++){
if(clientlist[i]!
=NULL)
if(strcmp(sname,clientlist[i])==0){
if(send(clntSock,rongname,sizeof(rongname),0)<=0)
DieWithSystemMessage(sendname()failed);
牰湩晴尨用户名已经存在\n);
break;
}
}
}
for(i=0;i<5;i++){//指定用户名在用户组的位置
if(clientlist[i]==NULL)break;
}
if(send(clntSock,rightname,sizeof(rightname),0)<=0)
DieWithSystemMessage(sendrightname()failed);
牰湩晴尨接入用户名:
%s\n,sname);
clientlist[i]=(char*)malloc(20);
memset(clientlist[i],0,20);
strcpy(clientlist[i],sname);
//将该连接相关信息传给该连接的第二个线程
((threadMessage*)threadArgs)->nameID=i;
pthread_tthreadID;
intreturnValue=pthread_create(&threadID,NULL,ThreadMain2,threadArgs);
if(returnValue!
=0)
DieWithUserMessage(pthread_create()failed,strerror(returnValue));
while
(1){
if(clientlist[i]==NULL)break;
if(i==currentID){//判断是否是给本连接的信息,是就发出去
ssize_tnumBytesSent=send(clntSock,rmessage,120,0);
if(numBytesSent<0)
DieWithSystemMessage(send()failed);
elseif(numBytesSent!
=120)
DieWithUserMessage(send(),sentunexpectednumberofbytes);
//strcpy(rmessage,
ull);
currentID=6;//设置标志,防止在此发送
if(pthread_mutex_unlock(&mutex)!
=0)//转发完成就解锁
printf(%unlockfailed);
}
}
free(sname);
close(clntSock);
return(NULL);
}
void*ThreadMain2(void*threadArgs){
char*buffer=(char*)malloc(120);
memset(buffer,0,120);
char*y,*b;
intnameid,j;
pthread_detach(pthread_self());
intclntSock=((threadMessage*)threadArgs)->clntSock;
nameid=((threadMessage*)threadArgs)->nameID;
free(threadArgs);//Deallocatememoryforargument
while
(1){
ssize_tnumBytesRcvd;
inttotalRecv=0;
//getchar();
while(totalRecv<120){
numBytesRcvd=recv(clntSock,buffer+totalRecv,120-totalRecv,0);
if(numBytesRcvd==0)break;//判断客户关闭
if(numBytesRcvd<0)
DieWithSystemMessage(
ecv()failed);
totalRecv+=numBytesRcvd;
}
if(numBytesRcvd==0)break;
牰湩晴尨收到:
%s\n,buffer);
//问题4:
假设锁被线程1用了,同时线程2阻塞在LOCK,这时如果客户在send2次消息过来,线程2下次调用recv会不会把2次send的数据和起来一次放进缓冲区里?
if(pthread_mutex_lock(&mutex)!
=0)
printf(lockfailed);
if(strcmp(buffer,list)==0){//编写list命令
charlocate[3]={\
1:
};
瑳捲祰爨敭獳条?
服务器在线用户列表);
for(j=0;j<5;j++){
if(clientlist[j]!
=NULL){
strcat(rmessage,locate);
strcat(rmessage,clientlist[j]);
locate[1]++;
}
}
currentID=nameid;
continue;
}
//解析受到的信息,并编码要传递的信息
b=buffer;
y=strsep(&b,:
);
for(j=0;j<5;j++){//对话时无效用户名处理
if(clientlist[j]!
=NULL)
if(strcmp(y,clientlist[j])==0)break;
}
if(j==5){
currentID=nameid;
瑳捲祰爨敭獳条?
您要对话的用户不存在);
}
else{//加入发送者名字
currentID=j;
strcpy(rmessage,clientlist[nameid]);
strcat(rmessage,:
);
strcat(rmessage,b);
printf(o:
%s\n,rmessage);
}
}
//处理客户退出并通知上一层线程
牰湩晴尨用户%s已经退出\n,clientlist[nameid]);//问题:
一开始没办法及时打印会等下次“收到”才打印?
后面测试又可以?
free(clientlist[nameid]);
clientlist[nameid]=NULL;
free(buffer);
return(NULL);
}
客户端
#include
#include
#include
#include
#include
#include
#include
#include
#includePractical.h
void*ThreadMain(void*arg);//用来挂阻塞的rcvd
structThreadArgs{
intclntSock;//传递给线程要处理的套接字
};
intmain(intargc,char*argv[])
{
if(argc!
=3)
DieWithUserMessage(Parameter(s),
[]);
char*server=argv[1];//Firstarg:
serveraddress/name
char*echoString=(char*)malloc(120);
char*service=argv[2];
intsock=SetupTCPClientSocket(server,service);//创建套接字并connect
if(sock<0)
DieWithUserMessage(SetupTCPClientSocket()failed,%unabletoconnect);
nameland(sock);
structThreadArgs*threadArgs=(structThreadArgs*)malloc(sizeof(structThreadArgs));
if(threadArgs==NULL)
DieWithSystemMessage(malloc()failed);
threadArgs->clntSock=sock;
pthread_tthreadID;//创建线程
intreturnValue=pthread_create(&threadID,NULL,ThreadMain,threadArgs);
if(returnValue!
=0)
DieWithUserMessage(pthread_createfailed,strerror(returnValue));
printf(|----------登陆成功请按此格式聊天(用户名:
信息内容)----------|\n|-------(信息最长100字符/使用list命令获取在线用户列表/close命令退出)--------|\n);
//挂send
for(;;){
scanf(%s,echoString);
if(strcmp(echoString,close)==0)break;
ssize_tnumBytes=send(sock,echoString,120,0);
if(numBytes<0)
DieWithSystemMessage(send()failed);
elseif(numBytes!
=120)
DieWithUserMessage(send(),sentunexpectednumberofbytes);
?
?
牰湩晴尨发送字节数:
%zd\n,numBytes);
}
灦瑵?
欢迎再次使用\n,stdout);
free(echoString);
close(sock);
exit(0);
}
void*ThreadMain(void*threadArgs){//挂recv
charbuffer[120];//I/Obuffer
pthread_detach(pthread_self());
intcsock=((structThreadArgs*)threadArgs)->clntSock;
free(threadArgs);
while
(1){
inttotalRecv=0;
while(totalRecv<120){
ssize_tnumBytesRcvd=recv(csock,buffer+totalRecv,120-totalRecv,0);
if(numBytesRcvd<=0)
DieWithSystemMessage(
ecv()failed);
totalRecv+=numBytesRcvd;
}
灦瑵?
收到信息来自:
stdout);//Setuptoprinttheechoedstring
fputs(buffer,stdout);//Printthebuffer
fputc('\n',stdout);//Printafinallinefeed
}
return(NULL);
}
//自写的接口
#include
#include
#include
#include
#include
#include
#include
#includePractical.h
#include
#include
staticconstintMAXPENDING=5;//Maximumoutstandingconnectionrequests
voidDieWithUserMessage(constchar*msg,constchar*detail