提交日期:2012-06-20 基于socket的局域网聊天软件的设计与实现1.实验目的《Linux操作系统课程设计B》是一门在课程《Linux操作系统与程序设计B》后独立开设的实验课程。
这一门实验课程的开设目的是为了通过学生独立完成一个基于Linux平台的较大型应用程序,巩固课堂上学到的Linux平台上的编程规范、技术和技巧,培养学生的编写较大型程序的能力和提高学生综合应用素质。
本课程设计实验主要围绕Linux平台上主流的基础技术展开,这些技术包括:Linux的进程、线程通信和同步技术;socket网络通信技术等,这些技术可以集中体现并应用在并发程序设计中。
通过并发程序的设计与开发,培养学生底层软件开发的能力,并为将来从事UNIX/Linux平台开发、嵌入式开发等相对高端的软件开发工作打下基础。
2.软件功能及模块划分本软件是一个linux下基于socket的聊天室程序,能让局域网内的用户通过该软件进行简单的文字通信。
在此基础上增加了1.聊天室成员之间的发送私聊信息;2.当新的成员加入后能自动收取最近一段时间内的聊天上下文;3.用户能够查看历史聊天记录;4.软件界面基于Qt实现,图形化界面方便用户操作。
主要模块划分:服务端:数据包发送和接受模块,聊天记录数据库读写模块,数据包处理模块,聊天记录查询模块客户端:数据包发送和接受模块,数据包处理模块,聊天记录查询模块,用户界面与展示模块3.设计与实现3.1系统概述与总体结构本系统采用CS架构,服务端采用固定的端口通信,每个客户端动态设置端口。
客户端启动后向服务端告知自己所使用的端口号,以便可以双向通信,同时服务器负责为每个客户端分配一个唯一的ID(服务器的ID为1)客户端和服务端以及客户端和客户端之间采用约定的数据格式进行通信,以便接收方可以正确的解析命令和数据。
数据包通用格式定义如下#define MAX_UDP_SIZE 1000struct udp_packet{int type;int senderId;long size;char content[MAX_UDP_SIZE];};type:表示该数据包的类型,直接决定content字段的含义senderId:该数据包的发送者的ID,size:整个数据包的数据长度content:数据包的内容,其数据格式由type决定。
服务器和客户端接受到数据包后,根据type字段的值来解析content字段的数据,从而作出正确的处理和响应。
所有的数据包类型以及对应的content字段的数据结构全部定义在define.h文件中[系统总体结构]由上图可以看出,服务器和客户端程序在总体结构上相似。
服务器和客户端的全部功在数据处理模块中实现,这也是整个程序的核心之处由于数据包的接受在单独的线程中完成,而界面采用Qt实现。
因此在接受线程中采用QCoreApplication::postEvent()方法向界面线程发送通知事件,在界面窗口中通过重载customEvent()方法响应该通知,然后从Server中获取数据并显示。
整个过程中涉及到线程同步和多线程安全问题,觉采用信号量和互斥量解决。
3.2服务端的实现3.2.1服务总体实现与概述服务端的全部功能在类Server中实现,由于在整个系统中有且只有一个服务端出现,应此该类采用单例模式实现,通过Server::Instance()方法获得Server的一个实例,通过调用Init()函数完成服务端的初始化(包括信号量的初始化、数据库的连接、设置要发送事件通知的界面对象Object*),最后通过Start()方法创建socket,绑定端口,创建数据包接受线程,至此,完成服务器的启动部分代码:class Server{friend class ServerPacketProcessor;public:virtual ~Server();static Server* Instance();bool Init(QObject *mainWindow);bool Start();void WaitForServerStop();protected:Server();void* ThreadStartRoutine(void* arg);private:static void* ThreadRoutineTransponder(void* arg);void postEvent(QEvent::Type eventType);static Server* m_server;sockaddr_in m_localaddr;int m_sockfd;pthread_t m_thread;bool bCancel;pthread_mutex_t m_mutex_clients;pthread_mutex_t m_mutex_msgs;QObject* m_ui;。
Server.cpp#include "server.h"Server* Server::m_server=0;bool Server::Init(QObject* mainWindow){m_ui=mainWindow;bCancel=false;pthread_mutex_init(&m_mutex_clients,0);pthread_mutex_init(&m_mutex_msgs,0);return true;}Server* Server::Instance(){if(m_server==0)m_server=new Server();return m_server;}bool Server::Start(){m_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(m_sockfd==-1){cerr<<"create udp socket failed!"<<endl;return false;}m_localaddr.sin_port=htons(SRV_PORT);m_localaddr.sin_family=AF_INET;m_localaddr.sin_addr.s_addr=htonl(INADDR_ANY);if(-1==bind(m_sockfd,(struct sockaddr*)&m_localaddr,sizeof(m_localaddr))) {cerr<<"bind socket failed!"<<endl;return false;}if(!m_chatRecordMgr.ConnectDB("canghai","canghai","chat_record_schema")) {cerr<<"server connect datebase error!"<<endl;return false;}if(0!=pthread_create(&m_thread,0,ThreadRoutineTransponder,0)){cerr<<"create thread failed!"<<endl;return false;}return true;}void* Server::ThreadRoutineTransponder(void* arg){return Server::Instance()->ThreadStartRoutine(arg);}。
3.2.2服务器发送数据模块的显示由于所有的数据包均以udp_packet结构包装,应此发送模块较简单void Server::SendMsgTo(sockaddr_in clientaddr,const udp_packet* packet){sendto(m_sockfd,packet,packet->size,0,(struct sockaddr*)&clientaddr,sizeof(clientaddr));}3.2.3服务器接受线程的实现服务端的接受线程负责接受客户端发来的数据,并且解析数据包,采取合适的动作。
由于数据包的种类较多(随着功能的增加而增加),处理过程函数较为复杂,因此将数据包的处理过程封装在类ServerPacketProcessor的子类中,GetPacketProcessor()方法根据数据包的类型会创建合适的ServerPacketProcessor对象并返回线程函数和GetPacketProcessor方法的实现void* Server::ThreadStartRoutine(void* arg){while(!bCancel){udp_packet packet; //定义缓冲区memset(&packet,0,sizeof(packet));sockaddr_in clientaddr;socklen_t len=sizeof(clientaddr);size_t res=recvfrom(m_sockfd,&packet,sizeof(packet),0,(struct sockaddr*)&clientaddr,&len);if(res==-1){cerr<<"recvfrom error..."<<endl;}//根据数据包的类型获得正确的处理方式ServerPacketProcessor* processor=GetPacketProcessor(packet.type); if(processor==0)continue;//处理数据包processor->ProcessPacket(&packet,clientaddr);delete processor;}return 0;}ServerPacketProcessor* Server::GetPacketProcessor(int packet_type){switch(packet_type){case CLIENT_ONLINE_REQUST: //客户端上线请求{return new CltOnlinePacketProcessor(this);}case GROUP_MSG_SEND: //客户端发送群聊消息{return new GroupMsgPacketProcessor(this);}case QUERY_USER_INFO: //客户端查询其它用户信息{return new QueryUserInfoPacketProcessor(this);}case QUERY_CHAT_RECORD: //客户端查询聊天记录{return new QueryRecordPacketProcessor(this);}case CLIENT_OFF_LINE: //客户端下线{return new CltOfflinePacketProcessor(this);}default:return 0;}}3.2.4服务器数据包处理模块的设计当接受线程收到数据包后要根据数据包的类型作出正确的处理和响应对应的消息类型和content字段的数据结构1.客户端上线请求#define CLIENT_ONLINE_REQUST 1struct client_info{char username[20]; //客户端用户昵称char hostname[30]; //客户端的主机名short client_port; //客户端使用的通信端口};2.客户端发送群聊信息#define GROUP_MSG_SEND 4struct group_msg_send{char content[MAX_MSG_SIZE]; //消息内容};3.客户端查询其它用户信息#define QUERY_USER_INFO 6struct query_user{int userId; //可以查询指定的ID的用户,0则表示查询所有的用户信息};4.客户端查询聊天记录#define QUERY_CHAT_RECORD 10struct query_chat_record{int by_begin_time; //查询方式,1表示离开始时间点最近的count条记录,0表示离结束时间点最近的count条记录time_t begintime; //开始时间点time_t endtime; //结束时间点int count; //查询记录的条数};5.客户端下线#define CLIENT_OFF_LINE 12(content内容无含义)3.3客户端设计概要本系统在群聊的基础上实现私聊功能,从服务器的设计可看出,群聊消息是由服务器转发的。