TCP:多人聊天窗口(1)

簡介

也算是一個小任務吧!基本要求,Linux下Tcp服務端,Windows,MFCTcp客戶端。

環境:

  Linux:Centos6.7
  Windows;vs2008MFC

思路

客戶端:登錄界面,主界面,聊天窗口。
登錄界面:輸入用戶ID,用戶IP。客戶端登錄服務端成功,進入主界面。客戶端登錄失敗,等待登錄成功。
主界面:所有用戶ID,組ID,雙擊打開聊天窗口,單一ID只能打開一個窗口。
聊天窗口:顯示聊天內容,聊天內容輸出窗口
服務端:消息中轉,控制羣組,用戶

代碼

  1. 協議:

消息類型:登錄消息,刪除賬號消息,個人消息(點對點聊天消息包),羣組消息(羣組聊天消息包),創建羣組,刪除羣組
報文結構:報文頭(消息類型,發送ID,收方ID,報文長度),消息內容
UDP心跳包,判斷在線不在線。(後來屏蔽)

#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;
 };
  1. 服務端

這版的缺陷,時間關係,加前期Tcp實現問題,沒時間繼續優化,用戶組,羣組寫死了。如果有時間下一版改進,都改爲動態聊天。初步計劃,讀寫本地配置文件,用來實現服務端對用戶的管理。

/*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創建,端口綁定,監聽端口設定,客戶端連接函數,消息接收,消息發送。

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;
};
/*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("listen\n");
		return false;
	}else{
		printf("Tcp:Listen Scucessfully\n");
		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:%d\n",Clent_sock);
	if(Clent_sock<0)	{
		printf(" Tcp:Connect fail\n");
		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:%d\n",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的創建,綁定,收發,還有一個心跳包的打印函數,用來測試。

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];
};
/*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:%d\n",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:%d\n",ClinentIp,ntohs(ClientSocket.sin_port));
}

多線程通信部分:

/*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管理

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);
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;
	}
}

報文處理

void MsgProess(char * RecvMsg,int sockID);//初步分析,報文頭的解析
void MsgPro(unsigned short msgId,char * Msg,int Msg_len,int sockID);//詳細解析,並做相應的處理
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,%d\n",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,%d\n",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,%d\n",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,%d\n",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;		
	}
}

入口函數

/*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 %d\n",ibuf);
			continue;
		}else{
			printf("This is %d thread\n",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;
}
  1. 客戶端
    下一篇介紹,有點多,今天寫不動了。
  2. 源碼
    源碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章