TCP:多人聊天窗口(1)

举报
何其不顾四月天 发表于 2020/12/28 23:07:22 2020/12/28
【摘要】 简介 也算是一个小任务吧!基本要求,Linux下Tcp服务端,Windows,MFCTcp客户端。 环境: Linux:Centos6.7 Windows;vs2008MFC 12 思路 客户端:登录界面,主界面,聊天窗口。 登录界面:输入用户ID,用户IP。客户端登录服务端成功,进入主界面。客户端登录失败,等待登录成功。 主界面:所有用户ID,组ID...

简介

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

环境:

  Linux:Centos6.7
  Windows;vs2008MFC

  
 
  • 1
  • 2

思路

客户端:登录界面,主界面,聊天窗口。
登录界面:输入用户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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  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));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
/*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);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
/*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));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

多线程通信部分:

/*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();

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

报文解析部分:
做了两个容器用来控制用户,和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);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
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;
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

报文处理

void MsgProess(char * RecvMsg,int sockID);//初步分析,报文头的解析
void MsgPro(unsigned short msgId,char * Msg,int Msg_len,int sockID);//详细解析,并做相应的处理

  
 
  • 1
  • 2
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; }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

入口函数

/*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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  1. 客户端
    下一篇介绍,有点多,今天写不动了。
  2. 源码
    源码

文章来源: blog.csdn.net,作者:何其不顾四月天,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/u011218356/article/details/91672934

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。