简介
也算是一个小任务吧!基本要求,Linux下Tcp服务端,Windows,MFCTcp客户端。
环境:
代码语言:javascript复制 Linux:Centos6.7
Windows;vs2008MFC
思路
客户端:登录界面,主界面,聊天窗口。 登录界面:输入用户ID,用户IP。客户端登录服务端成功,进入主界面。客户端登录失败,等待登录成功。 主界面:所有用户ID,组ID,双击打开聊天窗口,单一ID只能打开一个窗口。 聊天窗口:显示聊天内容,聊天内容输出窗口 服务端:消息中转,控制群组,用户
代码
- 协议:
消息类型:登录消息,删除账号消息,个人消息(点对点聊天消息包),群组消息(群组聊天消息包),创建群组,删除群组 报文结构:报文头(消息类型,发送ID,收方ID,报文长度),消息内容 UDP心跳包,判断在线不在线。(后来屏蔽)
代码语言:javascript复制#pragma pack(1)
#define MSG_MAXLEN 260
#define MSGHEADER_LEN 8
enum MsgType{
Res =0xFF20,
Del,
AloneMsg, //个人消息
ClubMsg, //群组消息
CreatCmd, //创建群组
DelCmd, //删除群组
};
struct MsgHeader{
unsigned short usMsgId; //消息类型 0x0020: 0: 1:群组消息 2:创建群组 3:删除群组
unsigned short usSendID; //发方ID
unsigned short usRecvID; //收方ID
unsigned short usMsgLen; //消息长度
};
struct ResDel{ //注册/注销消息
MsgHeader m_MsgHeader;
unsigned short usID; //人员ID(按照注册顺序分配,区间段为10001-10002)
char strIp[16]; //ip信息
};
struct Msg_pack{ //聊天消息包
MsgHeader m_Header; //报文头
char strMsg[255]; //消息字段
};
/*UDP心跳包-1S1发*/
struct UDPMsg{
unsigned short usID; //人员ID
unsigned int iNo;//Msg编号
};
/*创建群组*/
struct CreatClub{
MsgHeader m_MsgHeader;
unsigned short usClubID; //群组ID
unsigned short usClubCreatID; //群组创建人ID
unsigned short usMerberSum; //群组创建人数
char * strMerberIDInfo; //群组人员ID信息
};
/*删除群组*/
struct DelClub {
MsgHeader m_MsgHeader;
unsigned short usClubID;
unsigned short usClubCreatID;
};
/*所有在线用户状态包*/
struct UsrOnlineState{
MsgHeader m_MsgHeader;
unsigned short usSum;//用户个数
};
struct UsrOnline{
unsigned short usID;//用户ID
bool bState;
};
- 服务端
这版的缺陷,时间关系,加前期Tcp实现问题,没时间继续优化,用户组,群组写死了。如果有时间下一版改进,都改为动态聊天。初步计划,读写本地配置文件,用来实现服务端对用户的管理。
代码语言:javascript复制/*ListInit*/
unsigned short str_Number[10] = {10001,10002,10003,10004,10005,10006,10007,10008,10009,10010};
unsigned short str_Club[3][10] = {
{10001,10002,10003},
{10001,10004,10005},
{10006,10002,10008,10009},
};
void InfoInit()
{
std::vector<unsigned short> club_1(str_Club[0],str_Club[0] 3);
std::vector<unsigned short> club_2(str_Club[1],str_Club[1] 3);
std::vector<unsigned short> club_3(str_Club[2],str_Club[2] 3);
map_club.insert(std::make_pair(20001,club_1));
map_club.insert(std::make_pair(20002,club_2));
map_club.insert(std::make_pair(20003,club_3));
}
Tcp类: 包含,socket创建,端口绑定,监听端口设定,客户端连接函数,消息接收,消息发送。
代码语言:javascript复制class TcpNetwork{
public:
int CreatSocket();
bool BindSocket();
bool listenport();
int Clientconnect();
int TcpInit();
void RecvMsg();
void QuitTcp();
void SendMsg(Msg_pack m_Msg_pack,int sockID);
private:
struct timeval tv;
struct sockaddr_in ServerTalk;
static char TcpRecvBuf[TcpBufMax];
int sock_Server;
int Clent_sock;
};
代码语言:javascript复制/*TcpNetWork Funtion*/
int TcpNetwork::CreatSocket()
{
sock_Server = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
ServerTalk.sin_family = AF_INET;
ServerTalk.sin_port = htons(TcpRecvPort);
ServerTalk.sin_addr.s_addr = htonl(INADDR_ANY);
return sock_Server;
}
bool TcpNetwork::BindSocket()
{
if(bind(sock_Server,(struct sockaddr*)&ServerTalk,sizeof(sockaddr))==-1)
{
return false;
}else{
printf("Tcp:绑定成功n");
return true;
}
}
bool TcpNetwork::listenport(){
int iListen = listen(sock_Server,QUEUE);
if(iListen == -1){
printf("listenn");
return false;
}else{
printf("Tcp:Listen Scucessfullyn");
return true;
}
}
int TcpNetwork::Clientconnect()
{
struct sockaddr_in ClientTalk;
char ClinentIp[INET_ADDRSTRLEN];
socklen_t length = sizeof(ClientTalk);
Clent_sock = accept(sock_Server,(struct sockaddr*)&ClientTalk,&length);
printf("Clent_sock:%dn",Clent_sock);
if(Clent_sock<0) {
printf(" Tcp:Connect failn");
return -1;
}else {
printf("Tcp:Connect OK!n");
return Clent_sock;
}
}
int TcpNetwork::TcpInit()
{
int sock_tcp =CreatSocket();
if(BindSocket()==false) {
printf("Tcp:端口绑定失败n");
}
else{
if(listenport() == false) {
printf("Tcp:监听口设置失败n");;
}
}
return sock_tcp;
}
char TcpNetwork::TcpRecvBuf[TcpBufMax] = {0};
void TcpNetwork::RecvMsg()
{
char Recvbuf[280] = {0};
int ret = recv(Clent_sock,Recvbuf,280,0);
if(strlen(Recvbuf)>0){
printf("Recv:Clent_sock:%dn",Clent_sock);
MsgProess(Recvbuf,Clent_sock);
}else {
}
}
void TcpNetwork::QuitTcp()
{
close(Clent_sock);
close(sock_Server);
}
void TcpNetwork::SendMsg(Msg_pack m_Msg_pack,int sockID)
{
char Buf_Msg[280] = {0};
memcpy(Buf_Msg,&m_Msg_pack,sizeof(Msg_pack));
send(sockID,Buf_Msg,sizeof(Msg_pack),0);
}
UDP类 包含:UDPSocket的创建,绑定,收发,还有一个心跳包的打印函数,用来测试。
代码语言:javascript复制class UdpNetwork{
public:
void CreatSocket();
bool BindSocket();
void UdpInit();
void UdpRecv();
void PrintfClientInfo();
UDPMsg m_UDPMsg;
private:
int sockfd;
sockaddr_in ServerSocket;
sockaddr_in ClientSocket;
static char UdpRecvBuf[UdpBufMax];
};
代码语言:javascript复制/*UdpSocket Funtion*/
void UdpNetwork::CreatSocket(){
bzero(&ServerSocket,sizeof(ServerSocket));
ServerSocket.sin_family = AF_INET;
ServerSocket.sin_port = htons(UdpRecvPort);
ServerSocket.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockfd == -1){
printf("Udp:Creat Socket Fail!n");
}else printf("Udp:Creat Udp Socket OK!n");
}
bool UdpNetwork::BindSocket(){
int state =bind(sockfd,(struct sockaddr *)&ServerSocket,sizeof(ServerSocket)) ;
if(state == -1){
printf("Udp Bind fail!n");
return false;
}else {
printf("Udp Bind OK!n");
return true;
}
}
void UdpNetwork::UdpInit(){
CreatSocket();
BindSocket();
}
char UdpNetwork::UdpRecvBuf[UdpBufMax] = {0};
void UdpNetwork::UdpRecv(){
printf("Udp -----n");
socklen_t ClientLen = sizeof(struct sockaddr_in);
int RecvLen = recvfrom(sockfd,UdpRecvBuf,UdpBufMax,0,(sockaddr*)&ClientSocket,&ClientLen);
if(RecvLen<0){
printf("Udp:Recv Fail!n");
}else printf("Udp:Recv OK!n");
memcpy(&m_UDPMsg,&UdpRecvBuf,sizeof(UDPMsg));
printf("Udp:RecvMsg Len is:%d,usID is:%d,iNo:%dn",RecvLen,m_UDPMsg.usID,m_UDPMsg.iNo);
PrintfClientInfo();
}
void UdpNetwork::PrintfClientInfo(){
char ClinentIp[INET_ADDRSTRLEN];
inet_ntop(AF_INET,&ClientSocket.sin_addr,ClinentIp,sizeof(ClinentIp));
printf("Udp:Client IP is :%s,port is:%dn",ClinentIp,ntohs(ClientSocket.sin_port));
}
多线程通信部分:
代码语言:javascript复制/*Thread Funtion*/
int TcpInit()
{
int sock_Server = m_Tcp.TcpInit();
return sock_Server;
}
void *TcpThread(void *ptr)
{
int clientSocket = *((int *)ptr);
time_t Befortime; //时间相关部分可以注释掉,用来统计时间用,有一些错误,对主题消息流程没有影响
time(&Befortime);
time_t Nowtime;
double TimeErr = 0;
std::map<unsigned short,int>::iterator it = threadBind.begin();;
while(1)
{
time(&Nowtime);
char Recvbuf[280] = {0};
int ret = recv(clientSocket,Recvbuf,280,0);
if(ret<0)
{
printf("接收失败");
for(it;it != threadBind.end();it )
{
if(it->second == clientSocket)
{
threadBind.erase(it);
}
}
}
if(strlen(Recvbuf)>0)
{
MsgProess(Recvbuf,clientSocket);
}
TimeErr=difftime(Nowtime,Befortime);
usleep(20000);//线程休眠20ms
}
}
void UdpInit()
{
m_Udp.UdpInit();
}
void *UdpThread(void *ptr)
{
while(1)
m_Udp.UdpRecv();
}
报文解析部分: 做了两个容器用来控制用户,和Socket管理
代码语言:javascript复制std::map<unsigned short,int>threadBind;
std::map<unsigned short,std::vector<unsigned short> >map_club;
int GetSockID(unsigned short usID);
std::vector<unsigned short> GetClubVector(unsigned short usClubID);
代码语言:javascript复制std::vector<unsigned short> GetClubVector(unsigned short usClubID)
{
return map_club[usClubID];
}
int GetSockID(unsigned short usID)
{
std::map<unsigned short,int>::iterator it = threadBind.find(usID);
if(it != threadBind.end()){
return it->second;
}else{
return 0;
}
}
报文处理
代码语言:javascript复制void MsgProess(char * RecvMsg,int sockID);//初步分析,报文头的解析
void MsgPro(unsigned short msgId,char * Msg,int Msg_len,int sockID);//详细解析,并做相应的处理
代码语言:javascript复制TcpNetwork m_Tcp;
UdpNetwork m_Udp;
void MsgProess(char * RecvMsg,int sockID)
{
MsgHeader m_Header;
memcpy(&m_Header,RecvMsg,sizeof(MsgHeader));
printf("m_Header:%d,%d,%d,%dn",m_Header.usMsgId,m_Header.usSendID,m_Header.usRecvID,m_Header.usMsgLen);
MsgPro(m_Header.usMsgId,RecvMsg,m_Header.usMsgLen,sockID);
}
void MsgPro(unsigned short msgId,char * Msg,int Msg_len,int sockID)
{
Msg_pack m_Msg_pack ={0};
ResDel m_ResDel = {0};
std::pair<unsigned short,int> it;
std::vector<unsigned short> vec_clubNumber;
char Buf_Msg[280] = {0};
switch(msgId)
{
case Res:
memcpy(&m_ResDel,Msg,sizeof(ResDel));
it = std::make_pair(m_ResDel.m_MsgHeader.usSendID,sockID);
printf("Res:%d,%dn",it.first,it.second);
threadBind.insert(it);
break;
case Del:
memcpy(&m_ResDel,Msg,sizeof(ResDel));
break;
case AloneMsg:
memcpy(&m_Msg_pack,Msg,sizeof(Msg_pack));
if(m_Msg_pack.m_Header.usSendID == m_Msg_pack.m_Header.usRecvID) break;
if(GetSockID(m_Msg_pack.m_Header.usRecvID) != 0)
{
printf("AloneMsg SocketID::%d,%dn",sockID,GetSockID(m_Msg_pack.m_Header.usRecvID));
int i = send(GetSockID(m_Msg_pack.m_Header.usRecvID),Msg,sizeof(Msg_pack),0);
if(i<0)
{
printf("Err: TcpSendMsg Fail!n");
}
}
break;
case ClubMsg:
memcpy(&m_Msg_pack,Msg,sizeof(Msg_pack));
vec_clubNumber = GetClubVector(m_Msg_pack.m_Header.usRecvID);
for(int i= 0;i<vec_clubNumber.size();i ){
printf("Club::%d,%d,%dn",vec_clubNumber[i],GetSockID(vec_clubNumber[i]),m_Msg_pack.m_Header.usSendID);
if(m_Msg_pack.m_Header.usSendID == vec_clubNumber[i]) continue;
if(GetSockID(vec_clubNumber[i]) != 0){
int b= send(GetSockID(vec_clubNumber[i]),Msg,sizeof(Msg_pack),0);
if(b<0)
{
printf("Err: Tcp ClubMsg SendFail!n");
}
}
}
break;
case CreatCmd:
break;
case DelCmd:
break;
default:
break;
}
}
入口函数
代码语言:javascript复制/*Main Funtion*/
int main(){
InfoInit();
pthread_t Udp_id;
UdpInit();
int sock_Server = TcpInit();//Tcp初始化
int i = 0;
while(1){
int ibuf =m_Tcp.Clientconnect();//客户端连接判断
if(ibuf == -1)
{
printf("Tcp:ibuf %dn",ibuf);
continue;
}else{
printf("This is %d threadn",i);
pthread_t Tcp_id;
int ret_Tcp = pthread_create(&Tcp_id,NULL,TcpThread,&ibuf);
pthread_detach(Tcp_id);//线程分离
i =1;
}
usleep(10000);
}
/*int ret_Udp = pthread_create(&Udp_id,NULL,UdpThread,NULL);
if(ret_Udp != 0){
printf("ret_Udp Create Fail;n");
}*/
close(sock_Server);
//pthread_join(Udp_id,NULL);
return 0;
}
- 客户端 下一篇介绍,有点多,今天写不动了。
- 源码 源码