Java Socket实现聊天室

本文通过Socket和线程技术实现多个客户端与服务器及客户端间的通信,详细介绍了服务器端和客户端的代码逻辑,并分析了如何利用循环创建多个线程以支持多客户端通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考本文时最好已了解Socket基础知识Socket基础知识与简单案例请参考http://blog.youkuaiyun.com/qq_33865313/article/details/79300531

第一例:Socket实现多个客户端向服务器端通信

  实现多个客户端向服务器端的通信首先需要启动一个服务器端用来监听客户端的连接,然后会将连接放入线程中,这时客户端想服务器端发送信息就可以接收到了。为了简化代码,提高可读性,接下来的例子我将不再进行资源的关闭回收。

  每当有客户端向服务器请求连接时,服务器会获取客户端的Socket,然后将这个Socket放入一个新的线程中,在线程中监听客户端发送的信息,这时客户端向服务器端发送信息就可以接收到了。

   SocketService(服务器端)

package socket.service;

import java.net.ServerSocket;
import java.net.Socket;

public class SocketService {
    public static void main(String args[])throws Exception { 
        ServerSocket serverSocket = new ServerSocket(5208);
        System.out.println("服务器启动成功");
        while (true) {  
            Socket socket= serverSocket.accept();
            System.out.println("上线通知: " + socket.getInetAddress() + ":" +socket.getPort());
            new Thread(new ServerThread(socket)).start();
        }
    }  
}

   ServerThread(服务器端线程

package socket.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerThread implements Runnable {
    
    public Socket socket;
    
    public ServerThread (Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String str = br.readLine();
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

   SocketClient1(第一个客户端)

package socket.service;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketClient1 {
    public static void main(String args[])throws Exception{  
        Socket socket = new Socket("192.168.10.2", 5208);  
        System.out.println("小一连接成功");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(socket.getOutputStream());
        while(true){
            pw.println("小一说:"+br.readLine());
            pw.flush();
        }
    }  
}

   SocketClient2(第二个客户端)

package socket.service;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketClient2 {
    public static void main(String args[])throws Exception{  
        Socket socket = new Socket("192.168.10.2", 5208);  
        System.out.println("小二连接成功");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(socket.getOutputStream());
        while(true){
            pw.println("小二说:"+br.readLine());
            pw.flush();
        }
    }  
}

   这个例子是多个客户端向服务器发送信息,接下来我们分析一下上面的代码逻辑。

   首先我们先启动服务器,服务器启动后,进入无限循环中的accept方法,这里的无限循环是为了不断的获取n个客户端连接。在这里会阻塞等待一个socket连接,服务器的代码走到这就停了。

   然后我们启动第一个客户端。客户端启动后,服务器会往下走,服务器会新启动一个自己写的线程(ServerThread),socket连接作为构造函数的参数传入这个自己写的线程中,然后在线程中通过readline方法,阻塞等待客户端socket中输入流的出现,而同时,客户端也会被循环中的readline方法阻塞,等待控制台中的输入流的出现。

   这就是一个客户端的连接,这时整个项目就进入等待中,服务器被accept阻塞等待下一个客户端连接,服务器线程被readline阻塞等待socket中输入流的出现,客户端被readline阻塞等待控制台中输入流的出现。

   仔细思考后你会发现,这里的服务器端的功能其实只是实例化出一个个服务器线程,而真正进行通信的是服务器线程和客户端,所以这里的服务器线程其实就是上一篇博客中的服务器端,而这里的服务器端只是服务器的工厂类,用来生产出一个个服务器的。

   之后同样启动第二个客户端,逻辑同第一个客户端连接。然后我们在第一个客户端的控制台中输入一串字符,这时第一个客户端中的readline就会立刻读取到这段输入流,然后客户端通过PrintWriter将该输入流通过socket的输出流推送到服务器线程的socket中,然后进入下一个循环的等待。

   而服务器线程中的readline就会立刻读取到socket的输入流,然后打印到服务器的控制台,之后又会进入下一个循环的等待。在这里其实也可以进行服务器线程向客户端推送消息的操作,但为了简化代码逻辑,这里没有进行更多的操作,在下一个例子中有详细的解释。

   然后你在第二个客户端的控制台中输入一串字符,也同样能在服务器端进行打印输出。其实也就是两个客户端可以同时和服务器端的两个线程进行通信,一个服务器端线程阻塞时,不会影响到另一个,所以在服务器端是能够同时看到两个客户端的通信内容的。


   这里有几个个线程的注意事项。

   第一是自己写线程时一般有两种方式,继承Thread类和实现Runnable接口,一般情况下,如果我们只想重写run方法,都是使用实现Runnable接口的方式,Thread类的底层代码其实也是实现了Runnable接口。

   第二是启动线程一般也有两种方式,一个是调用run方法,一个是调用start方法,调用run方法其实并没有启动一个新的线程,而是只有一个线程执行了一下run方法。但start是新建了一个线程,所以线程的启动都是使用start方法

   但实现runnable接口的线程没有start方法,所以需要将实现runnable接口的类封装到Thread类中,然后调用Threadstart方法,如:new Thread(new ServerThread(socket)).start();


   理解了逻辑后不难看出,在这个例子中体现的最重要的一点是利用while(true)循环来实例化多个线程,然后使用一个新的线程来单独跑一个服务器,使得多个客户端能够和多个不同的服务器线程通信,形成了多个客户端同时和一个服务器端通信的效果,如果去掉了循环和线程,那么就只能实例化一个服务器,也就无法让多个socket客户端连接,其实也就是上一篇博客中的第一个例子。

 

第二例:Socket实现客户端与客户端通信

  在上一个例子中,是通过实例化多个服务器线程来和客户端连接,从而实现一个服务器连接多客户端的功能。那么如果我们在服务器线程中获取了客户端推送过来的信息后,再将这个信息推送到所有客户端,是不是就实现了客户端与客户端的通信呢?

   逻辑是没有错的,但是有两个难点,第一,如何将信息推送到所有客户端呢?所以这就得在服务器端用一个列表来存储所有客户端。第二,上一篇博客的学习中,我们知道获取控制台的信息需要用到readline这个阻塞函数,获取客户端推送过来的信息也需要用到readline这个阻塞函数,如果在一个方法中同时需要用到这两个功能的话,那么就每接收一次消息就需要发送一次消息,不然程序就会阻塞在一个地方。所以每个客户端都应实例化两个线程,一个用来等待控制台输入,一个用来等待服务器推送的消息。

    SocketService(服务器端)

package socket.chat;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class SocketService {
    public static List<Socket> socketList = new ArrayList<Socket>();
    
    public static void main(String args[])throws Exception { 
        ServerSocket serverSocket = new ServerSocket(5208);
        System.out.println("聊天室开启");
        while (true) {  
            Socket socket= serverSocket.accept();     //从连接请求队列中取出一个连接           
            System.out.println("上线通知: 用户" + socket.getPort()+"上线啦!");  
            socketList.add(socket);
            new Thread(new ServerThread(socket)).start();
        }
    }  
}

   ServerThread(服务器端线程)

package socket.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ServerThread implements Runnable {
    
    public Socket socket;
    
    public ServerThread (Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String str = br.readLine();
                for (Socket item : SocketService.socketList) {
                    PrintWriter pw = new PrintWriter(item.getOutputStream());
                    pw.println("用户"+socket.getPort()+"说:"+str);
                    pw.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

    SocketClient1(第一个客户端)

package socket.chat;

import java.net.Socket;

public class SocketClient1 {
    public static void main(String args[])throws Exception{  
        Socket socket = new Socket("192.168.10.2", 5208);
        System.out.println("恭喜你连接成功!");
        new Thread(new SocketThread1(socket)).start();
        new Thread(new SocketThread2(socket)).start();
    }  
}

    SocketClient2(第二个客户端)

package socket.chat;

import java.net.Socket;

public class SocketClient2 {
    public static void main(String args[])throws Exception{  
        Socket socket = new Socket("192.168.10.2", 5208);
        System.out.println("恭喜你连接成功!");
        new Thread(new SocketThread1(socket)).start();
        new Thread(new SocketThread2(socket)).start();
    }
}

    SocketThread1(监听控制台客户端线程)

package socket.chat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketThread1 implements Runnable {
    
    public Socket socket;
    
    public SocketThread1(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            while(true){
                String str = br.readLine();
                pw.println(str);
                pw.flush();
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        
    }

}

    SocketThread2(监听服务器客户端线程)

package socket.chat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

public class SocketThread2 implements Runnable {
    
    public Socket socket;
    
    public SocketThread2(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while(true){
                String str = br.readLine();
                System.out.println(str);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        
    }

}

   这个例子是客户端与客户端之间的通信,接下来我们分析一下上面的代码逻辑。

   首先在服务器端定义一个静态的List用来存放所有的客户端socket。我们首先会启动服务器,服务器启动后,进入无限循环中的accept方法,这里的无限循环是为了不断的获取n个客户端连接。在这里会阻塞等待一个socket连接,服务器的代码走到这就停了。

   然后我们启动第一个客户端。客户端启动后,服务器会往下走,首先先将socket连接加入静态列表中,以便服务器线程调用。然后服务器会新启动一个自己写的线程(ServerThread),将socket连接作为构造函数的参数传入这个自己写的线程中,然后在线程中通过readline方法,阻塞等待客户端socket中输入流的出现

   这时,客户端也会socket作为构造函数的参数来新建两个客户端线程,第一个线程的作用是等待控制台中输入流的出现被循环中的readline方法阻塞,等待控制台中的输入流的出现第二个线程的作用是等待服务器端推送过来的socket中输入流的出现

   这就是一个客户端的连接,这时整个项目就进入等待中,服务器被accept阻塞等待下一个客户端连接,服务器线程被readline阻塞等待客户端传过来的socket中输入流的出现,客户端的第一个线程被readline阻塞等待控制台中输入流的出现,客户端的第二个线程被readline阻塞等待服务器传过来的socket中输入流的出现。

   之后同样启动第二个客户端,逻辑同第一个客户端连接。然后我们在第一个客户端的控制台中输入一串字符,这时第一个客户端的第一个线程中的readline就会立刻读取到这段输入流,然后该线程通过PrintWriter将该输入流通过socket的输出流推送到服务器线程的socket中,然后进入下一个循环的等待。

   而服务器线程中的readline就会立刻读取到客户端传过来的socket的输入流,然后通过服务器端的静态列表获取所有的socket连接,再通过每个socket的输出流将这串字符推送到所有客户端,之后又会进入下一个循环的等待。

   这是所有客户端的第二个线程中的readline就会立刻读取到服务器端传过来的输入流,然后将这串字符串打印输出到控制台,然后进入下一个循环的等待。

   之后你在第二个客户端的控制台中输入一串字符,也是同样的逻辑。上一个例子是多个客户端向服务器的通信,这个例子加了一步服务器接收到通信后再次向所有客户端发送这次通信内容。上一个例子也提到了的服务器可以向客户端通信,这个例子通过静态List和客户端分两个线程的方式解决了两个难点,自然而然就实现了聊天室功能。

   总结一下,本文实现聊天室主要依靠的就是两个技术点:Socket + 线程

 如果你还有其他的看法或者想法,欢迎在评论区留下你的意见,大家一起讨论,一起进步,做一只有梦想的网虫!

java聊天室程序源码 2 需求分析 2.1 业务需求 1. 与聊天室成员一起聊天。 2. 可以与聊天室成员私聊。 3. 可以改变聊天内容风格。 4. 用户注册(含头像)、登录。 5. 服务器监控聊天内容。 6. 服务器过滤非法内容。 7. 服务器发送通知。 8. 服务器踢人。 9. 保存服务器日志。 10.保存用户聊天信息。 2.2 系统功能模块 2.2.1 服务器端 1.处理用户注册 2.处理用户登录 3.处理用户发送信息 4.处理用户得到信息 5.处理用户退出 2.2.2 客户端 1.用户注册界面及结果 2.用户登录界面及结果 3.用户发送信息界面及结果 4.用户得到信息界面及结果 5.用户退出界面及结果 2.3 性能需求 运行环境:Windows 9x、2000、xp、2003,Linux 必要环境:JDK 1.5 以上 硬件环境:CPU 400MHz以上,内存64MB以上 3.1.2 客户端结构 ChatClient.java 为客户端程序启动类,负责客户端的启动和退出。 Login.java 为客户端程序登录界面,负责用户帐号信息的验证与反馈。 Register.java 为客户端程序注册界面,负责用户帐号信息的注册验证与反馈。 ChatRoom.java 为客户端程序聊天室主界面,负责接收、发送聊天内容与服务器端的Connection.java 亲密合作。 Windowclose 为ChatRoom.java的内部类,负责监听聊天室界面的操作,当用户退出时返回给服务器信息。 Clock.java 为客户端程序的一个小程序,实现的一个石英钟功能。 3. 2 系统实现原理 当用户聊天时,将当前用户名、聊天对象、聊天内容、聊天语气和是否私聊进行封装,然后与服务器建立Socket连接,再用对象输出流包装Socket的输出流将聊天信息对象发送给服务器端 当用户发送聊天信息时,服务端将会收到客户端用Socket传输过来的聊天信息对象,然后将其强制转换为Chat对象,并将本次用户的聊天信息对象添加到聊天对象集Message中,以供所有聊天用户访问。 接收用户的聊天信息是由多线程技术实现的,因为客户端必须时时关注更新服务器上是否有最新消息,在本程序中设定的是3秒刷新服务器一次,如果间隔时间太短将会增加客户端与服务器端的通信负担,而间隔时间长就会让人感觉没有时效性,所以经过权衡后认为3秒最佳,因为每个用户都不可能在3秒内连续发送信息。 当每次用户接收到聊天信息后将会开始分析聊天信息然后将适合自己的信息人性化地显示在聊天信息界面上。 4.1.1 问题陈述 1.接受用户注册信息并保存在一个基于文件的对象型数据库。 2.能够允许注册过的用户登陆聊天界面并可以聊天。 3.能够接受私聊信息并发送给特定的用户。 4.服务器运行在自定义的端口上#1001。 5.服务器监控用户列表和用户聊天信息(私聊除外)。 6.服务器踢人,发送通知。 7.服务器保存日志。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值