TCP:多人聊天窗口(1)

2023-03-10 13:34:37 浏览数 (1)

简介

也算是一个小任务吧!基本要求,Linux下Tcp服务端,Windows,MFCTcp客户端。

环境:

代码语言:javascript复制
  Linux:Centos6.7
  Windows;vs2008MFC

思路

客户端:登录界面,主界面,聊天窗口。 登录界面:输入用户ID,用户IP。客户端登录服务端成功,进入主界面。客户端登录失败,等待登录成功。 主界面:所有用户ID,组ID,双击打开聊天窗口,单一ID只能打开一个窗口。 聊天窗口:显示聊天内容,聊天内容输出窗口 服务端:消息中转,控制群组,用户

代码

  1. 协议:

消息类型:登录消息,删除账号消息,个人消息(点对点聊天消息包),群组消息(群组聊天消息包),创建群组,删除群组 报文结构:报文头(消息类型,发送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;
 };
  1. 服务端

这版的缺陷,时间关系,加前期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;
}
  1. 客户端 下一篇介绍,有点多,今天写不动了。
  2. 源码 源码
udp

0 人点赞