2014-03-15 18:04:09 mjj123mjj123 阅读数 1862
  • 实战Go语言:多人聊天

    ·基于Go的网络和并发技术开发多人在线聊天室; ·功能包括:单聊、群聊、昵称、上下线通知、聊天日志等; ·技术栈:TCP通信、协程并发、文件读写、面向对象;

    584 人正在学习 去看看 欧阳桫

首先运行Server端,然后运行多个客户端

源代码:http://pan.baidu.com/s/1bnb4Mq7

聊天室Server端

import java.io.IOException;
import java.net.*;
import java.io.*;
import java.util.*;

public class ChatServer {
    ServerSocket ss=null;
    boolean started=false;
    Socket s=null;
    List<Client> clients=new ArrayList<Client>();//接收客户端连接
    
    
    public static void main(String[] args) {
        new ChatServer().start();        
    }

    public void start(){
            int counter=1;
        try {
            ss=new ServerSocket(7777);
            started=true;
        }catch(BindException e){
            System.out.println("端口正在使用。。。。");
            System.out.println("请先关闭相关的程序,在重新运行服务器");
            System.exit(0);
            
        }catch(IOException e){
            e.printStackTrace();
        }
        try{
            while(started){
                s=ss.accept();
                Client c=new Client(s);
                System.out.println("客户端:"+counter+++" 已经连上,"+"来自:"+
                                        s.getInetAddress().getHostName()+"端口号为:"+s.getPort());
                new Thread(c).start();
                clients.add(c);//保存下来
              }
         }catch(IOException e){
             e.printStackTrace();
         }finally{
                try {
                    ss.close();
                 }catch (Exception e) {
                    
                }
        }
    }
    class Client implements Runnable{

        private Socket s=null;
        private DataInputStream dis=null;
        private DataOutputStream dos=null;
        
        boolean bContected=false;
        
        public Client(Socket s){
            this.s=s;
            try {
                dis=new DataInputStream(s.getInputStream());
                dos=new DataOutputStream(s.getOutputStream());
                bContected=true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public void send(String str){
            try {
                dos.writeUTF(str);
            } catch (IOException e){
                clients.remove(this);
                System.out.println("对方退出了,也在clients中去掉了");
            }
            
        }
        public void run() {
            Client c=null;
            try{
                while(bContected)
                {
                    String str=dis.readUTF();
                    for(int i=0;i<clients.size();i++)
                    {
                        c=clients.get(i);
                            c.send("来自:"+s.getInetAddress().getHostName()+s.getPort()+"  : "+str);
                        
                    }
                    /*也行,但是慢,一下会锁定,效率低
                     for(Iterator<Client> it=clients.iterator();it.hasNext()){
                         Client c=it.next();
                            c.send(str);
                     }
                    */
                    System.out.println("来自:"+s.getInetAddress().getHostName()+s.getPort()+"  : "+str);
                }
            }catch(SocketException e){
                System.out.println("来自:"+s.getInetAddress().getHostName()
                                +"端口号为:"+s.getPort()+" 的客户端关闭了!");
            }catch(EOFException e){
                System.out.println("来自:"+s.getInetAddress().getHostName()
                        +"端口号为:"+s.getPort()+" 的客户端关闭了!");
             }catch(IOException e){
                 System.out.println("来自:"+s.getInetAddress().getHostName()
                            +"端口号为:"+s.getPort()+" 的客户端关闭了!");
             }finally{
                 try {
                    if(dis!=null)    dis.close();
                    if(s!=null)      s.close();
                    if(dos!=null)  dos.close();
                    
                //clients.remove(this);
                    
                   }catch (Exception e) {
                        e.printStackTrace();
                   }
            }
            
            
        }
        
    }
}

客户端:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.*;

public class ChatClient extends Frame {
    
    Socket s=null;
    DataOutputStream dos=null;
    DataInputStream dis=null;
    boolean beContected=false;
    private static final long serialVersionUID = 1L;

    TextField tfTxt = new TextField();
    TextArea taContent = new TextArea();

    public static void main(String[] args) {
        new ChatClient().launchFrame();
    }

    public void launchFrame() {
        setLocation(300, 200);
        setSize(100, 100);
        add(tfTxt, BorderLayout.SOUTH);
        add(taContent, BorderLayout.NORTH);
        pack();
        tfTxt.addActionListener(new TfListener());
        taContent.setEditable(false);
        this.addWindowListener(new WindowAdapter(){

            public void windowClosing(WindowEvent e) {
                discontect();
                System.exit(0);
            }    
        });
        contect();
        new Thread(new RevThread()).start();
        
        setVisible(true);
    }
    
    public void contect(){
        try {
           s=new Socket("127.0.0.1",7777);
           this.setTitle("已连接");
           dos=new DataOutputStream(s.getOutputStream());
           dis=new DataInputStream(s.getInputStream());//接收来自由服务器的信息()
           
           //System.out.println("==Contected!==");
            beContected=true;
        } catch (UnknownHostException e) {
            System.out.println("请确保服务器已经开启!!!");
        } catch (IOException e) {            
            System.out.println("请检查设置!!!");
        }
        
    } 
    public void discontect(){
        try {
            dos.close();
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private class TfListener implements ActionListener{

        public void actionPerformed(ActionEvent e) {
            String str=tfTxt.getText().trim();
            //taContent.setText(str);            //最后才注释
            tfTxt.setText("");
            try {
                dos.writeUTF(str);
                dos.flush();        
            }catch(IOException e1){
                
            }
                
        }    
        
        
        
        
    }
    public class RevThread implements Runnable{
        
        public void run() {
            try {
                while(beContected){
                    String str;    
                    str = dis.readUTF();
                    //System.out.println(str);
                    taContent.setText(taContent.getText()+str+'\n');
                }
            }catch(SocketException e){
                System.out.println("退出了,拜拜--bye");
            }catch(EOFException e){
                System.out.println("退出了,拜拜");
            } catch (IOException e) {                        
                    e.printStackTrace();
            }
    
        }
        
    }
}

运行效果如图:




2019-07-08 16:38:38 qq_44891751 阅读数 185
  • 实战Go语言:多人聊天

    ·基于Go的网络和并发技术开发多人在线聊天室; ·功能包括:单聊、群聊、昵称、上下线通知、聊天日志等; ·技术栈:TCP通信、协程并发、文件读写、面向对象;

    584 人正在学习 去看看 欧阳桫

用socket实现一个多人聊天室的思路很简单,即在服务器端定义一个fd的int型数组,用来存储已经连接的客户端的socket连接套接字fd(因为发送和接收数据都只需要借助连接套接字fd),当服务器接收到来自某一客户端的数据时,直接转发给其他所有连接着的客户端,即完成了多人聊天室
服务器端:多线程进行数据的转发,记录聊天记录,向客户端发送进入退出聊天室提醒。
客户端:在主线程内发送数据,多线程接收其他客户端发的数据。输入“bye”退出客户端,
在这里插入图片描述
TCP.server
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define PORT 4567
typedef struct
{
int fd;
char buff[100];
}MSG;
int sockfd;//服务器socket
int fds[10];//客户端的socketfd,10个元素,
int size =10 ;//用来控制进入聊天室的人数为10以内
char buf3[100] ={0};
typedef struct sockaddr SA;
typedef struct sockaddr_in SIN;
//给所有用户发送信息
void SendMsgToAll(MSG* msg)
{
MSG send_msg = *msg;

for (int i = 0;i < size;i++)
{
    if (fds[i] != 0)
    {
        if(fds[i] == send_msg.fd)  
        	continue;
        printf("sendto%d\n",fds[i]);
        send(fds[i],send_msg.buff,sizeof(send_msg.buff),0);
    FILE * fp = fopen("history.txt","a+");
    fwrite(send_msg.buff,strlen(send_msg.buff),1,fp);
    fclose(fp);
    }
}

}
//信息接收线程
void* service_thread(void* p)
{
int fd = (int)p;
printf(“pthread = %d\n”,fd);
while(1)
{
char buf[100] = {0};
memset(buf,0,100);
if (recv(fd,buf,sizeof(buf),0) <= 0) //当用户fd发生变化时遍历所有fd发送退出信息
{
int i;
for (i = 0;i < size;i++)
{
if (fd == fds[i])
{
fds[i] = 0;
break;
}
}
printf(“退出:fd = %dquit\n”,fd);
memset(buf3,0,100);
sprintf(buf3,"%s退出了聊天室",buf);
for (int i = 0;i < size;i++)
{
if (fds[i] != 0)
{
if(fds[i] == fd){
continue;
}
printf(“sendto%d\n”,fds[i]);
send(fds[i],buf3,strlen(buf3),0);

   		}
    	}
	memset(buf3,0,100);
            pthread_exit((void*)i);
    }
else
{
	if(strcmp(buf,"chat_history") == 0)
	{
		memset(buf,0,100);
		FILE * fp = fopen("history.txt","r");
		//fseek(fp,0,SEEK_SET);
		fread(buf,1,100,fp);
		fclose(fp);
		send(fd,buf,sizeof(buf),0);
		memset(buf,0,100);
		continue;
	}
	//把服务器接受到的信息发给所有的客户端
	  MSG msg_info;
	  msg_info.fd = fd;
	strcpy(msg_info.buff,buf);
	SendMsgToAll(&msg_info);
}
}

}
//等待客户端连接
void service()
{
printf(“服务器启动,等待连接\n”);
while(1)
{
SIN fromaddr;
socklen_t len = sizeof(fromaddr);
int fd = accept(sockfd,(SA*)&fromaddr,&len);

    if (fd == -1)
    {
        printf("客户端连接出错...\n");
        continue;
    }
     //记录客户端的socket
  	int i = 0;
    for (i = 0;i < size;i++)
    {
        if (fds[i] == 0)
        {
            fds[i] = fd;
            printf("进入聊天室的fd:%d\n",fd);
            //有客户端连接之后,启动线程给此客户服务
            pthread_t tid;
            pthread_create(&tid,0,service_thread,&fd);
            break;
        }
        if (size == i)
        {
            //发送给客户端说聊天室满了
            char* str = "对不起,聊天室已经满了!";
            send(fd,str,strlen(str),0); 
            close(fd);
        }
    }
//提示用户上线
i = 0;
char buf2[20] = {0};
recv(fd,buf2,20,0);
sprintf(buf3,"%s进入了聊天室",buf2);
 for (i = 0;i < size;i++)
    {
       if (fds[i] != 0)
       {
           if(fds[i] == fd)
			{
        	    continue;
			}
        printf("sendto%d\n",fds[i]);
    send(fds[i],buf3,strlen(buf3),0);
  
 	  }
    }  
memset(buf3,0,100);
}

}

int main(int argc,char * argv[])
{
if(argc != 2)
{
printf(“命令格式错误\n”);
return -1;
}
//设置套接字
sockfd = socket(PF_INET,SOCK_STREAM,0);
if (sockfd == -1){
perror(“创建socket失败”);
exit(-1);
}
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(argv[1]);
if (bind(sockfd,(SA*)&addr,sizeof(addr)) == -1){
perror(“绑定失败”);
exit(-1);
}
if (listen(sockfd,10) == -1){
perror(“设置监听失败”);
exit(-1);
}
service();
}

TCP.clint.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT 4567
int sockfd;//客户端socket
char name[20] = {0};//客户端姓名
typedef struct sockaddr SA;
//接收服务器转发的信息
void* recv_thread(void* p){
while(1){
char buf[100] = {};
if (recv(sockfd,buf,sizeof(buf),0) <= 0)
{
break;
}
printf("%s\n",buf);
}
}

void start()
{
//给服务器发送信息
pthread_t id;
pthread_create(&id,0,recv_thread,0);
char buf2[100] = {0};
send(sockfd,name,strlen(name),0);
while(1)
{
char buf[100] = {0};
scanf("%s",buf);
if (strcmp(buf,“bye”) == 0)
{
send(sockfd,name,(strlen(name)),0);
break;
}
else if(strcmp(buf,“chat_history”) == 0)
{
send(sockfd,buf,strlen(buf),0);
continue;
}
char msg[131] = {0};
sprintf(msg,"%s:%s",name,buf);
send(sockfd,msg,strlen(msg),0);
}
close(sockfd);
}
//主函数
int main(int argc,char * argv[])
{
//设置套接字
sockfd = socket(PF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(argv[1]);
//连接服务器
if (connect(sockfd,(SA*)&addr,sizeof(addr)) == -1)
{
perror(“无法连接到服务器”);
exit(-1);
}
printf(“客户端启动成功\n”);
printf(“请输入您的名字:”);
scanf("%s",name);
start();
return 0;
}
在这里插入图片描述
这个聊天室程序还有很多不足,1.发送数据不能识别空格;2.查找聊天室记录功能还需完善,现在只能显示前100个字符。如果还有其他不足的地方希望各位能提出来,以供其他阅读的人参考
希望能对各位有所帮助,同时希望各位大佬们能够完善。

2019-08-25 00:56:01 qq_36863664 阅读数 241
  • 实战Go语言:多人聊天

    ·基于Go的网络和并发技术开发多人在线聊天室; ·功能包括:单聊、群聊、昵称、上下线通知、聊天日志等; ·技术栈:TCP通信、协程并发、文件读写、面向对象;

    584 人正在学习 去看看 欧阳桫

经过好几天的日夜奋斗,总算把这个聊天室给做出来了,虽然说不上多好,但也是这几天从早到晚劳动的成功,所以就写这篇博文来记录一下啦。别的不敢说,确保能用就是了,完整代码在最后哦~
当然啦,如果有幸被转发,还请注明来处哈~

一、功能

这个Linux下C版本的多人网络聊天室具备以下几个基本功能(或者说需求):
(一)C/S模式,IPv4的TCP通信;
(二)客户端登录需要账号密码,没有账号需要注册;
(三)服务器每接收到一个客户端的连接,就产生一个线程为其服务;
(四)进入聊天室后,客户端可以选择群聊(发给所有人),也可以选择私聊(发给指定账号的人);
(五)服务器可以限定同时登录聊天室的账号的最大数量;
(六)用sqlite3创建保存用户账号信息的数据库;
(七)客户端(服务器端要的话应该也可以)自动获取本地主机未被占用的端口号以连接服务器。

由于时间关系,原本设定的用户登录或退出时通知所有人和保存聊天记录等功能,笔者就没能添加进去。若有兴趣,读者可参考以下想法进行添加:“用户登录或退出时通知所有人”这点可以在用户确认登录后但尚未进入聊天室时通过send/recv实现,而“保存聊天记录”这点就像保存用户账号信息一样保存到数据库(当然更简单的方法是保存到普通文件中)。另外,也有一大点笔者心有余而力不足,有大佬路过时还请指教指教,就是多线程之间交互的问题,例如主线程如何等待所有子线程正常退出等,这一块我不熟悉~

二、主要功能实现分析

(一)网络通信基本步骤
简单来讲就是:客户端——创建套接字,配置网络结构体,绑定,请求连接,正常通信;服务器——创建套接字,配置网络结构体,绑定,监听,接受连接,正常通信。至于网络通信基本原理详细点的介绍可以看我这篇博文介绍——网络通信基本原理
在这里要注意以下两点:
一个是配置网络结构体,这点其实可以在man手册看到相关(结构体)说明,具体来说就是设定端口号、IP协议(IPv4/IPv6)以及通信对象的IP地址,配置网络结构体是为了后续bind服务的。

 //配置网络结构体
  struct sockaddr_in myaddr; 
  myaddr.sin_port = htons(50000);	//应用层端口号
  myaddr.sin_family = AF_INET;		//IPv4协议
  myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址,也可以指定IP

另一个是所谓的“自动获取本地主机未被占用的端口号”。由于笔者只是在自己一台电脑上开多个虚拟终端来运行client,连接的服务器在本地主机(127.0.0.1),而我又不想手动地一个客户端给他传递一个端口号,所以就想到了这个方法。具体来讲,就是从50001开始随便一个一个去尝试bind,绑定失败就下一个,绑定成功的话那就它了。至于为什么是50001开始呢,一个依据是《计算机网络》中说到客户端使用的端口号为49152~65535,然后我就在这个范围内选个中意的范围了。

portnum = 50001;
  while(1){
    if(portnum > 65535){
      printf("bind error because of no port number available!\n");
      return -1;
    }
    (.......配置网络结构体)
    ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
    if(ret == -1) {
      continue;
    }
    else{
      printf("bind successfully!\n");
      break;
    }
  }

(二)用户账号信息如何验证、存储
讲到这点,我们得先看看数据库方面的知识了,这里我用的是SQLite,其操作简单讲就是增、删、查、改这些,具体地C接口在这个程序中我使用的有sqlite3_open_v2()、sqlite3_exec()、sqlite3_get_table()。其实说到数据库,我也是在做这个小项目过程中现学现卖的,所以就不多班门弄斧了,不过一些基本操作咱可以看这个网站。在这里我就介绍下我怎么存储怎么验证。
(1)存储
说到存储,在我这个小项目里那就只能是在注册过程才会用到了,而注册是在客户端进行,所以要先获取ID信息(昵称和密码),然后发送给服务器服务器读取用户账号信息表格并分配一个尚未使用的ID,然后将账号信息(ID,name,passwd)插入到数据库,而插入成功了也就是说注册成功了,那就要返回一个注册成功标志给客户端

//这个代码段是用来计算用户账号信息表中有多少条记录的,将这个数量加上起始的100的结果作为账号分配,就能使得账号不重复了。
int j = 100;
	char *sql1 = "select * from hwy_id_sheet;";
	sqlite3_get_table(db,sql1,&azResult,&nrow,&ncolumn,&errmsg);
	j = j+ nrow;
	memset(ack_ID_info,0,4);
	sprintf(ack_ID_info,"%d",j);//---itoa

(2)验证登录
验证起始也跟前面“存储”差不多,就是:客户端获取登录信息(ID,passwd)–发送给服务器–服务器在用户账号信息表中查找是否有相关记录,有则代表登录成功,没有就代表登录失败–返回登录状态–若登录成功则也返回(name)。

sprintf(sql,"select * from hwy_id_sheet where id = '%s' and passwd = '%s';",
			client_ID_info.id,client_ID_info.passwd);
	sqlite3_get_table(db,sql,&azResult,&nrow,&ncolumn,&errmsg);

(三)群聊OR私聊
这好办,程序中,当写入的聊天信息中“目标ID”为“999”时发给所有人,也就是所谓的群聊;当写入的聊天信息中“目标ID”为其他ID如“101”时,则发给对应用户。

void SendMsgToAll(char *msg)
{
  int i;
  for(i=0;i<CLIENT_MAX;i++){
    if(id_to_fd[i].client_fd != 0){
      printf("sendto%s\n",id_to_fd[i].client_id);
      printf("%s\n",msg);
      send(id_to_fd[i].client_fd,msg,strlen(msg),0);
    }
  }
}
void SendMsgToSb(int destfd,char *msg)
{
  int i;
  for(i=0;i<CLIENT_MAX;i++){
    if(id_to_fd[i].client_fd == destfd ){
  		printf("sendto%s\n",id_to_fd[i].client_id);
  		printf("%s\n",msg);
  		send(destfd,msg,strlen(msg),0);
		break;
  	}
  }
}

(四)服务器端与客户端对应的fd和ID如何绑定?
因为服务器端accept客户端后会产生client_sockfd,之后服务器就用这个client_sockfd来与客户端通信,然而我们私聊时是无法得知服务器给某个客户端分配了哪一个client_sockfd的,只能通过ID来指定通信用户,所以就产生了这个问题了。至于怎么绑定,看下面结构体就知道了~

//id--fd结构体类型
typedef struct
{
	int client_fd;
	char client_id[4];
}client_id_to_fd;

(1)服务器每接收到客户端连接时就会产生一个对应的fd,这事写进client_fd中;
(2)服务器验证客户端登录账号成功时会获取一个正确的ID,这时再写进去。
由此也就实现了这一绑定了,之后怎么通信,看上面步骤(三)。

三、完整代码

(1)头文件–hwy_network_chat.h

#ifndef __HWY_NETWORK_CHAT_H__
#define __HWY_NETWORK_CHAT_H__

//聊天室中能注册登陆的最大人员数量
#define  PEOPLE_NUM_MAX   10
//聊天信息(发送结构体)长度
#define  CHAT_STRUCT_SIZE (POSITION_CONTENT+128)
//客户端最大连接数量
#define  CLIENT_MAX       3

#define  POSITION_SELFID  0
#define  POSITION_NAME    (4+POSITION_SELFID)
#define  POSITION_DESTID  (4+POSITION_NAME)
#define  POSITION_TIME    (4+POSITION_DESTID)
#define  POSITION_CONTENT (26+POSITION_TIME)

//聊天室中人员身份信息
typedef struct 
{
  char id[4];	 //登陆帐号
  char name[4];	 //昵称
  char passwd[8];//登陆密码
}hwy_people_t;

//聊天消息之发送的格式
typedef struct
{
  char self_id[4];	//自身ID
  char name[4];		//自身昵称
  char dest_id[4];	//目标ID
  char time[26];	//发送时间
  char content[128];	//发送内容
}hwy_send_msg_t;

//聊天消息之接收的格式
typedef struct
{
  char who[4];		//发送者
  char time[26];	//发送时间
  char content[128];	//发送内容
}hwy_recv_msg_t;



#endif

(2)client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include "hwy_network_chat.h"
#include <stdlib.h>

//登录者帐号信息
static hwy_people_t client_id;

/*
 * 功能:建立网络通信的初始化工作,包括创建套接字和绑定可用端口号
 * 输入参数:无
 * 输出参数:成功返回用于网络通信的套接字,失败返回-1
 */
int sock_init(void)
{
  int ret;
  int portnum;//端口号
  //创建套接字--设置通信为IPv4的TCP通信
  int sockfd = socket(AF_INET,SOCK_STREAM,0);
  if(sockfd == -1)
  {
    perror("socket failed");
    return -1;
  }
  
  //配置网络结构体
  /*从50001开始,查找主机中尚未被占用的端口号,然后绑定
   */
  struct sockaddr_in myaddr;
  portnum = 50001;
  while(1){
    if(portnum > 65535){
      printf("bind error because of no port number available!\n");
      return -1;
    }
    myaddr.sin_port = htons(portnum++);       //应用层端口号
    myaddr.sin_family = AF_INET;          //IPv4协议
    myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址
    //绑定套接字和结构体----主机自检端口号是否重复,IP是否准确
    ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
    if(ret == -1)
    {
      continue;
    }
    else{
      printf("bind successfully!\n");
      break;
    }
  }
  return sockfd;
}

/*
 *功能:连接服务器
 *输入参数:套接字
 *输出参数:连接成功与否的标志,成功返回1,失败返回0
 * */
int sock_client(int sockfd)
{
  //连接服务器
  struct sockaddr_in srcaddr;
  srcaddr.sin_port = htons(50000);//服务器的应用层端口号
  srcaddr.sin_family = AF_INET;//服务器的IPv4协议
  srcaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器的IP地址-本地主机

  int ret = connect(sockfd,(struct sockaddr*)&srcaddr,sizeof(srcaddr));
  if(ret == -1)
  {
    perror("connect failed");
    return -1;
  }
  printf("connect OK\n");
  return 0; 
}



/*
 *功能:验证登录或注册帐号信息
 *输入:客户端套接字,登录/注册选择
 *输出:登录成功1,注册成功2,任意失败-1
 * */
int check_id(int sockfd,int choice)
{
  char ID_info[17];//存储帐号信息以及登录/注册选择
  int i;
  char status[16];//存储登录/注册状态
  int ret;

  printf("check ID information!\n");
  memset(ID_info,0,17);
  //登录,携带ID 和密码
  if(1 == choice){
    ID_info[0]='1';
    memcpy(ID_info+1,client_id.id,4);
    memcpy(ID_info+9,client_id.passwd,8);
  }

  //注册,携带昵称和密码
  else {
    ID_info[0]='2';
    memcpy(ID_info+5,client_id.name,4);
    memcpy(ID_info+9,client_id.passwd,8);
  }

  //发送帐号信息给服务器端进行验证
  for(i=0;i<16;i++){
  	if(ID_info[i] == '\0'){
		ID_info[i] = '/';
	}
  }
  ID_info[i] = '\0';
  ret = send(sockfd, ID_info, strlen(ID_info), 0);
  if(-1 == ret){
    perror("send id_info error!");
	return -1;
  }

  //接收帐号验证信息
  memset(status,0,16);
  ret = recv(sockfd,status,16,0);
  if(-1 == ret){
    perror("recv id_info error!");
	return -1;
  }
  
  if(memcmp(status,"successfully!",13)==0){
    //登录成功
    //printf("login successfully!\n");
	send(sockfd,"ok",3,0);
	ret = recv(sockfd,client_id.name,4,0);
	if(-1 == ret){
		perror("recv ack_id_info error");
		return -1;
	}
	return 1;
  }
  else if(memcmp(status,"sign up",7)==0){
    //注册成功
    //printf("sign up successfully!\n");
	send(sockfd,"ok",3,0);
	ret = recv(sockfd,client_id.id,4,0);
	if(-1 == ret){
		perror("recv ack_id_info error");
		return -1;
	}
	return 2;
  }
  
  else 
  {
    printf("login or sign up error!\n");
	return -1;
  }
}


/*
 *功能:登录界面
 *输入:客户端套接字
 *输出:成功0,失败-1
 *注意:登录失败可以重新登录,其他失败会退出
 */
int hwy_login(int sockfd )
{
  int choice;//1--代表登录,2代表注册
  int login_status;//登录状态,-1代表失败,1代表成功
  int signup_status;//注册状态,-1代表失败,2代表成功

  while(1){
    printf("1------------login\n2------------sign up\n");
    scanf("%d",&choice);
    if(1 == choice){
      //登录,输入ID和密码
      printf("ID:");
      scanf("%s",client_id.id);  
      printf("passwd:");
      scanf("%s",client_id.passwd);

      login_status=check_id(sockfd,choice);
	  if(1 == login_status){
	  	//登录成功,进入聊天室
		printf("欢迎登录聊天室~%s\n",client_id.name);
		break;
	  }
	  else 
      	continue;
    }
    else if(2 == choice){
	  //注册,输入昵称和密码
      printf("name:");
	  scanf("%s",client_id.name);
	  printf("passwd:");
	  scanf("%s",client_id.passwd);
	  signup_status = check_id(sockfd,choice);
	  if(2 == signup_status){
	  	//注册成功,返回登录界面
		printf("注册成功\n");
		printf("你的帐号为:%s\n请重新登录\n",client_id.id);
		continue;
	  }
	  else {
	  	//注册失败
		return -1;
	  }
    }
    else {
      printf("错误!请输入正确数值!\n");
      continue;
    }
  }
  return 0;
}


/*
 *功能:获取聊天具体内容
 *输入:保存聊天内容的指针
 *输出:无
 * */
//获取聊天具体内容
void get_send_content(char get_send_buffer[CHAT_STRUCT_SIZE])
{
  int i;
  char dest[4];//目标帐号
  char time_buf[26];//时间
  time_t t;
  time(&t);
  memset(get_send_buffer,0,CHAT_STRUCT_SIZE);
  //发送者
  memcpy(get_send_buffer+POSITION_SELFID,client_id.id,4);
  memcpy(get_send_buffer+POSITION_NAME,client_id.name,4);
  //接收者
  //printf("你要发给谁?\n");
  scanf("%s",get_send_buffer+POSITION_DESTID);
  //发送内容
  //printf("你要发什么?\n");
  scanf("%s",get_send_buffer+POSITION_CONTENT);
  //发送时间
  memcpy(get_send_buffer+POSITION_TIME,ctime_r(&t,time_buf),26);  
  for(i=0;i<POSITION_CONTENT;i++){
  	if(get_send_buffer[i] == '\0')
		get_send_buffer[i] = '/';
  }
}


/*
 *功能:处理接收信息的子线程处理函数
 *
 * */
void *pthread_recv_func (void *arg)
{
  int sockfd = *(int *)arg;
  int ret;
  int i;
  hwy_recv_msg_t hwy_msg;//谁发的,什么时候发,发了什么
  char recv_buffer[CHAT_STRUCT_SIZE];//接收内容缓冲区
  printf("现在可以聊天了~\n");
  while(1){
    memset(recv_buffer,0,CHAT_STRUCT_SIZE);
    ret=recv(sockfd, recv_buffer,CHAT_STRUCT_SIZE, 0);
    if(ret == -1){
      printf("client received error!\n");
      return;
    }
    else {
		for(i=0;i<POSITION_CONTENT;i++){
			if(recv_buffer[i]== '/')
				recv_buffer[i]= '\0';
		}
		memcpy(hwy_msg.who,recv_buffer+POSITION_NAME,4);
		memcpy(hwy_msg.time,recv_buffer+POSITION_TIME,26);
		memcpy(hwy_msg.content,recv_buffer+POSITION_CONTENT,128);
        printf("%s:%s\n%s",hwy_msg.who,hwy_msg.content,hwy_msg.time);
	}
  }
}

void *pthread_send_func (void *arg)
{
  int sockfd = *(int *)arg;
  char send_buffer[CHAT_STRUCT_SIZE];//发送内容缓冲区

  while(1){
    get_send_content(send_buffer);
	//输入bye退出聊天室
	if(memcmp(send_buffer,"bye",3)== 0 ){
    	send(sockfd,"byebye~", strlen(send_buffer), 0);
		close(sockfd);
		exit(0);
	}

    send(sockfd, send_buffer, strlen(send_buffer), 0);
  }
}


int main(int argc,char *argv[])
{
  int sockfd = sock_init();
  if(sockfd == -1){
    printf("sock_init error!\n");
    return -1;
  }
  int ret = sock_client(sockfd);
  if(-1 == ret){
    printf("sock_client error!\n");
    return -1;
  }
  
  ret = hwy_login(sockfd);
  if(ret == -1){
	perror("hwy_login function error!");
	return -1;
  }
  //一个线程负责接收信息,一个线程负责发送信息
  pthread_t tid1,tid2;
  pthread_create(&tid2,0,pthread_recv_func,&sockfd);
  pthread_create(&tid1,0,pthread_send_func,&sockfd);
  while(1){
  	sleep(9);
  }

  close(sockfd);
  return 0;
}	

(3)server.c

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "hwy_network_chat.h"
#include "sqlite3.h"
#include <stdlib.h>


//id--fd结构体类型
typedef struct
{
	int client_fd;
	char client_id[4];
}client_id_to_fd;

//将用户帐号和占用的文件描述符一一对应起来,
//方便后续一对一通信
client_id_to_fd id_to_fd[CLIENT_MAX];

//数据库的连接指针
static sqlite3 *db = NULL;

/*
 *功能:建立网络通信的初始化工作,包括创建套接字和绑定可用端口号
 *输入:无
 *输出:成功返回套接字,失败返回-1
 * */
int sock_init(void )
{
  int ret;
  //创建套接字--设置通信为IPv4的TCP通信
  int sockfd = socket(AF_INET,SOCK_STREAM,0);
  if(sockfd == -1)
  {
    perror("socket failed");
    return -1;
  }

  //配置网络结构体
  struct sockaddr_in myaddr; 
  myaddr.sin_port = htons(50000);	//应用层端口号
  myaddr.sin_family = AF_INET;		//IPv4协议
  myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址

  //绑定套接字和结构体----主机自检端口号是否重复,IP是否准确
  ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
  if(ret == -1)
  {
    perror("bind failed");
    return -1;
  }
  
  return sockfd;
}

/*
 *功能:把服务器接收的信息发给所有人
 *输入:聊天信息具体内容
 *输出:无
 * */
void SendMsgToAll(char *msg)
{
  int i;
  for(i=0;i<CLIENT_MAX;i++){
    if(id_to_fd[i].client_fd != 0){
      printf("sendto%s\n",id_to_fd[i].client_id);
      printf("%s\n",msg);
      send(id_to_fd[i].client_fd,msg,strlen(msg),0);
    }
  }
}

/*
 * 功能:把服务器接收的消息发给指定的人
 * 输入:目标帐号所绑定的fd,具体聊天内容
 * 输出:无
 */
void SendMsgToSb(int destfd,char *msg)
{
  int i;
  for(i=0;i<CLIENT_MAX;i++){
    if(id_to_fd[i].client_fd == destfd ){
  		printf("sendto%s\n",id_to_fd[i].client_id);
  		printf("%s\n",msg);
  		send(destfd,msg,strlen(msg),0);
		break;
  	}
  }
}

//数据库初始化工作
//连接数据库,创建表格
void hwyDataBase_init(void )
{
  // 打开hwyhwy.db的数据库,如果数据库不存在,则创建并打开
  sqlite3_open_v2("hwyhwy.db",&db,
  				SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,//可读可写可创建
				NULL);
  if(db==NULL)
  {
	perror("sqlite3_open_v2 faield\n");
	exit(-1);
  }
  
  //在数据库中创建表格
  //ID 昵称 密码,ID为主键
  char *errmsg = NULL;
  char *sql = "CREATE TABLE if not exists hwy_id_sheet(id text primary key,\
  				name text not null,passwd text not null);";
  sqlite3_exec(db, sql, NULL, NULL, &errmsg);
}


/*
 *功能:验证客户端传来的ID信息
 *输入:服务器accept后产生的套接字
 *输出:验证状态,1登录成功,2注册成功,其他失败
 * */
int check_recv_id(int fd )
{

  int ret;
  int i;
  hwy_people_t client_ID_info; //帐号信息结构体ID_info
  char recv_ID_info[17];//客户端传来的帐号信息其登录/注册选择
  char ack_ID_info[4];
  char ack_ok[3];
  char *errmsg = NULL;
  char sql[128];

  int nrow=0;
  int ncolumn=0;
  char **azResult=NULL;
  char status[16];
  
  //接收ID_info
  memset(recv_ID_info,0,17);
  memset(ack_ID_info,0,4);
  memset(sql,0,128);
  memset(status,0,16);

  ret = recv(fd, recv_ID_info, 17, 0);
  if(-1 == ret){
    perror("recv error!");
    return -1;
  }

  //打印接收到的信息
  for(i=0;i<17;i++){
  	if(recv_ID_info[i] == '/')
		recv_ID_info[i] = '\0';
  }
  memcpy(client_ID_info.id,recv_ID_info+1,4);
  memcpy(client_ID_info.name,recv_ID_info+5,4);
  memcpy(client_ID_info.passwd,recv_ID_info+9,8);
  
  //登录,验证输入的ID和passwd是否正确
  if(recv_ID_info[0] == '1'){
	sprintf(sql,"select * from hwy_id_sheet where id = '%s' and passwd = '%s';",
			client_ID_info.id,client_ID_info.passwd);
	sqlite3_get_table(db,sql,&azResult,&nrow,&ncolumn,&errmsg);
	if(nrow == 0){
		//没有匹配项,登录验证失败
		strcpy(status,"login failed!");
		send(fd,status,strlen(status),0);
		return -1;
	}
	else {
		//登录验证成功
  		memset(status,0,16);
		strcpy(status,"successfully!");
		send(fd,status,strlen(status),0);
		recv(fd,ack_ok,3,0);
	
		//在这里绑定client_fd---client_id
		for(i=0;i<CLIENT_MAX;i++){
			if(id_to_fd[i].client_fd == fd){
				memcpy(id_to_fd[i].client_id,client_ID_info.id,4);
				break;
			}
		}
		
		//发送用户昵称
	   	strcpy(ack_ID_info,azResult[4]);
		send(fd,ack_ID_info,strlen(ack_ID_info),0);
		return 1;
	}
  }

  //注册,根据昵称和密码注册、记录帐号信息,并返回帐号
  else {
    int j = 100;
	char *sql1 = "select * from hwy_id_sheet;";
	sqlite3_get_table(db,sql1,&azResult,&nrow,&ncolumn,&errmsg);
	j = j+ nrow;
	memset(ack_ID_info,0,4);
	sprintf(ack_ID_info,"%d",j);//---itoa
	memcpy(client_ID_info.id,ack_ID_info,4);

	sprintf(sql,"insert into hwy_id_sheet values('%s','%s','%s'); ",client_ID_info.id,
				client_ID_info.name,client_ID_info.passwd);
	ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
	if(ret == SQLITE_OK){
		printf("注册成功\n");
		memset(status,0,16);
		strcpy(status,"sign up");
		send(fd,status,strlen(status),0);
		recv(fd,ack_ok,3,0);
		
		//发送用户帐号信息
		send(fd,ack_ID_info,strlen(ack_ID_info),0);
		return 2;
	}
	else {
		printf("注册失败\n");
		memset(status,0,16);
		strcpy(status,"sign up error");
		send(fd,status,strlen(status),0);
		return -1;
	}
  }
}

//每接收一个客户端的连接,便创建一个线程
void * thread_func (void *arg)
{
  int fd = *(int *)arg;
  int ret;
  int i;
  hwy_send_msg_t hwy_C_SendMsg;
  printf("pthread = %d\n",fd);
   
  char recv_buffer[CHAT_STRUCT_SIZE];
  
  //验证登录/注册信息
  while(1){ 
  	ret = check_recv_id(fd);
	printf("check_recv_id = %d\n",ret);
	if(ret == 1){
		//成功登录
		printf("登录成功\n");
		break;
	}
	else if(ret == 2){
		//注册成功,需要重新登录
		continue;
	}
	else {
		//验证失败,服务器不退出
		continue;
	}
  }

  //登录成功,处理正常聊天的信息--接收与转发
  while(1){
    memset(recv_buffer,0,CHAT_STRUCT_SIZE);
    ret = recv(fd, recv_buffer, CHAT_STRUCT_SIZE, 0);
    if(-1 == ret){
      perror("recv error!");
      return;
    }
    else if(ret>0){
      printf("接收到的内容为:%s\n",recv_buffer);

      if(memcmp(recv_buffer+POSITION_DESTID,"999",3)== 0)
        SendMsgToAll(recv_buffer);
      else{
		for(i = 0;i< CLIENT_MAX;i++){
			if(memcmp(id_to_fd[i].client_id,recv_buffer+POSITION_DESTID,\
				3)== 0){
        		SendMsgToSb(id_to_fd[i].client_fd,recv_buffer);
				break;
			}
		}
	  
	  }
    }
  
  }  
}

void service(int sock_fd)
{
  printf("服务器启动...\n");
  listen(sock_fd,CLIENT_MAX);
  while(1){
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int client_sock = accept(sock_fd,(struct sockaddr*)&clientaddr,&len);
    if(client_sock== -1)
    {
      printf("accept failed...\n");
      continue;
    }
    printf("accept OK!\n");
    int i;
    for(i=0;i<CLIENT_MAX;i++){
      if(id_to_fd[i].client_fd == 0){
        id_to_fd[i].client_fd = client_sock;
        printf("client fd = %d\n",client_sock);
        //有客户端连接之后,启动线程为此客户端服务
        pthread_t tid;
        pthread_create(&tid, 0,thread_func,&client_sock);
        break;
      }
    }
    if(i == CLIENT_MAX){
      char * str = "对不起,聊天室已满人!\n";
      send(client_sock,str,sizeof(str),0);
      close(client_sock);
    }

  }
  
}


int main(int argc,char *argv[])
{
  int sock_fd = sock_init();
  hwyDataBase_init();
  service(sock_fd);
  return 0;
}

(3)数据库相关代码
sqlite3.c sqlite3.h这个可以下载。

(4)编译方法
gcc client.c -lpthread -o client
gcc server.c sqlite.c -ldl -lpthread -o server
(路过的还请赐教下这个怎么写Makefile??~)

2019-06-13 00:35:00 u011218356 阅读数 148
  • 实战Go语言:多人聊天

    ·基于Go的网络和并发技术开发多人在线聊天室; ·功能包括:单聊、群聊、昵称、上下线通知、聊天日志等; ·技术栈:TCP通信、协程并发、文件读写、面向对象;

    584 人正在学习 去看看 欧阳桫

简介

也算是一个小任务吧!基本要求,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. 源码
    源码
2019-06-17 23:39:21 u011218356 阅读数 193
  • 实战Go语言:多人聊天

    ·基于Go的网络和并发技术开发多人在线聊天室; ·功能包括:单聊、群聊、昵称、上下线通知、聊天日志等; ·技术栈:TCP通信、协程并发、文件读写、面向对象;

    584 人正在学习 去看看 欧阳桫

简介

上一篇说明了LinuxTcp服务端,这篇说一下,Windows客户端,MFC。

环境

Windows系统:Win10
IDE:VS2008,MFC

思路

客户端思路如下:

  • 登录:客户端Tcp连接服务端成功后,可以登录服务端,登录成功
  • 主界面:类似于QQ,能看到朋友,聊天群组
  • 聊天:单人聊天:点击聊天的ID,创建聊天窗口,不可以点击自己。多人聊天:点击群组ID,进入群聊天界面
    大致编程思路也是遵循,MVC,对外网络协议有单独的协议,界面层有自己单独界面结构体,控制层负责逻辑,进行交互。
    代码结构大概如下:
    代码结构

代码片段

看上诉代码结构,可以看出来,代码分为:单人聊天模块,群组聊天模块,配置文件读取,登录界面,主界面,网络模块,网络模块里边分为UDP,TCP,协议模块,多线程模块,入口函数模块,Other为系统自带资源文件,预编译文件
资源视图
UI文件如下,单人聊天界面,群组聊天界面,登录界面,主界面
下面按模块来看代码:

  • AloneTalk
    AloneTalk界面
按界面来说,分为了消息输入区,消息显示区,发送按钮。
界面控件都为:IDET_BOX,只不过消息显示区做了一个禁止输入操作,都支持多行操作。

代码如下:
自定义函数只有四个:

	afx_msg void OnBnClickedButton1(); //发送消息
	void ShowRecvMsg(char * msg,unsigned short usID);//显示接收消息
	AloneTalking * GetWindPaint();//得到窗口指针
	void SetSendID(unsigned short usID);//设置发送消息ID
	unsigned short usSendID;//定义发方ID
void AloneTalking::OnBnClickedButton1()
{
	// TODO: Add your control notification handler code here
	CString m_msg;
	CString buf_msg;
	CString strID;
	strID.Format("%d:\r\n\t",UsingID);
	GetDlgItemText(IDC_EDIT3,m_msg);
	GetDlgItem(IDC_EDIT3)->SetWindowText("");
	msg_show.GetWindowText(buf_msg);
	buf_msg+="\r\n";
	buf_msg+=strID;
	buf_msg+=m_msg;
	msg_show.SetWindowText(buf_msg);
	msg_show.LineScroll(msg_show.GetLineCount()-1,0);
	SendMeg(m_msg);
}

void AloneTalking::ShowRecvMsg(char * msg,unsigned short usID)
{
	CString m_msg(msg);
	CString buf_msg;
	CString strID;
	strID.Format("%d:\r\n\t",usID);
	msg_show.GetWindowText(buf_msg);
	buf_msg+="\r\n";
	buf_msg+=strID;
	buf_msg+=m_msg;
	msg_show.SetWindowText(buf_msg);
	msg_show.LineScroll(msg_show.GetLineCount()-1,0); //自动索引到最后一行
}

AloneTalking * AloneTalking::GetWindPaint()
{
	return this;
}

void AloneTalking::SendMeg(CString msg)
{
	Msg_pack m_pack = {0};
	m_pack.m_Msg_Header.usMsgID = AloneMsg;
	m_pack.m_Msg_Header.usSendID = UsingID;
	m_pack.m_Msg_Header.usRecvID = usSendID;

	int len = msg.GetLength();
	//	if(len>Msg_MaxLen) msg.
	memcpy(m_pack.strMsg,msg,msg.GetLength());
	m_pack.m_Msg_Header.usMsgLen = sizeof(m_pack);
	char SendMsg[PACK_MAXLEN] = {0};
	memcpy(SendMsg,&m_pack,sizeof(Msg_pack));
	m_TcpNetWork.ClientSend(SendMsg,sizeof(SendMsg)); //调用客户端发送
}

void AloneTalking::SetSendID(unsigned short usID)
{
	usSendID = usID;
}
  • 多人聊天
    多人聊天界面
    比单人聊天界面,多了一个LIST,用来显示群组人员。
    基础逻辑和单人聊天一样,
	void ClubNumberInit(std::vector<unsigned short> vec_list); //人员列表初始化
	afx_msg void OnBnClickedButton1();
	void ShowRecvMsg(char * msg,unsigned short usID);
	void SendMeg(CString msg); //发送消息
	void SetClubID(unsigned short usID);
	unsigned short usClubID;
	CEdit msg_show;
void ClubTalk::ClubNumberInit(std::vector<unsigned short> vec_list)
{
	//人员列表初始化
	int len = vec_list.size();
	for (int i = 0;i<len;i++)
	{	
		m_ClubList.AddString((LPCTSTR)ListItemName(vec_list[i]));
	}
}
  • 初始化登录界面
    登录界面
    界面比较简单:用户ID输入窗口,ip地址输入窗口,登录按钮,退出按钮
    功能不做简介了,说一个逻辑
	bool TcpConnect(); //Tcp连接
	CwTalkDlg dlg; //主对话框比那辆
	bool connectFlag;//连接标志位
	afx_msg void OnBnClickedBtnlog(); //登录按钮响应事件
	void LogingMsg();//登录消息发送

构造函数的处理:Tcp连接

LogDlg::LogDlg(CWnd* pParent /*=NULL*/)
	: CDialog(LogDlg::IDD, pParent)
{
	connectFlag=TcpConnect();
}
bool LogDlg::TcpConnect()
{	
	if(m_TcpNetWork.NetworkInit()){
		m_TcpNetWork.PortInit();
		if(!m_TcpNetWork.ConnectServer()){
			//	MessageBox(NULL,"服务器连接失败!\n请检查服务器是否开启或者检查网络连接情况!","警告",0);
			return false;
		}else{
			return true;
		}
	}else
	{
		//		AfxMessageBox("套接字初始化失败!");
		return false;
	}	
}

// LogDlg message handlers

void LogDlg::LogingMsg()
{
	CString csID;
	CString csip;
	LogStruct m_logstruct = {0};

	GetDlgItemText(IDC_EDIT1,csID);
	m_logstruct.ID = _ttoi(csID);
	UsingID =  _ttoi(csID);
	GetDlgItemText(IDC_IPADDRESS1,csip);
	memcpy(m_logstruct.ip,csip,csip.GetLength());
	ResDel m_ResDel;

	m_ResDel.m_MsgHeader.usMsgLen = sizeof(ResDel);
	m_ResDel.m_MsgHeader.usMsgID = Res;
	m_ResDel.m_MsgHeader.usSendID = UsingID;
	m_ResDel.m_MsgHeader.usRecvID = ServerID;
	m_ResDel.usID = m_logstruct.ID;
	memcpy(m_ResDel.strIp,m_logstruct.ip,sizeof(m_logstruct.ip));
	char Buf[128] = {0};
	memcpy(Buf,&m_ResDel,sizeof(ResDel));
	//	sprintf(Buf,"%d%d%d%d%d%s",m_ResDel.m_MsgHeader.usMsgID,m_ResDel.m_MsgHeader.usMsgLen,m_ResDel.m_MsgHeader.usSendID,m_ResDel.m_MsgHeader.usRecvID,m_ResDel.usID,m_ResDel.strIp);
	m_TcpNetWork.ClientSend(Buf,sizeof(Buf));
}

void LogDlg::OnBnClickedBtnlog()
{
	// TODO: Add your control notification handler code here
	if(connectFlag==true){
		LogingMsg();
		LogDlg::OnCancel();
		dlg.DoModal();
	}else{
	}
}

需要注意的地方:按下登录按钮,Tcp连接成功,登录消息发送,登录界面隐藏,主界面唯一实例化。如果测试,可以把登录界面隐藏,主界面显示放出来。不进行判断。

	if(connectFlag==true){
		LogingMsg();
		LogDlg::OnCancel();
		dlg.DoModal();
	}
  • 主界面
    主界面
    主界面比较简单,上方List是用户ID显示区,下方List是群组ID显示区
    主界面的逻辑稍微多一些。
    定时器定义,TcpRecv接收多线程函数
#define UDP_TIMER 1
#define TCP_TIMER 2

DWORD WINAPI TcpRecv(LPVOID lpParamter);
	void InitTimer(); //定时器初始化
	void UDPInit();//udp初始化
	bool closeflag;//关闭标志位
	config m_config;
	AloneTalking * p_AloneTalking; //单人对话框指针
	ClubTalk * p_ClubTalk; //多人对话框指针
	afx_msg void OnTimer(UINT_PTR nIDEvent); //定时器响应函数
	afx_msg void OnClose();
	afx_msg void OnLbnDblclkList2();//用户窗口双击消息事件
	afx_msg void OnLbnDblclkList1();//群组窗口双击响应事件
	private:
	UDPMsg m_Udpmsg; //UDP 消息变量
	UDPNetwork m_UdpNetwork;//UDP 变量
	CString strID;	
	void ListViewInit(); //List初始化
	
	void CreatAloneDlg(unsigned short usID); //创建单人聊天窗口
	void CreatClubDlg(unsigned short usClubID);//创建多人聊天窗口
	bool elemfind(unsigned short usID,std::vector<unsigned short> m_list); //元素寻找
	CListBox m_ListBox;//用户List
	CListBox m_ClubListbox;//群组List

	std::vector<unsigned short> m_NumberListBuf;//用户ID缓冲区
	std::vector<unsigned short> m_ClubListBuf;//群组ID缓冲区
	std::map<unsigned short,AloneTalking *> m_AloneWindow;//用户聊天窗口,用户ID map
	std::map<unsigned short,ClubTalk *> m_ClubWindow;//群组聊天窗口,群组ID map

	AloneTalking * GetAlonePaint(unsigned short usID); //得到用户聊天窗口指针
	ClubTalk * GetClubPaint(unsigned short usID);//得到群组聊天窗口指针
	void Msg_Pro();//Tcp消息初步处理
	void Msg_Process(unsigned short usMsgID,int len,char * RecvBuf);	//Tcp消息详细解析

构造函数,析构函数

CwTalkDlg::CwTalkDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CwTalkDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_Udpmsg.iNo = 0;
	m_config.InfoInit();
}

void CwTalkDlg::DoDataExchange(CDataExchange* pDX)
{
	KillTimer(UDP_TIMER);
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST2, m_ListBox);
	DDX_Control(pDX, IDC_LIST1, m_ClubListbox);
}
void CwTalkDlg::InitTimer()
{
	SetTimer(UDP_TIMER,1000,NULL);
	SetTimer(TCP_TIMER,200,NULL);
}

void CwTalkDlg::UDPInit()
{
	m_UdpNetwork.SocketInit();
	InitTimer();
}
//定时器消息处理函数
void CwTalkDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default
	char  buf_msg[6];
	int len = 0;
	switch(nIDEvent)
	{
	case 1: //UDP消息发送
		m_Udpmsg.usID = 10001;
		m_Udpmsg.iNo = 1;
		len = sizeof(UDPMsg);		

		memset(&buf_msg,0,len);
		memcpy(&buf_msg,&m_Udpmsg,len);

	//	m_UdpNetwork.SocketSendMsg(buf_msg,len);
		break;
	case 2:
		Msg_Pro();//接收消息解析
		break;
	default:
		break;

	}
	CDialog::OnTimer(nIDEvent);
}

void CwTalkDlg::OnClose()
{
	// TODO: Add your message handler code here and/or call default
	closeflag = true;
	CDialog::OnClose();
}

CString GetListItemName(unsigned short usID)
{
	char s[6];
	sprintf(s,"%d",usID);
	CString linename(s);
	return linename;
}

void CwTalkDlg::ListViewInit()
{
	/*人员列表初始化*/
	int len = m_config.vec_NumberList.size();
	for (int i = 0;i<len;i++)
	{	
		m_ListBox.AddString((LPCTSTR)GetListItemName(m_config.vec_NumberList[i]));
	}
	/*群组列表初始化*/
	int iClublen = m_config.map_Club.size();
	std::map<unsigned short,std::vector<unsigned short>>::iterator it;
	for ( it = m_config.map_Club.begin();it != m_config.map_Club.end();it++)
	{
		m_ClubListbox.AddString((LPCTSTR)GetListItemName(it->first));
	}

}
//双击,对应的群组ID相,创建群组聊天窗口
void CwTalkDlg::OnLbnDblclkList2()
{
	// TODO: Add your control notification handler code here
	int iSel = m_ListBox.GetCurSel();
	unsigned short usID = m_config.vec_NumberList[iSel];
	if(!elemfind(usID,m_NumberListBuf))	{
		m_NumberListBuf.push_back(usID);
		CreatAloneDlg(usID);
	}else{
		AloneTalking * p = GetAlonePaint(usID);
		p->ShowWindow(SW_SHOW);
	}	
}
//创建单人聊天窗口
void CwTalkDlg::CreatAloneDlg(unsigned short usID)
{
	p_AloneTalking = new AloneTalking;
	p_AloneTalking->Create(AloneTalking::IDD,this);
	CString AloneTitle;
	AloneTitle.Format("与%d的对话",usID);
	p_AloneTalking->SetSendID(usID);
	p_AloneTalking->SetWindowText(AloneTitle);
	p_AloneTalking->ShowWindow(SW_SHOWNORMAL);
	std::pair<unsigned short,AloneTalking *> buf(usID,p_AloneTalking);
	m_AloneWindow.insert(buf);
}
//创建群组聊天窗口,根据对应ID
void CwTalkDlg::CreatClubDlg(unsigned short usClubID)
{
	p_ClubTalk = new ClubTalk;
	p_ClubTalk->Create(ClubTalk::IDD,this);
	CString AloneTitle;
	AloneTitle.Format("%d群组对话",usClubID);
	p_ClubTalk->SetClubID(usClubID);
	p_ClubTalk->SetWindowText(AloneTitle);
	std::vector<unsigned short>m_ClubList = m_config.map_Club[usClubID];
	p_ClubTalk->ClubNumberInit(m_ClubList);
	p_ClubTalk->ShowWindow(SW_SHOWNORMAL);
	std::pair<unsigned short,ClubTalk *> buf(usClubID,p_ClubTalk);
	m_ClubWindow.insert(buf);
}
//双击,对应群组ID相,有,则显示,无,则创建
void CwTalkDlg::OnLbnDblclkList1()
{
	// TODO: Add your control notification handler code here
	int iItem = m_ClubListbox.GetCurSel();
	CString sText;
	m_ClubListbox.GetText(iItem,sText);
	unsigned short usClubID = atoi(sText);
	if(!elemfind(usClubID,m_ClubListBuf)){
		m_ClubListBuf.push_back(usClubID);
		CreatClubDlg(usClubID);
	}else{
		ClubTalk * p =GetClubPaint(usClubID);
		p->ShowWindow(SW_SHOWNORMAL);
	}
}

bool CwTalkDlg::elemfind(unsigned short usID,std::vector<unsigned short> m_list)
{
	std::vector<unsigned short>::iterator iter_find = std::find(m_list.begin(),m_list.end(),usID);
	if (iter_find != m_list.end())	{
		return true;
	}else{
		return false;
	}
}

AloneTalking * CwTalkDlg::GetAlonePaint(unsigned short usID)
{
	AloneTalking * p ;
	//收方ID,寻找对应的窗口指针,有,则显示,无则创建
	std::map<unsigned short,AloneTalking *>::iterator it = m_AloneWindow.find(usID);
	if(it != m_AloneWindow.end()){
		p = it->second;		
	}else{
		CreatClubDlg(usID);
		p = m_AloneWindow[usID];
	}
	return p;
}

ClubTalk * CwTalkDlg::GetClubPaint(unsigned short usID)
{
	ClubTalk * p;
	//收方ID,寻找对应的窗口指针,有,则显示,无则创建
	std::map<unsigned short,ClubTalk *>::iterator it = m_ClubWindow.find(usID);
	if(it != m_ClubWindow.end()){
		p = it->second;		
	}else{
		CreatAloneDlg(usID);
		p = m_ClubWindow[usID];
	}
	return p;
}
//对消息缓冲区的消息进行处理
void CwTalkDlg::Msg_Pro()
{
	int vec_msglen = vec_msg.size();
	MsgHeader m_MsgHeader;
	char Buf_Recv[280] = {0};
	if(vec_msglen>0)
	{
		for(int i =0 ;i<vec_msglen;i++)
		{
			memcpy(Buf_Recv,vec_msg[i],280);
			memcpy(&m_MsgHeader,Buf_Recv,sizeof(MsgHeader));
			Msg_Process(m_MsgHeader.usMsgID,m_MsgHeader.usMsgLen,Buf_Recv);
		}
		vec_msg.clear();
	}else{  

	}
}

void CwTalkDlg::Msg_Process(unsigned short usMsgID,int len,char * RecvBuf)
{
	Msg_pack m_Msg_pack = {0};
	CString msg;
	AloneTalking * p;
	switch(usMsgID)
	{
	case Res:
		break;
	case Del:
		break;
	case AloneMsg: //单人消息显示
		memcpy(&m_Msg_pack,RecvBuf,sizeof(Msg_pack));
		GetAlonePaint(m_Msg_pack.m_Msg_Header.usSendID)->ShowRecvMsg(m_Msg_pack.strMsg,m_Msg_pack.m_Msg_Header.usSendID);
		break;
	case ClubMsg://群组消息显示
		memcpy(&m_Msg_pack,RecvBuf,sizeof(Msg_pack));
		//得到消息窗口指针,显示消息
		GetClubPaint(m_Msg_pack.m_Msg_Header.usRecvID)->ShowRecvMsg(m_Msg_pack.strMsg,m_Msg_pack.m_Msg_Header.usSendID);
		break;
	case CreatCmd:

		break;
	case DelCmd:
		break;
	default:
		break;
	}
}

/*多线程接收*/
HANDLE p_RecvEvent;
DWORD WINAPI TcpRecv(LPVOID lpParamter)
{	
	char RecvMsg[PACK_MAXLEN];
	while(1)
	{
		m_TcpNetWork.ClientRecv(RecvMsg);
		if(strlen(RecvMsg)>0)
		{
			char Buf_Msg[PACK_MAXLEN] ={0};
			memcpy(Buf_Msg,RecvMsg,sizeof(RecvMsg));
			vec_msg.push_back(Buf_Msg);
			memset(RecvMsg,0,sizeof(RecvMsg));
		}else{
			
		}
	}	
}
  • 网络相关(Tcp,Udp的初始化不做讲解)
  • TCP
#include <WinSock2.h>
#pragma comment(lib,"WS2_32.lib")

#define MAXLEN 1024
#define SendPort 2000

class TcpNetWork
{
public:
	TcpNetWork(void);
	~TcpNetWork(void);
	bool NetworkInit();//套接字初始化;Tcp
	void PortInit(); //端口初始化
	bool ConnectServer(); //连接服务器
	char * ClientRecv(char * Buf_Recv);//客户端接收
	void ClientSend(char * Buf_Recv,int Msg_len);//客户端发送
private:
	WSADATA data;
	SOCKET ClientSocket;
	SOCKADDR_IN ClientAddr;
	SOCKADDR_IN ServerAddr;
};
bool TcpNetWork::NetworkInit()
{
	bool connectstate = 0;
	WSAStartup(MAKEWORD(2,2),&data);
	ClientSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(INVALID_SOCKET == ClientSocket){
		return false;
	}else{
		return true;
	}
}

void TcpNetWork::PortInit()
{
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons(3000);
	ServerAddr.sin_addr.S_un.S_addr = inet_addr("10.30.20.138");
}

bool TcpNetWork::ConnectServer()
{
	int res = connect(ClientSocket,(SOCKADDR *)&ServerAddr,sizeof(SOCKADDR_IN));
	if(res == SOCKET_ERROR)	{
		return false;
	}else{
		return true;
	}
}

char * TcpNetWork::ClientRecv(char * Buf_Recv)
{
	int iRet = recv(ClientSocket,Buf_Recv,MAXLEN,0);
	return Buf_Recv;
}

void TcpNetWork::ClientSend(char * Buf_Recv,int Msg_len)
{
	int iRet = send(ClientSocket,Buf_Recv,Msg_len,0);
	if(iRet == 0)
	{
		printf("消息发送超时!\n");
	}
}
  • UDP
#pragma comment(lib, "wsock32.lib")

#define UdpSendPort 2001
#define UdpRecvPort 3001

#define LOCALIP 10.30.20.137

class UDPNetwork
{
public:
	UDPNetwork(void);
	~UDPNetwork(void);

	void SocketInit();
	void SocketSendMsg(char * strMsg,int ilen);

private:

	WSADATA data;
	SOCKET SendSocket;
	SOCKADDR_IN addr;
	SOCKET TargetSocket;
	SOCKADDR_IN TargetAddr;
};
void UDPNetwork::SocketInit()
{
	WSAStartup(MAKEWORD(2,2),&data);
	SendSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(UdpSendPort);
	addr.sin_addr.S_un.S_addr=inet_addr("10.30.20.137");
	bind(SendSocket,(SOCKADDR *)&addr,sizeof(addr));

	TargetAddr.sin_family = AF_INET;
	TargetAddr.sin_port =htons(UdpRecvPort);
	TargetAddr.sin_addr.S_un.S_addr = inet_addr("10.30.20.138");
}

void UDPNetwork::SocketSendMsg(char * strMsg,int ilen)
{
	int len = sizeof(TargetAddr);
	int flag = sendto(SendSocket,strMsg,ilen,0,(const SOCKADDR*)&TargetAddr,len);
}

不足:

说一下不足,因为时间关系,还有最初进行设计的时候,考虑不周,导致最后功能模块全部实现,但是不是预初的设计。比如:只实现了主体功能,一些,用户注册,用户登录,用户注销,列表的动态维护,创键群组,删除群组,等其他功能,虽然只是简单的周边的功能,调用现有函数均可实现,Tcp服务端的TCP网络模型的使用,是Tcp服务端容错率,收发效率更高。

源码

我应经将工程转为VS2017的工程了。
源码正在审核中,随后上传。

java tcp多人聊天室

阅读数 1512

没有更多推荐了,返回首页