网络编程的知识点

一、网络编程概述

1.1 网络编程简介

其实,所谓的网络编程,就是编写程序,实现让同一个网络中的机器实现数据的传递,实现通信。

Java是 Internet 的语言,它从语言级上提供了对网络应用程序的支持。

Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制,并且Java 实现了一个跨平台的网络库,因此程序员面对的是一个统一的网络编程环境,很容易开发常见的网络应用程序。

1.2 网络编程需要具备的条件

如果要实现两台机器之间的通信,必须满足以下几点要求:

1、需要知道对方的 IP 地址。

2 、需要知道对方的哪一个端口号来做数据的接受。

3 、通信的双方,需要遵循相同的通信协议。

1.3 IP地址和端口号的简介

1.3.1 IP地址

IP是 Internet Protocol (网络互连协议),在计算机中,使用IP地址来描述一个上网终端的唯一的地址编号。分为 IPv4 和 IPv6。

IPv4 : 使用4个字节来描述一个IP地址,由四部分组成,每一部分一个字节。

IPv6 : 使用6个字节来描述一个IP地址,由六部分组成,每一部分一个字节。

IP地址的分类:

A类: <1.0.0.1> ~ <126.255.255.254> :保留给政府机构

B类: <128.0.0.1> ~ <191.255.255.254> :分配给大中型企业

C类: <192.0.0.1> ~ <223.255.255.254> :分配给任何有需要的个人

D类: <224.0.0.1> ~ <239.255.255.254> :用于组播

E类: <240.0.0.1> ~ <255.255.255.254> :用于实验

1.3.2 端口号的简介

端口是设备与外界进行通信的数据的出口。

端口号的范围: [0,65535]。

常见的端口占用:

3306: MySQL

1521 :Oracle

8080 :Tomcat

1.4 通信协议

常用的通信协议有:TCP 、UDP 、HTTP;而OSI模型过于理想化,未能在因特网上进行广泛推广,下面,我们着重讲解一下TCP协议。

1.4.1 TCP协议

是一个传输层的通信协议,是一个安全的,面向连接的通信协议。所谓的安全,指的是在数据传递的过程中,不会出现数据丢失的问题。特点:安全的、面向连接。

TCP/IP 因其传输控制协议(TCP)和网络互联协议(IP)而得名。实际上是一组协议,其包含多个具有不同功能且互为关联的协议。

TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即网络接口层、IP层、传输层和应用层。下图表示了TCP/IP的分层结构和与OSI参考模型的对应关系:

1.4.2 TCP的三次握手

使用TCP实现数据在两个计算机之间进行传递,需要先在两个计算机之间建立一个安全的连接,然后再通过这个连接,进行数据的传递。因此,TCP是一个安全的通信协议。

在使用TCP进行数据传递的时候,存在两个角色,客户端和服务端。

在TCP中,连接的建立过程会经历如下几个步骤:

  1. 第一次握手:建立连接时,由客户端向服务端发送一个 syn 包, syn包中是同步序列编号, 并进入SYN_SENT 状态,并等待服务器确认。

  2. 第二次握手:服务器收到了 syn 包,服务器必须确认客户端的syn,同时自己也发送一个syn包给客户端,即 SYN_ACK 包,并进入到 SYN_RECV 状态。

  3. 第三次握手:客户端收到了服务端 SYN_ACK 包,并向服务端回复一个 ACK 包,此时客户端和服务端会进 入到TCP连接成功状态。 ESTABLISHED。

1.4.3 TCP与UDP的区别

运输层协议中有两个非常重要的协议:

  1. 传输控制协议TCP(Transmission Control Protocol)

  2. 用户数据报协议UDP(User Datagram Protocol)

TCP是面向连接的运输层协议。即应用进程(或程序)在使用TCP协议之前,必须先建立TCP连接,在传输完毕后,释放已经建立的连接。利用TCP协议进行通信的两个应用进程,一个是服务器进程。另一个是客户进程。

UDP是面向无连接的运输层协议。即应用进程(或程序)在使用UDP协议之前,不必先建立连接。自然,发送数据结束时也没有连接需要释放。因此,减少了开销和发送数据之前的时延。

二、TCP/IP程序编程

2.1 TCP/IP编程简介

2.1.1 C/S架构模型

CS架构由客户端和服务器两个部分组成,通常采用C/S模式进行通信。其中,客户端负责向用户提供界面和交互功能,而服务器则负责存储和处理数据。在这种架构中,客户端和服务器之间通过网络进行通信,客户端向服务器发出请求,服务器响应并返回相应的结果。

目前,CS架构已被广泛应用于各种场景中,如Web应用程序、电子邮件、数据库管理等,并且具有可扩展性、可靠性和安全性等优点。

TCP利用Socket(套接字)接口来实现C/S模型的网络程序开发,其早已被广泛的采用。通常,主动发起通信的应用程序属于客户端。而服务器则是等待通信请求,当服务器收到客户端的请求,执行需要的运算然后向客户端返回结果。

如我们使用QQ软件时,我们电脑上安装的就是一个客户端软件,而腾讯必需运行一个服务器。

2.1.2 Socket套接字的介绍

socket被称为套接字,用于描述IP地址和端口号。主机上一般运行了多个服务软件,同时提供多种服务,每种服务都会打开一个Socket,并绑定到一个端口上,不同的端口对应不同的服务。

Socket和ServerSocket类位于java.net包中,ServerSocket用于服务端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,两个Socket构成一个通信管道。操作这个实例,完成所需要的通信。

套接字的基本操作有七个基本操作:

  1. 连接到远程主机

  2. 绑定到端口

  3. 接收从远程机器来的连接请求

  4. 监听到达的数据

  5. 发送数据

  6. 接收数据

  7. 关闭连接

2.1.3 Socket编程步骤

套接字编程分为服务器编程和客户端编程,其通信模型如图所示:

1)服务端编程步骤如下:

  1. 调用 ServerSocket(int port) 创建一个服务器端套接字,并绑定到指定端口上。

  2. 调用 accept(),监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字。

  3. 调用 Socket类的 getOutputStream 和 getInputStream 获取输出流和输入流,开始网络数据的 发送和接收。

  4. 最后关闭通信套接字。

2)客户端编程步骤如下:

  1. 创建 Socket。根据指定的 IP和port构造 Socket 类对象,并发送连接请求。如服务器端响应,则建立客户端到服务器的通信线路。

  2. 通过Socket获取与服务器的通信流。 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流。

  3. 按照一定的协议对 Socket进行读/写操作。通过输入流读取服务端响应的信息,通过输出流将信息发送给服务端。

  4. 关闭 Socket。断开客户端到服务器的连接,释放线路。

3)流连接

  1. 客户端和服务器端的套接字对象诞生以后,必须进行输入、输出流的连接。

  2. 套接字调用 close()方法 可以关闭双方的套接字连接,只要一方关闭连接,就会导致对方发生 IOException异常。

2.2 三个常用类的API

2.2.1 InetAddress

InetAddress,该类用于描述一台机器的IP和主机名信息;常用的两个子类,分别是Inet4Address、Inet6Address。

InetAddress的实例对象由一个IP地址和可能与之对应的主机名(域名)组成。

复制代码
互联网中的主机地址有两种表现形式:
域名: www.baidu.com
IP地址: 110.242.68.4
域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,
这样才能和主机建立连接。

常用API:

  1. static InetAddress getLocalHost():返回本地主机名和IP地址。

  2. static InetAddress getByName(String host):返回一个确定主机名称的IP地址。

  3. String getHostAddress():返回文本显示中的IP地址字符串。

  4. String getHostName():获取此IP地址的主机名。

描述本机的形式有:

  1. 127.0.0.1

  2. localhost

  3. 具体IP

java 复制代码
public static void main(String[] args) {
    //获取一个描述本机信息的InetAddress对象: 包括主机名称和IP地址
    try {
        InetAddress addr = InetAddress.getLocalHost();
        System.out.println(addr);
        //获取里面的主机名称
        System.out.println("主机名称:"+addr.getHostName());
        //获取里面的IP地址 192.168.1.124
        System.out.println("IP地址:"+addr.getHostAddress());
        //IP的字节序列使用 [-64, -88, 1, 124]
        System.out.println(Arrays.toString(addr.getAddress()));
        //描述本机的形式有: 1. 127.0.0.1  2. localhost  3. 具体IP
        InetAddress addr2 = InetAddress.getByName("127.0.0.1");
        System.out.println(addr2);
        //获取其他域名或者指定IP的信息
        InetAddress inet2 = InetAddress.getByName("www.baidu.com");
        System.out.println(inet2);
        System.out.println(inet2.getHostAddress());
        System.out.println(inet2.getHostName());
        //获取域名对应的所有的主机
        InetAddress[] allByName = InetAddress.getAllByName("www.baidu.com");
        System.out.println(allByName.length);
        for (InetAddress inet : allByName) {
            System.out.println(inet.getHostAddress());
        }
    } catch (UnknownHostException e) {
        e.printStackTrace();
    }        
}
运行结果:
LAPTOP-PKV7GBLS/192.168.1.124
主机名称:LAPTOP-PKV7GBLS
IP地址:192.168.1.124
[-64, -88, 1, 124]
/127.0.0.1
www.baidu.com/110.242.68.3
110.242.68.3
www.baidu.com
2
110.242.68.3
110.242.68.4

2.2.2 Socket

Socket这个类用于描述TCP的客户端。

常用构造器:

  1. Socket(InetAdress address,int port):指定要连接的服务器的地址和端口号。
java 复制代码
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
  1. Socket(String host,int port):指定要连接的服务器的IP和端口号。
java 复制代码
Socket socket = new Socket("127.0.0.1", 9999);

常用方法:

  1. InetAddress getLocalAddress():返回客户端的地址信息。

  2. int getLocalPort():返回客户端自己用于通信的端口号(系统分配的)。

  3. InetAddress getInetAddress():返回远程服务端的地址信息。

  4. int getInetPort():返回远程服务端的端口号。

  5. void close() throws IOException:关闭Socket,释放资源。

  6. InputStream getInputStream() throws Exception:获取字节输入流,用于读取远程服务端发送的数据。

  7. OutputStream getOutputStream() throws IOException:获取字节输出流,用于向远程服务端发送数据。

2.2.3 ServerSocket

ServerSocket这个类用来描述TCP的服务端。

常用构造器:

ServerSocket(int port): 用于指定一个端口号,返回具体实例。

java 复制代码
ServerSocket server = new ServerSocket(9999);

常用方法:

  1. setSoTimeOut(int timeOut): 用于定义一个阻塞的超时时间,单位是毫秒,超过指定时间,抛异常。
java 复制代码
server.setSoTimeout(10000);
  1. accept(): 一个阻塞方法,用于监听客户端的连接,如果监听到,则返回一个Socket对象。
java 复制代码
//如果指定了超时时间,该时间段内没有客户端连接,抛异常 SocketTimeoutException
Socket socket = server.accept();
  1. 其他方法
java 复制代码
System.out.println("服务端IP: "+server.getInetAddress().getHostAddress());
System.out.println("服务端IP: "+server.getInetAddress().getHostName());
System.out.println("详细内容: "+server.getLocalSocketAddress());
System.out.println("正在使用的端口号: "+server.getLocalPort());
System.out.println("服务端是否关闭: "+server.isClosed());
System.out.println("超时时间: "+server.getSoTimeout());
System.out.println("服务器绑定的端口是否是正确的: "+server.isBound());

三、聊天小项目

3.1 第一个小版本

客户端向服务端发送信息,服务端打印消息。

3.1.1 服务端的代码

java 复制代码
package com.se.day12.Exercise;

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

/**
 *              服务端
 */
public class ChatServer01 {
    private ServerSocket server;
    public ChatServer01(int port) {
        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public void start() {
        System.out.println("---等待客户端连接---");
        Socket socket = null;
        try {
            server.setSoTimeout(10000);
            socket = server.accept();
            System.out.println("---一个客户端已经连接---");
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
            String info = br.readLine();
            System.out.println("客户端发送过来的信息:"+info);
        } catch (IOException e) {
            System.out.println("10s内没有客户端进行连接");
        }
    }

    public static void main(String[] args) {
        ChatServer01 chatServer = new ChatServer01(1020);
        chatServer.start();
    }
}

3.1.2 客户端的代码

java 复制代码
package com.se.day12.Exercise;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

/**
 *              客户端
 */
public class ChatClient01 {
    private Socket client;
    public ChatClient01() {
        try {
            client = new Socket("localhost",1020);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public void start() {
        PrintWriter out = null;
        try {
            OutputStream os = client.getOutputStream();
            out = new PrintWriter(new OutputStreamWriter(os,"UTF-8"), true);
            out.println("你好!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            out.close();
        }
    }
    public static void main(String[] args) {
        ChatClient01 chatClient = new ChatClient01();
        chatClient.start();
    }
}

3.2 第二个小版本

客户端循环向服务端发送信息,服务端负责打印。

3.2.1 服务端代码

3.2.2 客户端代码

3.3 第三个小版本

客户端收到服务端转发的消息。

3.3.1 服务端代码

java 复制代码
package com.se.day12.Exercise;

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

/**
 * 3.客户端收到服务端转发的消息。
 *          服务端
 */
public class ChatServer03 {
    //定义一个TCP通信协议的服务端属性
    private ServerSocket server;
    public ChatServer03(int port) {
        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            System.out.println("---服务器启动失败---");
        }
    }

    /**
     * 聊天的主方法
     */
    public void start() {
        try {
            System.out.println("---等待客户端连接---");
            Socket socket = server.accept();
            System.out.println("---一个客户端连接上了---");

            //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据
            InputStream inputStream = socket.getInputStream();
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(inputStream,"utf-8"));

            //通过服务端的Socket对象,获取输出流,将信息返回给客户端
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(
                    new OutputStreamWriter(outputStream,"utf-8"),true);


            //循环打印客户端发送过来的信息
            String info = "";
            while ((info = br.readLine()) != null) {
                //使用输出流,将其返回即可
                pw.println(info);
            }
        } catch (IOException e) {
            System.out.println("---一个客户端离线了---");
        }
    }

    public static void main(String[] args) {
        //创建一个具体的服务器对象
        ChatServer03 chatServer  = new ChatServer03(8888);
        //调用聊天的主方法
        chatServer.start();
    }
}

3.3.2 客户端代码

java 复制代码
package com.se.day12.Exercise;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 3. 客户端收到服务端转发的消息。
 *          客户端
 */

public class ChatClient03 {
    //定义一个TCP通信协议的客户端属性
    private Socket socket;
    public ChatClient03() {
        //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset
        try {
            socket = new Socket("localhost",8888);
        } catch (IOException e) {
            System.out.println("---服务器崩溃中---");
        }
    }
    // 聊天的主方法
    public void start(){
        try {

            //获取一个获取服务端信息的处理器任务
            Runnable task = new GetServerInfoHandler();
            Thread thread = new Thread(task);
            thread.start();

            //获取向服务器发送信息的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            //封装成字符缓存流,可以按行输出
            PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(outputStream,"UTF-8"),
                    true);
            //使用控制台扫描对象类型,不断的扫描控制台上的文字
            Scanner scan = new Scanner(System.in);
            System.out.println("====开始聊天====");
            while(true){
                String info = scan.nextLine();
                //将扫描的信息发送到服务端
                out.println(info);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //创建一个具体的聊天客户端对象
        ChatClient03 client = new ChatClient03();
        //调用客户端的主方法
        client.start();
    }

    /**
     * 编写一个获取服务端信息的处理器。即一个任务体
     */
    class GetServerInfoHandler implements Runnable{
        public void run(){
            try {
                //获取接受服务端发送过来的数据的输入流对象
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));
                String line = "";
                while((line = br.readLine()) != null){
                    System.out.println("服务端返回的数据:"+line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4 第四个版本

客户端多开,客户端自己和自己聊天。

3.4.1 服务端代码

将第三个版本的代码,改成多客户端(多线程)并发执行。

java 复制代码
package com.se.day12.Exercise;

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

/**
 * 4. 客户端多开,客户端自己和自己聊天。
 *              服务端
 */
public class ChatServer04 {
    //定义一个TCP通信协议的服务端属性
    private ServerSocket server;
    public ChatServer04(int port) {
        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            System.out.println("---服务器启动失败---");
        }
    }

    /**
     * 聊天的主方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("---等待客户端连接---");
                Socket socket = server.accept();
                System.out.println("---一个客户端连接上了---");

                //每获取一个客户端的Socket对象,就应该将其放入一个并发线程中
                Runnable task = new GetClientInfoHandler(socket);
                Thread thread = new Thread(task);
                thread.start();
            }

        } catch (IOException e) {
            System.out.println("---一个客户端离线了---");
        }
    }

    public static void main(String[] args) {
        //创建一个具体的服务器对象
        ChatServer04 chatServer  = new ChatServer04(8888);
        //调用聊天的主方法
        chatServer.start();

    }

    /**
     * 定义一个处理客户端信息的处理器,即一个任务体
     */
    class GetClientInfoHandler implements Runnable {
        //因为run方法中使用了 start方法中的socket局部变量
        //所以可以在该类中添加一个属性,run方法中可以访问该类的属性
        private Socket socket;
        //提供一个构造器,将start方法中的局部变量socket传进来,给属性赋值, 这样,run方法中使用的socket就是
        //start方法中的局部变量指向的对象
        public GetClientInfoHandler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            try {
                //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));

                //通过服务端的Socket对象,获取输出流,将信息返回给客户端
                OutputStream outputStream = socket.getOutputStream();
                PrintWriter pw = new PrintWriter(
                        new OutputStreamWriter(outputStream,"utf-8"),true);

                //循环打印客户端发送过来的信息
                String info = "";
                while ((info = br.readLine()) != null) {
                    //使用输出流,将其返回即可
                    pw.println(info);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4.2 客户端代码

与第三个版本一致。

3.5 第五个版本

一个客户端与多个客户端对话,同时处理某一个客户端下线的问题。

3.5.1 服务端代码

java 复制代码
package com.se.day12.Exercise;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 5.一个客户端与多个客户端对话,同时处理某一个客户端下线的问题。
 *          服务端
 */
public class ChatServer05 {
    //定义一个TCP通信协议的服务端属性
    private ServerSocket server;
    //添加一个Map属性,用于存储多个客户端的标识与输出流对象。 Map集合(散列表)
    private Map<String,PrintWriter> allOut;

    public ChatServer05(int port) {
        try {
            server = new ServerSocket(port);
            //给map集合赋值
            allOut = new HashMap<String,PrintWriter>();

        } catch (IOException e) {
            System.out.println("---服务器启动失败---");
        }
    }

    /**
     * 聊天的主方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("---等待客户端连接---");
                Socket socket = server.accept();
                System.out.println("---一个客户端连接上了---");

                //每获取一个客户端的Socket对象,就应该将其放入一个并发线程中
                Runnable task = new GetClientInfoHandler(socket);
                Thread thread = new Thread(task);
                thread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //创建一个具体的服务器对象
        ChatServer05 chatServer  = new ChatServer05(8888);
        //调用聊天的主方法
        chatServer.start();

    }

    /**
     * 定义一个处理客户端信息的处理器,即一个任务体
     */
    class GetClientInfoHandler implements Runnable {
        //因为run方法中使用了 start方法中的socket局部变量
        //所以可以在该类中添加一个属性,run方法中可以访问该类的属性
        private Socket socket;
        //提供一个构造器,将start方法中的局部变量socket传进来,给属性赋值, 这样,run方法中使用的socket就是
        //start方法中的局部变量指向的对象
        public GetClientInfoHandler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            String nickname = null;
            try {
                //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));

                //通过服务端的Socket对象,获取输出流,将信息返回给客户端
                OutputStream outputStream = socket.getOutputStream();
                PrintWriter pw = new PrintWriter(
                        new OutputStreamWriter(outputStream,"utf-8"),true);



                //先获取客户端的昵称
                nickname = br.readLine();
                //判断是否已经被占用,如果已经被占用,加后缀
                while(allOut.containsKey(nickname)){
                    nickname = nickname+Math.random();
                }
                //将昵称告诉客户端
                pw.println(nickname);

                //将输出流添加到Map集合中
                allOut.put(nickname,pw);
                System.out.println("-----在线人数:"+allOut.size()+"--------");



                //循环打印客户端发送过来的信息
                String info = "";
                while ((info = br.readLine()) != null) {
                    //将信息info发送到所有的客户端里
                    sendToAllClient(nickname,info);
                }
            } catch (IOException e) {
                System.out.println("---一个客户端离线了---");
                //删除对应的键值对
                allOut.remove(nickname);
                System.out.println("-----在线人数:"+allOut.size()+"--------");
            }
        }
    }
    public void sendToAllClient(String nickname, String info) {
        //遍历Map里的所有输出流,来发送info信息
        Set<String> nicks = allOut.keySet();
        for (String nick : nicks) {
            //获取对应的输出流,然后发送信息
            PrintWriter writer = allOut.get(nick);
            writer.println(nickname+"说:"+info);
        }
    }
}

3.5.2 客户端代码

java 复制代码
package com.se.day12.Exercise;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 6.一个客户端与多个客户端对话,同时处理某一个客户端下线的问题。
 *                      客户端
 */
public class ChatClient05 {
    //定义一个TCP通信协议的客户端属性
    private Socket socket;
    public ChatClient05() {
        //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset
        try {
            socket = new Socket("localhost",8888);
        } catch (IOException e) {
            System.out.println("---服务器崩溃中---");
        }
    }
    // 聊天的主方法
    public void start(){
        try {

            //获取一个获取服务端信息的处理器任务
            Runnable task = new GetServerInfoHandler();
            Thread thread = new Thread(task);
            thread.start();

            //获取向服务器发送信息的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            //封装成字符缓存流,可以按行输出
            PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(outputStream,"UTF-8"),
                    true);
            //使用控制台扫描对象类型,不断的扫描控制台上的文字
            Scanner scan = new Scanner(System.in);
            //先设置昵称
            System.out.println("请输入您的昵称:");
            String nickname = scan.nextLine();
            //将昵称发送给服务器,让服务器校验昵称是否已经被占用
            out.println(nickname);

            System.out.println("====开始聊天====");
            while(true){
                String info = scan.nextLine();
                //将扫描的信息发送到服务端
                out.println(info);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //创建一个具体的聊天客户端对象
        ChatClient05 client = new ChatClient05();
        //调用客户端的主方法
        client.start();
    }

    /**
     * 编写一个获取服务端信息的处理器。即一个任务体
     */
    class GetServerInfoHandler implements Runnable{
        public void run(){
            try {
                //获取接受服务端发送过来的数据的输入流对象
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));
                //获取自己的昵称:
                String nickname = br.readLine();
                System.out.println("您的昵称:"+nickname);


                String line = "";
                while((line = br.readLine()) != null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.6 第六个版本

客户端私聊模式。

3.6.1 服务端代码

java 复制代码
package com.se.day12.chat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 6.私聊模式
 *  聊天服务端
 */
public class ChatServer {
    //定义一个TCP通信协议的服务端属性
    private ServerSocket server;
    //添加一个Map属性,用于存储多个客户端的标识与输出流对象。 Map集合(散列表)
    private Map<String,PrintWriter> allOut;

    public ChatServer(int port) {
        try {
            server = new ServerSocket(port);
            //给map集合赋值
            allOut = new HashMap<String,PrintWriter>();

        } catch (IOException e) {
            System.out.println("---服务器启动失败---");
        }
    }
    /**
     * 聊天的主方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("---等待客户端连接---");
                Socket socket = server.accept();
                System.out.println("---一个客户端连接上了---");
                Runnable task = new GetClientInfoHandler(socket);
                Thread thread = new Thread(task);
                thread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //创建一个具体的服务器对象
        ChatServer chatServer = new ChatServer(8888);
        //调用聊天的主方法
        chatServer.start();
    }

    /**
     * 定义一个处理客户端信息的处理器,即一个任务体。
     */
    class GetClientInfoHandler implements Runnable {
        //因为run方法中使用了 start方法中的socket局部变量
        //所以可以在该类中添加一个属性,run方法中可以访问该类的属性
        private Socket socket;
        //提供一个构造器,将start方法中的局部变量socket传进来,给属性赋值, 这样,run方法中使用的socket就是
        //start方法中的局部变量指向的对象
        public GetClientInfoHandler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {
            String nickname = null;
            try {
                //通过服务端的Socket对象,获取输入流,接受客户端发送过来的数据
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));

                //通过服务端的Socket对象,获取输出流,将信息返回给客户端
                OutputStream outputStream = socket.getOutputStream();
                PrintWriter pw = new PrintWriter(
                        new OutputStreamWriter(outputStream,"utf-8"),true);

                //先获取客户端的昵称
                nickname = br.readLine();
                //判断是否已经被占用,如果已经被占用,加后缀
                while (allOut.containsKey(nickname)) {
                    nickname = nickname + Math.random();
                }
                //将昵称告诉客户端
                pw.println(nickname);
                //将该客户端的输出流添加到Map集合中
                allOut.put(nickname, pw);
                System.out.println("-----在线人数:" + allOut.size() + "-----------");
                //循环打印客户端发送过来的信息
                String info = "";
                while ((info = br.readLine()) != null) {
                    if(info.startsWith("@")){
                        sendPrivate(nickname,info);
                    }else{
                    //将信息info发送到所有的客户端里
                    sendToAllClient(nickname, info);
                    }
                }
            } catch (IOException e) {
                System.out.println("---一个客户端离线了---");
                //删除对应的键值对
                allOut.remove(nickname);
                System.out.println("-----在线人数:" + allOut.size() + "-----------");
            }
        }

        public void sendToAllClient(String nickname, String info) {
            //遍历Map里的所有输出流,来发送info信息。
            Set<String> nicks = allOut.keySet();
            for (String nick : nicks) {
                //获取对应的输出流,然后发送信息。
                allOut.get(nick).println(nickname + "说:" + info);
            }
        }
        public void sendPrivate(String nickname, String info) {
            Set<Map.Entry<String, PrintWriter>> entries = allOut.entrySet();
            for (Map.Entry<String, PrintWriter> entry : entries) {
                if (info.startsWith("@" + entry.getKey())) {
                    entry.getValue().println(nickname + "说:" + info);
                }
            }
            PrintWriter value = allOut.get(nickname);
            value.println(nickname + "说:" + info);
        }
    }
}

3.6.2 客户端代码

java 复制代码
package com.se.day12.chat;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 6.私聊模式
 *  聊天客户端
 */
public class ChatClient {
    //定义一个TCP通信协议的客户端属性
    private Socket socket;
    public ChatClient() {
        //获取Socket对象,同时向服务端发送连接请求, 连接失败:Connection reset
        try {
            socket = new Socket("localhost",8888);
        } catch (IOException e) {
            System.out.println("---服务器崩溃中---");
        }
    }
    // 聊天的主方法
    public void start(){
        try {

            //获取一个获取服务端信息的处理器任务
            Runnable task = new GetServerInfoHandler();
            Thread thread = new Thread(task);
            thread.start();

            //获取向服务器发送信息的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            //封装成字符缓存流,可以按行输出
            PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(outputStream,"UTF-8"),
                    true);
            //使用控制台扫描对象类型,不断的扫描控制台上的文字
            Scanner scan = new Scanner(System.in);
            //先设置昵称
            System.out.println("请输入您的昵称:");
            String nickname = scan.nextLine();
            //将昵称发送给服务器,让服务器校验昵称是否已经被占用
            out.println(nickname);

            System.out.println("====开始聊天====");
            while(true){
                String info = scan.nextLine();
                //将扫描的信息发送到服务端
                out.println(info);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //创建一个具体的聊天客户端对象
        ChatClient client = new ChatClient();
        //调用客户端的主方法
        client.start();
    }

    /**
     * 编写一个获取服务端信息的处理器,即一个任务体。
     */
    class GetServerInfoHandler implements Runnable{
        public void run(){
            try {
                //获取接受服务端发送过来的数据的输入流对象
                InputStream inputStream = socket.getInputStream();
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(inputStream,"utf-8"));
                //获取自己的昵称:
                String nickname = br.readLine();
                System.out.println("您的昵称:"+nickname);

                String line = "";
                while((line = br.readLine()) != null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
相关推荐
一袋米扛几楼981 小时前
【密码学】CrypTool2 工具是什么?
服务器·网络·密码学
码农的小菜园4 小时前
gradle常用指令使用笔记
笔记
南棱笑笑生5 小时前
20260310在瑞芯微原厂RK3576的Android14查看系统休眠时间
服务器·网络·数据库·rockchip
鸟电波5 小时前
硬件笔记——示波器篇
笔记
yy55275 小时前
LNAMP 网络架构与部署
网络·架构
Don.TIk5 小时前
SpringCloud学习笔记
笔记·学习·spring cloud
Godspeed Zhao6 小时前
现代智能汽车系统——CAN网络2
网络·汽车
cd11840516 小时前
AutoCAD Electrical 2020学习笔记
笔记·学习
爱丽_6 小时前
Docker 从原理到项目落地(镜像 / 容器 / 网络 / 卷 / Dockerfile)
网络·docker·容器
Sarvartha7 小时前
递归、回溯与动态规划学习笔记
笔记·学习·动态规划