利用TCP实现多客户聊天系统

读书笔记:《Java语言程序设计》–郭克华

案例介绍

  上一篇博客已经介绍了客户端和服务器的相互通信。但是实际应用中,应该是客户端和客户端聊天,而不是客户端和服务器聊天。客户端和客户端聊天的本质是信息由服务器端转发。因此本篇博客将开发一个支持多个客户端的程序。服务器端界面如图1所示。
这里写图片描述
       图一
  当客户端出现时,需要输入昵称,如图2所示。
这里写图片描述
        图二
  单击“确定”按钮,连接到服务器。如果连接成功服务器回送一个“连接成功”的信息,如图3所示。
这里写图片描述
       图三
  单击“确定”按钮,即可进行聊天。
  为了体现多客户端效果,打开了3个客户端,如图4所示。
这里写图片描述这里写图片描述这里写图片描述
      图四
  在界面下方可以输入消息,按Enter键,消息发出。消息发出后,文本框自动清空。消息发送后,能够让各个客户端收到聊天消息,聊天消息打印在界面上的多行文本框内,在打印聊天信息的同时,还能够打印这条聊天消息的发出人。

编写服务器程序

  要让服务器端能够接受多个客户端的连接,需要注意以下几个问题:
  1.由于事先不知道客户端什么时候连接,因此,服务端必须首先有一个线程负责接受多个客户端的连接,结构如下:

public class Server extends JFrame implements Runnable {
    public Sever() {
        //服务器端打开端口
        //服务器端开启线程,接受客户端连接
    }
    public void run() {
        //不断接受客户端连接
        while(true) {
            //接受客户端连接
            //开一个聊天线程给这个客户端
            //将该聊天线程对象添加进集合
            //聊天线程启动
        }
    }
}

  2.当客户端连接上之后,服务器端要等待这些客户端传送消息过来,而实现并不知道客户端什么时候会发信息过来。所以,每一个一个客户端连上之后,必须为这个客户端单独看一个线程,读取它发过来的信息。因此,需要再编写一个线程类。
  3.服务器收到谋改革客户端信息后,需要将其转发给各个客户端,这就需要再服务端保存各个客户端的输入输出流的引用。实际上,这些引用可以保存在客户端服务的线程中。
  因此,整个服务器端程序的基本结构如下:

public class Server extends JFrame implements Runnable {
    public Server() {
        //服务器端打开端口
        //服务器端开启线程,接受客户端连接
    }
    public void run() {
        //不断接受客户端连接
        while(true) {
            //接收客户端连接
            //开一个聊天线程给这个客户端
            //将该聊天线程对象添加进集合
            //聊天线程启动
        }
    }
    /*聊天线程类,每连接上一个客户端,就为它开一个聊天线程*/
    class ChatThread extends Thread {
        //负责读取相应SocketConnection的信息
        public void run() {
            while(true) {
                //读取客户端发来的信息
                //将该信息发送给所有其他客户端
            }
        }
    }
}

服务器端详细代码如下:

package practice;
import java.awt.*;
import java.io.*;
import java.net.*;
import javax.swing.JFrame;
import java.util.ArrayList;

public class Server extends JFrame implements Runnable {
    private Socket s=null;
    private ServerSocket ss=null;
    private ArrayList clients=new ArrayList(); //保存客户端的线程
    public Server() throws Exception {
        this.setTitle("服务器端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setBackground(Color.yellow);
        this.setSize(200,100);
        this.setVisible(true);
        ss=new ServerSocket(9999);   //服务器端开辟端口,接受连接
        new Thread(this).start();   //接受客户连接的死循环开始运行
    }

    @Override
    public void run(){
        try {
            while(true) {
                s=ss.accept();
                //s就是当前的连接对应的Socket,对应一个客户端
                //该客户端随时可能发信息过来,必须要接受
                //另外开辟一个线程,专门为这个s服务,负责接受信息
                ChatThread ct=new ChatThread(s);
                clients.add(ct);
                ct.start();
            }
        }
        catch (Exception ex) {
        }
    }

    class ChatThread extends Thread {  //为某个Socket负责接受信息
        private Socket s=null;
        private BufferedReader br=null;
        public PrintStream ps=null;
        public ChatThread(Socket s) throws Exception {
            this.s=s;
            br=new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps=new PrintStream(s.getOutputStream());
        }
        @Override
        public void run() {
            try {
                while(true) {
                    String str=br.readLine();//读取该Socket传来的信息
                    sendMessage(str);        //将str转发给所有客户端
                }
            }
            catch (Exception ex) {
            }
        }
    }

    public void sendMessage(String msg) {  //将信息发给所有客户端
        for(int i=0;i<clients.size();i++) {
            ChatThread ct=(ChatThread)clients.get(i);
            //向ct内的Socket内写msg
            ct.ps.println(msg);
        }
    }

    public static void main(String[] args) throws Exception {
        new Server();
    }
}

编写客户端程序

  客户端编程相对简单,只需要编写发送信息、连接服务器、接受服务器端传送的信息即可。代码如下:

package practice;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import javax.swing.*;

public class Client extends JFrame implements ActionListener,Runnable {
    private JTextArea taMsg=new JTextArea("以下是聊天记录\n");
    private JTextField tfMsg=new JTextField();
    private Socket s=null;
    private String nickName=null;
    public Client() {
        this.setTitle("客户端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(taMsg,BorderLayout.CENTER);
        tfMsg.setBackground(Color.yellow);
        this.add(tfMsg,BorderLayout.SOUTH);
        tfMsg.addActionListener(this);
        this.setSize(280,400);
        this.setVisible(true);
        nickName=JOptionPane.showInputDialog("输入昵称:");
        try {
            s=new Socket("127.0.0.1",9999);
            JOptionPane.showMessageDialog(this,"连接成功");
            this.setTitle("客户端:"+nickName);
            new Thread(this).start();
        }
        catch (Exception ex) {
        }
    }
    @Override
    public void run(){
        try {
            while(true) {
                InputStream is=s.getInputStream();
                BufferedReader br=new BufferedReader(new InputStreamReader(is));
                String str=br.readLine();//读
                taMsg.append(str+"\n");  //添加内容
            }
        }
        catch (Exception ex) {
        }
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            OutputStream os=s.getOutputStream();
            PrintStream ps=new PrintStream(os);
            ps.println(nickName+"说:"+tfMsg.getText());
            tfMsg.setText("");
        }
        catch (Exception ex) {
        }
    }
    public static void main(String[] args) throws Exception {
        new Client();
    }
}

运行服务器和客户端,就可以得到本案例需求中的效果。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值