簡介
也算是一個小任務吧!基本要求,Linux下Tcp服務端,Windows,MFCTcp客戶端。
環境:
Linux:Centos6.7
Windows;vs2008MFC
思路
客戶端:登錄界面,主界面,聊天窗口。
登錄界面:輸入用戶ID,用戶IP。客戶端登錄服務端成功,進入主界面。客戶端登錄失敗,等待登錄成功。
主界面:所有用戶ID,組ID,雙擊打開聊天窗口,單一ID只能打開一個窗口。
聊天窗口:顯示聊天內容,聊天內容輸出窗口
服務端:消息中轉,控制羣組,用戶
代碼
- 協議:
消息類型:登錄消息,刪除賬號消息,個人消息(點對點聊天消息包),羣組消息(羣組聊天消息包),創建羣組,刪除羣組
報文結構:報文頭(消息類型,發送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;
};
- 服務端
這版的缺陷,時間關係,加前期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;
}
- 客戶端
下一篇介紹,有點多,今天寫不動了。 - 源碼
源碼