IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程--聊天室 -> 正文阅读

[网络协议]网络编程--聊天室

网络编程-聊天室

网络编程是指编写运行在多个设备的计算机程序,这些设备通过网络连接起来

主要使用:
java.net包:JavaSE的API包含有类和接口,它们提供低层次的通信细节。

java.net包中提供了两种常见的网络协议的支持:

TCP:TCP是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称TCP/IP。

UDP:UDP是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据报。

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

因此:
1.服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
2.服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
3.服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
4.Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
5.在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
6.连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

/**
 * 服务端
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class GreetingServer {
    public static void main(String[] args) throws IOException {
        //1.创建服务器Server对象和系统要指定的端口号。
        ServerSocket Server=new ServerSocket(8888);
        //2.使用成员方法accept(),获取到请求的客户端。
        Socket so = Server.accept();
        //3.使用成员方法对象getInputStream获取网络字节输入流对象InputStream
        InputStream input = so.getInputStream();
        //4.使用InputStream对象中方法read()方法读取客户端数据。
        byte[] bytes=new byte[1024];
        int len=input.read(bytes);
        System.out.println(new String(bytes,0,len));
        //5.使用成员方法对象getOutputStream()获取网络字节输出流对象OutputStream
        OutputStream output = so.getOutputStream();
        //6.使用OutputStream对象中方法write()方法向客户端回送数据。
        output.write("谢谢收到".getBytes());
        //7.释放资源(Socket,ServerSocket)
        so.close();
        Server.close();
    }

}


/**
 * 客户端
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.io.IOException;
import java.net.Socket;

public class GreetingClient {
    public static void main(String[] args) throws IOException {
        //创建一个客户端对象Socket
        Socket client=new Socket("127.0.0.1",8888);
        //使用成员方法对象getOutputStream获取网络字节输出流对象OutputStream
        OutputStream os = client.getOutputStream();
        os.write("你好服务器".getBytes());
        //使用成员方法对象getInputStream获取网络字节输入流对象InputStream
        InputStream input = client.getInputStream();
        //使用InputStream对象中方法read()方法读取数据。
        byte[] bytes=new byte[1024];
        int len=input.read(bytes);
        System.out.println(new String(bytes,0,len));
        //6.释放资源(Socket)
        client.close();
    }
}


聊天室功能

聊天室需要一个服务器支持,多个客户端连接服务端,服务端的作用就是接受不同的客户端数据,并转发到其他客户端。

客户端可发发送数据给服务器端,同时客户端也需要接收服务器端返回的数据。客户端的发送数据和接收数据是两个独立的通道,互不影响。
客户端的输出与输入要独立,可以使用多线程来实现。

服务端要为每一个客户端建立一个通道,服务端也使用多线程来实现。

服务端需要创建一个通道的列表,统一管理客户端的通道,为了实现自己发的消息,其他人都可以看到。

在客户端程序里为每一个客户端设置一个名称,约定以@name#开头的格式为私聊,就可以实现私聊的功能。

当程序中发生异常时,线程就停止执行。

总体:

每个客户端在连接到服务器端时,要通过控制台输入自己的名称,然后开始发送消息到服务端,服务端在接收到客户端的连接时,首先输出谁进入了聊天室,然后把客户端发来的消息转发给其他客户端,实现群聊的功能,如果客户端按照约定以@name#开头的格式输入消息,服务端需要解析到客户端要私聊的对象,把消息单独发送给要私聊的客户端。

1.处理IO异常

import java.io.Closeable;

/**
 * 释放资源,由于代码里会处理很多 IO 异常,当程序中发生异常时,线程就停止执行,并且关闭掉对应的资源
 */

public class Util {
    public static void closeAll(Closeable... io) {
        for (Closeable temp : io) {
            try {
                if (null != temp) {
                    temp.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2.服务器端多线程,维护一个客户端的通道列表,服务端实现既能接受客户端的数据,又能转发给对应的客户端

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * 实现服务器端的多线程,维护一个客户端的通道列表,服务器端既能接收客户端的数据,又能把数据转发给对应的客户端
 */

public class ChatChannel implements Runnable{
    public static List<ChatChannel> all = new ArrayList<ChatChannel>();// 通道列表

    private DataInputStream dis; // 输入流
    private DataOutputStream dos;// 输出流
    private String name;// 客户端名称
    private boolean isRunning = true;

    public ChatChannel(Socket client) {
        try {
            dis = new DataInputStream(client.getInputStream());
            dos = new DataOutputStream(client.getOutputStream());
            this.name = dis.readUTF();
            System.out.println(this.name + "进入了聊天室");
            this.send(this.name + ",您好!欢迎您进入聊天室");
            sendOthers(this.name + "进入了聊天室", true); // 系统消息
        } catch (IOException e) {
            e.printStackTrace();
            Util.closeAll(dis, dos);
            isRunning = false;
        }
    }

    /**
     * 读取数据
     */
    private String receive() {
        String msg = "";
        try {
            msg = dis.readUTF();
        } catch (IOException e) {
            e.printStackTrace();
            Util.closeAll(dis);
            isRunning = false;
            all.remove(this); // 移除自身
        }
        return msg;
    }

    /**
     * 发送数据
     */
    private void send(String msg) {
        if (msg != null && !"".equals(msg)) {
            try {
                dos.writeUTF(msg);
                dos.flush();
            } catch (IOException e) {
                e.printStackTrace();
                Util.closeAll(dos);
                isRunning = false;
                all.remove(this); // 移除自身
            }
        }
    }

    /**
     * @param msg    消息内容
     * @param sysMsg 是否是系统消息
     */
    private void sendOthers(String msg, boolean sysMsg) {
        // 加入私聊的判断,约定@name#格式为私聊
        if (msg.startsWith("@") && msg.indexOf("#") > -1) { // 私聊
            // 获取name
            String name = msg.substring(1, msg.indexOf("#"));
            String content = msg.substring(msg.indexOf("#") + 1);
            for (ChatChannel other : all) {
                if (name.equals(other.name)) {
                    other.send(this.name + "悄悄地对您说:" + content);
                }
            }
        } else {
            for (ChatChannel other : all) {
                if (other == this) {
                    continue;
                }
                if (sysMsg) {
                    other.send("系统信息:" + msg);
                } else {
                    // 发送其他客户端
                    other.send(this.name + "对所有人说:" + msg);
                }
            }
        }
    }

    @Override
    public void run() {
        while (isRunning) {
            sendOthers(receive(), false); // 用户消息
        }
    }
}

3.创建服务端类Server,使用多线程和通道容器

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 创建服务端Server类,使用多线程和通道容器
 */

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        while (true) {
            Socket client = server.accept();
            ChatChannel channel = new ChatChannel(client);
            ChatChannel.all.add(channel);// 统一管理客户端的通道
            new Thread(channel).start(); // 启动一条通道
        }
    }
}

4.客户端发送消息线程类Send,设定自己的名字,并发送给服务端

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

/**
 * 客户端的消息发送线程类 Send,每个客户端要设定自己的名字,同时接收控制台输入的数据并发送给服务端。
 */

public class Send implements Runnable{
    // 控制台输入
    private BufferedReader console;
    // 输出流
    private DataOutputStream dos;
    // 客户端名称
    private String name;
    // 控制线程
    private boolean isRunning = true;

    public Send(Socket client, String name) {
        try {
            console = new BufferedReader(new InputStreamReader(System.in));
            dos = new DataOutputStream(client.getOutputStream());
            this.name = name;
            send(this.name); // 把自己的名字发给服务端
        } catch (IOException e) {
            e.printStackTrace();
            isRunning = false;
            Util.closeAll(dos, console);
        }
    }

    /**
     * 从控制台接收数据并发送数据
     */
    public void send(String msg) {
        try {
            if (msg != null && !"".equals(msg)) {
                dos.writeUTF(msg);
                dos.flush(); // 强制刷新
            }
        } catch (IOException e) {
            e.printStackTrace();
            isRunning = false;
            Util.closeAll(dos, console);
        }
    }

    // 从控制台接收数据
    private String getMsgFromConsole() {
        try {
            return console.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    public void run() {
        while (isRunning) {
            send(getMsgFromConsole());
        }
    }
}

5.客户端接收消息Receive,用于独立接受服务端返回的数据

mport java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 客户端的消息接收线程类 Receive,用于独立接收服务端返回的数据。
 */

public class Receive implements Runnable{
    // 输入流
    private DataInputStream dis;
    // 线程标识
    private boolean isRunning = true;

    public Receive(Socket client) {
        try {
            dis = new DataInputStream(client.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
            isRunning = false;
            Util.closeAll(dis);
        }
    }

    /**
     * 接收数据
     */
    public String receive() {
        String msg = "";
        try {
            msg = dis.readUTF();
        } catch (IOException e) {
            e.printStackTrace();
            isRunning = false;
            Util.closeAll(dis);
        }
        return msg;
    }

    @Override
    public void run() {
        while (isRunning) {
            System.out.println(receive());
        }
    }
}

6.创建客户端类Client,发送和接收数据分布使用独立的多线程

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

/**
 * 创建客户端类 Client,发送数据和接收数据分布使用独立的多线程处理。
 */

public class Client {
    public static void main(String[] args) throws IOException {
        System.out.println("请输入您的名称:");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String name = br.readLine();
        if ("".equals(name)) {
            return;
        }
        Socket client = new Socket("localhost", 8888);
        new Thread(new Send(client, name)).start(); // 发送一条通道
        new Thread(new Receive(client)).start(); // 接收一条通道
    }
}

最后,先启动Server,然后可以启动多个客户端Client
在这里插入图片描述
客户端启动后,创建名字:
在这里插入图片描述
在这里插入图片描述
客户端公开发言
在这里插入图片描述
客户端私聊
在这里插入图片描述
在这里插入图片描述

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:59:55  更:2022-02-28 16:01:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:19:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码