一个简单的Java聊天程序

1.写服务器端

因为是简单的网络聊天,所以我们需要服务器端来处理发送的信息在这里插入图片描述

这个是我们的服务器处理图,我们在这里省略了注册步骤,因为在后期的Java应用中,我们基本用不到Swing所以这里我们直接使用下面的控制台进行输入输出。
需求分析
因为我们需要发送信息,所以我们至少需要两个集合,一个用来接收信息并将其列入队列,以便在处理的时候我们不出现输出序列问题,我们是聊天程序,所以不会是一个人聊天,我们需要一个用户集合,来保存用户的数据,方便我们知道是谁发送的消息,以及用户的IP,端口等等。
这里就是生产关系和消费关系,我们需要一个生产消息的队列和一个消费消息的队列。
代码如下:

import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ChatServer {
    /**
     * 客户端连接集合
     */
    private ConcurrentHashMap<String, Socket> allCustomer = new ConcurrentHashMap<>();
    
    /**
     * 存放消息的队列
     */
    private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();
}

2.创建接收线程

因为接收线程只在服务器端使用,所以单独写出来没有意义,秉承着数据简单,我们使用数据专家模式,将方法写在离数据最近的地方,我们就将它西在服务器里面,写成一个内部类。

import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ChatServer {
    /**
     * 所以客户端连接集合
     */
    private ConcurrentHashMap<String, Socket> allCustomer = new ConcurrentHashMap<>();
    
    /**
     * 存放消息的队列
     */
    private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();

    /**
     * 创建接收线程
     * 内部类因为已经可以访问外部类的所有属性和方法,所以没必要再创建
     */
    private class ReceiveService extends Thread{
    //            this.messageQueue = messageQueue;
//        }
	public void run(){
	
		}
	}
}

下面我们先把接收线程的具体工作放一下,考虑一下接收线程中的Socket怎么得到的呢?
显然需要我们之前学过的监听方法。

4.添加监听客户端连接的代码

Socket.accept()就是我们之前学习过的监听客户端连接的方法,它的返回值是一个Socket类型的,而监听它的是ServerSocket类型,ServerSocket会申请一个端口和地址,当有服务器连接到这个地址和端口的时候,使用accept就能将它和服务器连接在一起。

/**
     * 监听
     */
    public void start(){
        ServerSocket serverSocket = null;
                Socket client = null;
        try {
            //申请端口
            serverSocket = new ServerSocket(port);
                        while (true) {
                //监听客户端连接
                System.out.println("开始监听新的客户端连接...");
                                client = serverSocket.accept();
                System.out.println("监听到客户端【" + client.getInetAddress()
                       .getHostAddress() + ":" + client.getPort() + "】");
                                       //提供消息服务
                new ReceiveService(client).start();
                //把socket放进客户socket集合,以便发送线程使用
                 allCustomer.put(client.getInetAddress().getHostAddress(),client);
                //监听下一个
            }
                    } catch (Exception e) {
            e.printStackTrace();
        }
    }

5.完成接收服务线程

在网络上进行传输就是对流的使用,因为我们只是做聊天系统,所以我们只需要选择字符流,并且Buffer字符流的readLine()非常好用,所以选择它。
!!注意:socket只能得到字节流,所以要把它包装成字符流得用InputStreamReadedr再包装一下

public void run(){
            //因为接收字符所以选择字符流,并且Buffer字符流的readLine()非常好用,所以选择它
            BufferedReader br = null;
            try {
                //注意socket只能得到字节流,所以要把它包装成字符流得用InputStreamReadedr再包装一下
                br = new BufferedReader(
                        new InputStreamReader(client.getInputStream()));
                while (true) {
                    //接收消息
                     System.out.println("等待接收客户端【" + client.getInetAddress()
                                       .getHostAddress() + "】消息");
                    String mesg = br.readLine();
                    System.out.println("接收到客户端【" + client.getInetAddress()
                                       .getHostAddress() + "】消息【" + mesg + "】");
                    //放入消息队列
                    messageQueue.offer(mesg);
                    //接收下一条
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

6.定义发送线程

这里我们使用简单遍历,将所有的信息全部打印出来

/**
     * 创建发送线程
     */
    private class SendService implements Runnable{

        @Override
        public void run() {
            try {
                PrintWriter pw = null;
                while (true) {
                   //取消息队列中的消息
                    String mesg = messageQueue.poll();
                    if(mesg != null) {
                        //遍历客户连接
                        for (Socket socket : allCustomer.values()) {
                        //创建字符输出流半配网络字节流
                            pw = new PrintWriter(socket.getOutputStream());
                            //向客户端发送消息
                            pw.println(mesg);
                             pw.flush();
                        }
                        //到队列里取下一条消息
                    }
                }
                 } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

我们可以在start刚开始的时候就启动发送消息的线程。防止在后面输入时消息发送不正确。

7.思考线程协做的问题

在一般的网络数据传输中,我们都将数据转化为JSON的形式发送以及接收。我们在这里举一个例子,来说明JSON对象的传输。

import net.sf.json.JSONObject;
import org.junit.Test;

public class Testjson {
    @Test
    public void Test1(){
        Studnet s=new Studnet("zhangsan",12,1001);
        JSONObject js= JSONObject.fromObject(s);
        System.out.println(js.toString());
    }
    @Test
    public void Test2(){
        String jsstr="{\"age\":12,\"namber\":1001,\"name\":\"zhangsan\"}";
        JSONObject object=JSONObject.fromObject(jsstr);
        Studnet o= (Studnet) JSONObject.toBean(object,Studnet.class);
        System.out.println(o);

    }
}

其中Student类为:

public class Studnet {
    private String name;
    private int age;
    private int namber;

    @Override
    public String toString() {
        return "Studnet{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", namber=" + namber +
                '}';
    }

    public Studnet(String name, int age, int namber) {
        this.name = name;
        this.age = age;
        this.namber = namber;
    }
    public Studnet(){}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getNamber() {
        return namber;
    }

    public void setNamber(int namber) {
        this.namber = namber;
    }
}

我们将输出结果发出来:
在这里插入图片描述

在这里插入图片描述
这里我们就将Student对象转换为JSON对象,并且将它转换回来了。
因为我们要频繁的使用将字符串和JSON对象相互转换的方法,所以我们将这个方法单独写一个工具包,将转换的方法放进去。

package edu.xatu.util;

import net.sf.json.JSONObject;

public class JSONUtil {
    /**
     * 对象转json的方法
     * @return
     */
    public static String boj2json(Object obj){
        JSONObject ob=JSONObject.fromObject(obj);
        return ob.toString();
    }
    public static  <T> T json2obj(String jsonstr,Class<T> t){
        JSONObject object=JSONObject.fromObject(jsonstr);
        return (T)JSONObject.toBean(object,t);
    }
}

以及消息的标识符:

package edu.xatu.util;

import java.util.Date;

public class MessageVO {
    private String mesg;
    private Date date;
public MessageVO(){}
    public MessageVO(String mesg, Date date) {
        this.mesg = mesg;
        this.date = date;
    }

    public String getMesg() {
        return mesg;
    }

    public void setMesg(String mesg) {
        this.mesg = mesg;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

编写服务端启动类

public class SeverStart {
    public static void main(String[] args) throws Exception {
        new ChatSever().start();
    }
}

9.写客户端

9.1客户端知道服务器的地址和端口,先编写客户端类直接连接服务器

import java.io.IOException;
import java.net.Socket;
public class ChatClient {
    /**
     * 聊天服务器的地址
     */
    private String addr = "127.0.0.1";
        /**
     * 聊天服务的端口
     */
    private int port = 9999;
    public void start(){

        Socket s = null;
        try {
            //客户知道服务器的地址和端口,直接创建套接字
            s = new Socket(addr,port);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端要做两件事:
1.监听服务器返回的消息,并输出到控制台
2.监听键盘消息,并发向服务器很显然,这里需要两个客户线程
9.2创建客户端接收线程
1…监听服务器返回的消息,并输出到控制台,因为离开客户端没有复用价值,所以我们也是写成ChatClient类的内部类

 /*
 *  创建监听服务器消息线程
*/
 private class ReceiveService extends Thread{
     private BufferedReader br=null;
     public void run(){
         try {
             while (true) {
                 br=new BufferedReader(
                         new InputStreamReader(s.getInputStream()));
                 String jsonStr=br.readLine();
                 //把json串转换成对象
                 MessageVO mvo= JSONUtil.json2obj(jsonStr,MessageVO.class);
                 //在控制台输出
                 System.out.println("info:"+mvo.getMesg()+"【时间:"+mvo.getDate()+"】");
             }
             //再监听有没有下一个
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }

2.创建键盘监听类,并发向服务器

private class SendService extends Thread{
    private PrintWriter pw=null;
    public void run(){
        try {
            while (true) {
                Scanner in=new Scanner(System.in);
                //监听键盘消息
                String mesg=in.nextLine();
                //封装MessageVo
                MessageVO vo=new MessageVO(mesg,new Date());
                //解析成json串
                String jsonstr=JSONUtil.boj2json(vo);
                //发送到服务器
                pw=new PrintWriter(s.getOutputStream());
                pw.println(jsonstr);
                pw.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后,要在客户端启动两个线程,这就是两个用户。

public class ChatClient {//客户端
    /*
    *聊天服务器的地址和端口
     */
    Socket s=null;
     private String addr="127.0.0.1";
     private static int port=8888;
     public void start(){
         try {
             s=new Socket(addr,port);
             //知道地址和端口,直接创建套接字
             //启动两个监听服务线程
             new ReceiveService().start();
             new SendService().start();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }

两个监听线程均依赖网络套接字,所以启动线程的代码写在创建套接后就可以
最后 ,编写客户端的启动类

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

我们现在将程序运行:
客户端1:
在这里插入图片描述
客户端2:
在这里插入图片描述
服务器端:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值