网络编程的知识点

一、网络编程概述

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();
            }
        }
    }
}
相关推荐
齐 飞22 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
方方怪38 分钟前
与IP网络规划相关的知识点
服务器·网络·tcp/ip
丫头,冲鸭!!!1 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
听忆.1 小时前
手机屏幕上进行OCR识别方案
笔记
weixin_442643422 小时前
推荐FileLink数据跨网摆渡系统 — 安全、高效的数据传输解决方案
服务器·网络·安全·filelink数据摆渡系统
阑梦清川2 小时前
JavaEE初阶---网络原理(五)---HTTP协议
网络·http·java-ee
Selina K2 小时前
shell脚本知识点记录
笔记·shell
2 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
FeelTouch Labs3 小时前
Netty实现WebSocket Server是否开启压缩深度分析
网络·websocket·网络协议
霍格沃兹测试开发学社测试人社区3 小时前
软件测试学习笔记丨Flask操作数据库-数据库和表的管理
软件测试·笔记·测试开发·学习·flask