Java实现Socket聊天室

一、网络编程是什么?

在网络通信协议下,不同计算机上运行的程序,进行数据传输。

  • 应用场景:即时通讯、网游对战、金融证券、国际贸易、邮件、等等。

不管是什么场景,都是计算机与计算机之间通过网络进行数据传输。

  • Java中可以使用java.net包下的技术轻松开发出常见的网络应用程序。

二、常见的软件架构?

  1. 常见的软件架构有哪些?

    CS/BS。 即Client/Server 和 Browser/Server模式

  2. 通信的软件架构CS/BS的各有什么优缺点和区别?

    CS:客户端服务端模式都需要开发客户端

    BS:浏览器服务器模式不需要开发客户端

    CS:适合定制专业化的办公类软件:IDEA、网游

    BS:适合移动互联网应用,可以在任何地方随时访问系统

三、网络编程三要素?

  • IP:设备在网络中的地址,是唯一的标识
  • 端口号:应用程序在设备中唯一标识
  • 协议:数据在网络中传输的规则,常见的协议游UDP、TCP、HTTP、HTTPS、FTP

四、Socket编程(Java)

Socket(套接字)使用TCP提供了两台计算机之间的通信机制。客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立的时候,服务器会创建一个Socket对象。客户端和服务器可以通过对Socket对象写入和读取来进行通信。

java.net.Socket类代表一个套接字,并且java.net.ServerSocket类为服务器程序提供了一种监听 客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用Socket建立TCP连接出现:

  • 服务器实例化一个ServerSocket对象,表示通过服务器端口通信。(ServerSocket本质就是监听端口等待Socket对象连接
  • 服务器调用ServerSocket类的accept()方法,该方法将一直等待,直到一个客户端连接到服务器上给定的端口。
  • 服务器 ServerSocket监听等待连接的过程中,客户端 创建一个Socket对象,并指定该Socket要连接到的服务器的名称端口
  • Socket类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
  1. 连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
  2. TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送。

五、ServerSocket类的方法

服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。

ServerSocket 类有四个构造方法:

序号 方法描述
1 public ServerSocket(int port) throws IOException 创建监听特定端口的服务器套接字。
2 public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket(int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket() throws IOException 创建非绑定服务器套接字。

创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些 ServerSocket 类的常用方法:

序号 方法描述
1 public int getLocalPort() 返回此套接字在其上侦听的端口。
2 public Socket accept() throws IOException 侦听并接受到此套接字的连接。
3 public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind(SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

六、Socket 类的方法

java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。

Socket 类有五个构造方法.

序号 方法描述
1 public Socket(String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字

当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。

序号 方法描述
1 public void connect(SocketAddress host, int timeout) throws IOException 将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress() 返回套接字连接的地址。
3 public int getPort() 返回此套接字连接到的远程端口。
4 public int getLocalPort() 返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream() throws IOException 返回此套接字的输入流。
7 public OutputStream getOutputStream() throws IOException 返回此套接字的输出流。
8 public void close() throws IOException 关闭此套接字。

例子(一):Socket 实例

1. 服务端

如下的 MySocketServer 是一个服务端程序,该程序通过 socket 连接到服务器并发送一个请求,然后等待一个响应。

java 复制代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class MySocketServer extends Thread{
    private ServerSocket serverSocket;
    
    public MySocketServer(int port)throws IOException {
        //创建ServerSocket监听端口port
        serverSocket = new ServerSocket(port);
        //设置等待时间:10000毫秒没有监听到Socket连接该端口就报错
        serverSocket.setSoTimeout(10000);
    }
    
    public void run(){
        try{
            //ServerSocket等待连接,链接成功就创建一个与客户端对等通信的socket
            Socket server = serverSocket.accept();
            System.out.println("客户"+server.getLocalAddress()+"连接成功");
            //获取Socket的输入流输出流
            DataInputStream inputStream = new DataInputStream(server.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(server.getOutputStream());
            while(true){
                //从流中获取信息
                String msg = inputStream.readUTF();
                //当客户端传递的信息为ends时,结束。
                if(msg.equals("ends")) break;
                //服务端显示流中的信息
                System.out.println(msg);
            }
            server.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        try{
            System.out.println("请输入服务端绑定端口:");
            //运行线程让服务端异步运行这样可以让主程序继续干自己的事
            Thread t = new MySocketServer(scanner.nextInt());
            t.run();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

2. 客户端

java 复制代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class MySocketClient{
    public static void main(String[] args){
        try{
            Scanner keyboard = new Scanner(System.in);
            System.out.println("请输入连接主机的IP地址:");
            String host = keyboard.nextLine();
            System.out.println("输入主机"+host+" 的端口号:");
            int port = keyboard.nextInt();
            //创建Socket并尝试连接 IP=host && 端口=port的服务端
            Socket client = new Socket(host,port);
            InputStream in = client.getInputStream();
            OutputStream out = client.getOutputStream();
            DataInputStream inputStream = new DataInputStream(in);
            DataOutputStream outputStream = new DataOutputStream(out);
            while(true){
                String msg = keyboard.nextLine();
                outputStream.writeUTF(msg);
                //当输入了ends时,客户端关闭socket,服务端也关闭socket,二者结束通信。
                if(msg.equals("ends"))break;
            }
            client.close();
        }catch(Exception ex){
            ex.printStackTrace();
        };
    }
}

3. 测试结果

例子(二):聊天室

1. 服务端

  • 服务端使用while循环的添加Socket用户
  • 每个用户有自己独立的线程(异步性:使各个用户可以同时输出输入的同时服务端能够继续监听端口)
java 复制代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class ChatRoomServer{
    private static ServerSocket serverSocket;
    private static ArrayList<Socket> clientList = new ArrayList<Socket>();

    public static void main(String[] args){
        try{
            serverSocket = new ServerSocket(8888);
            while(true){
                Socket client = serverSocket.accept();
                clientList.add(client);
                //TODO:开启客户端线程,进行异步聊天
                ClientThread ct = new ClientThread(client,clientList);
                ct.start();
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(serverSocket != null)serverSocket.close();
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
}

class ClientThread extends Thread{
    private Socket client = null;
    private ArrayList<Socket> clientList;
    public ClientThread(Socket s,ArrayList<Socket>ss){
        client = s;
        clientList = ss;
    }
    public void run(){
        DataInputStream input = null;
        DataOutputStream output =  null;
        try{
            input = new DataInputStream(client.getInputStream());
            String rec = null;
            String send = null;
            while(true){
                if(!client.isClosed()){
                    rec = input.readUTF();
                    System.out.println("服务端接收到数据:"+rec);
                    clientList.trimToSize();
                    String[] param = rec.split("&");
                    //将输入进行一些封装
                    if("$start$".equals(param[1])){
                        send = param[0] + "进入聊天室";
                    }else{
                        send = param[0] + "说:  " + param[1];
                    }
                    //将非取消信号的数据发送出去
                    if(!("$ends$".equals(param[1]))){
                        for(Socket socket : clientList){
                            if(!socket.isClosed()){
                                output = new DataOutputStream(socket.getOutputStream());
                                output.writeUTF(send);
                            }
                        }
                    }else{
                        for(Socket socket : clientList){
                            if(socket!= client && !socket.isClosed()){
                                output = new DataOutputStream(socket.getOutputStream());
                                output.writeUTF(param[0]+"已退出聊天室");
                            }
                        }
                        output = new DataOutputStream(client.getOutputStream());
                        output.writeUTF("$ends$");
                        client.close();
                        input.close();
                        output.close();
                    }
                }
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

2. 客户端

java 复制代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class ChatRoomClient {
    public static final String ip = "127.0.0.1";
    public static final int port = 8888;
    public Socket socket = null;
    public DataInputStream input = null;
    public DataOutputStream output = null;
    public Scanner keyboard = new Scanner(System.in);
    public String send;
    public String name;
    public void start(){
        try{
            System.out.println(" ################ 欢迎进入Socket聊天室 ################ ");
            System.out.println("输入您在聊天室的昵称: ");
            name = keyboard.nextLine();
            socket = new Socket(ip,port);
            input = new DataInputStream(socket.getInputStream());
            output = new DataOutputStream(socket.getOutputStream());
            send = name + "&$start$";
            System.out.println(" ################ 进入聊天室成功 ################ ");
            System.out.println("如需退出聊天室,输入'$ends$'即可....");
            output.writeUTF(send);
            //TODO: 编写聊天的线程
            MsgThread mt = new MsgThread(output,name,input);
            mt.start();

            while(true){
                String rec = input.readUTF();
                if("$ends$".equals(rec)){
                    System.out.println(" ################ 退出聊天室成功 ################ ");
                    input.close();output.close();socket.close();
                    System.exit(0);
                }else{
                    System.out.println(rec);
                }
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(socket!= null){
                    socket.close();input.close();output.close();
                }
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        ChatRoomClient client = new ChatRoomClient();
        client.start();
    }
}
class MsgThread extends Thread{
    private DataInputStream input;
    private DataOutputStream output;
    private Scanner keyboard = new Scanner(System.in);
    public static String name;
    public MsgThread(DataOutputStream o,String n,DataInputStream i){
        output = o;input = i;name = n;
    }
    public void run(){
        ChatRoomClient client = new ChatRoomClient();
        try{
            while(true){
                String send = name+"&" + keyboard.nextLine();
                output.writeUTF(send);
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            System.out.println("sfef");
        }
    }
}

3. 测试结果

相关推荐
Am心若依旧4094 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
明月看潮生7 分钟前
青少年编程与数学 02-004 Go语言Web编程 20课题、单元测试
开发语言·青少年编程·单元测试·编程与数学·goweb
Yan.love13 分钟前
开发场景中Java 集合的最佳选择
java·数据结构·链表
椰椰椰耶16 分钟前
【文档搜索引擎】搜索模块的完整实现
java·搜索引擎
大G哥16 分钟前
java提高正则处理效率
java·开发语言
VBA633726 分钟前
VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL
开发语言
轩辰~28 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
小_太_阳38 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
向宇it38 分钟前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring