JAVA基于TCP的Socket同一服务器连接多客户端使用多线程完成(实现多人聊天功能)

一.题目要求如下:

1.编写客户端程序,连接同一服务器,并实现发送线程和接收线程,发送的信息中应包括“消息+发送时间”,显示消息应包括:“Socket+信息+发送时间”。(GUI实现后续完成)

2.编写服务器端程序,使用TCP Socket启动12345端口,判断该端口是否被占用,可以接收多个客户端的连接(连接成功发出欢迎信息“Welcome”+客户端套接字信息),并将Socket保存在Vector中,将收到的信息转发给所有连接的客户端。(GUI实现后续完成)

二.具体代码实现案例
1.服务器端代码如下:(先有服务器再有客户机,故此处先给出服务器端程序)

package day26.tcp.test;
/*
* 编写服务器端程序,使用TCP Socket启动12345端口,判断该端口是否被占用,可以接收多个客户端的连接
* (连接成功发出欢迎信息“Welcome”+客户端套接字信息),并将Socket保存在Vector中,将收到的信息转
* 发给所有连接的客户端
* */
import java.net.*;
import java.io.*;

public class ServerTest {
    public static void main(String[] args) throws IOException{
    
        System.out.println("服务器等待建立连接...");     //服务器端给出提示信息
        
        //创建服务器Socket对象
        while(true){
            try (ServerSocket ss = new ServerSocket(12345)) {  //判断端口12345是否被占用
               
                Socket s = ss.accept();       //监听客户端Socket对象
                int count = 1;       //记录客户机数量并标记,第一个客户机标记为1


                //启动服务器线程(可以不用多线程实现,但是为了提高效率,此处用多线程改进)
                new Thread(new UserThread(s,count)).start();
                System.out.println("连接成功!");
        
                count++;        //下一个客户机标记+1

            }catch (IOException e){
                e.printStackTrace();
            }

        }

    }
}

2.服务器端的多线程实现类UserThread

package day26.tcp.test;
/**
 * 服务器多线程实现,用来提高效率
 */

import java.io.*;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

public class UserThread implements Runnable {      //该类实现Runnable接口(多线程前提)
	//成员变量定义为私有
    private int count;
    private Socket s;
    
	//带参构造接收服务器端传来的Socket对象和客户机标记count
    public UserThread(Socket s, int count) {
        this.s = s;
        this.count = count;
    }

	//必须重写的run()方法
    @Override
    public void run() {
        try {
            SimpleDateFormat date = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); //建立连接的时间
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    s.getOutputStream()));                     //包装通道里的输出流
                    
                    
            //连接成功给出反馈:“连接时间和Welcome+Host+Port”
            String _str = s.getInetAddress().getHostName();
            String str = "Host:" + _str  + "--" + count +   "号  port:" + s.getPort() ;
            bw.write("Welcome    " + str+ "   " + date.format(new Date()));
            bw.newLine();
            bw.flush();


            //这里用List代替Vector,因为List可以用Collections工具类实现线程安全,并且比Vector高效。
            //将Socket信息添加到集合中
            List<Socket> li = Collections.synchronizedList(
                    new ArrayList<Socket>());
            li.add(s);


            BufferedReader br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));               //包装输入流
                  
                    
			//客户端收到信息后转发送给所有已连接的客户端
            String line = null;          //先给line初始化
            while ((line = br.readLine()) != null) {
                if(!li.isEmpty()) {         //先判断集合是否为空(增强for使用的前提)
                    for (Socket _s : li) {          //增强for获得集合中所有的Socket对象
                        bw.write(str + " 的客户说:" + " " + line);     //将当前客户机所发的消息转发给所有已连接客户机
                        bw.newLine();
                        bw.flush();

                    }
                }

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.客户端的代码如下:(使用发送接收线程实现)

package day26.tcp.test;
/**
*  编写客户端程序,连接上题服务器,并实现发送线程和接收线程,发送的信息中应包括
*  消息+发送时间,显示消息应包括:Socket+信息+发送时间
*  TODO:GUI?(后续完成)
* /

import java.net.*;
import java.io.*;

public class ClientTest {
    public static void main(String[] args) throws IOException {
    
        //创建Socket对象,指定ip和端口
        Socket s = new Socket("192.168.1.174",12345);


        //包装输入输出流,用作参数传递
        BufferedReader br = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));
        SeThread st = new SeThread(bw);    //发送线程类
        ReThread rt = new ReThread(br);    //接收线程类


        //启动发送接收线程
        new Thread(new SeThread(bw)).start();
        new Thread(new ReThread(br)).start();
        
    }

}

4.客户端的发送线程类SeThread

package day26.tcp.test;
/**
 * 客户端发送线程
 */

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SeThread implements Runnable {     //实现Runnable接口

    //成员变量定义私有
    private BufferedWriter bw;       
    
	//带参构造,传入输出流对象
    public SeThread(BufferedWriter bw) {
        this.bw = bw;
    }


	//重写run()方法
    @Override
    public void run() {
        try {
        	//包装键盘输入
            BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
			
			
			//客户端发送信息
            String line = null;      //初始化line
            while ((line = buf.readLine()) != null) {
                if ("886".equals(line)) {    //如果客户机输入886
                	buf.close();             //关闭键盘输入流
                    bw.close();              //关闭输出流
                    break;
                }

				//将客户输入的信息传到输出流:“信息+当前时间”
				//客户机发送信息时附带时间
                SimpleDateFormat date = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");   
                bw.write("“"+line + "”" + "   " + date.format(new Date()));
                bw.newLine();
                bw.flush();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

5.客户机接收线程类ReThread

package day26.tcp.test;
/**
 * 客户端接收线程
 */

import java.io.BufferedReader;
import java.io.IOException;

public class ReThread implements Runnable {    //实现Runnable接口
	//成员变量定义私有
    private BufferedReader br;

    //带参构造,传入输入流对象
    public ReThread(BufferedReader br) {
        this.br = br;
    }


	//重写run()方法
    @Override
    public void run() {
        try {
            String _line = null;    //初始化_line
            while ((_line = br.readLine()) != null) {
                System.out.println(_line);    //打印此客户机收到的信息

            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

6.启动服务器程序
在这里插入图片描述
7.启动客户端程序,并发送消息

在这里插入图片描述
8.由于自己只有一台PC,目前暂时无法完全模仿多客户机同时开启的情况,不过在一台主机上同时开启多个客户端程序,但由于Socket中信息相同,故达不到预期效果。希望自己可以找到解决方案,若解决,就来更新本博文。

猜你喜欢

转载自blog.csdn.net/weixin_42437164/article/details/89283337