网络编程

网络通信三要素

IP地址:设备在网络中的地址,是唯一的标识

端口:应用程序在设备中的唯一标识

协议:数据在网络传输中的规则,常见的协议有UDP协议和TCP协议

要素一、IP地址

  • IP(Internet Protocol):全称“ 互联网协议地址”,是分配给上网设备的唯一标志。
  • 常见的IP分类为:IPv4和IPv6
    • IPv4:32bit (4个字节)
    • IPv6:128位(16个字节),号称可以为地球每一粒沙子编号。
      • IPv6分成8个整数,每个整数用四个十六进制位表示,数之间用冒号分开

IP地址形式:

  • 公网地址、和私有地址(局域网使用)
  • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0-- 192.168.255.255,专门为组织机构内部使用。

IP常用命令:

  • ipconfig:查看本机IP地址
  • 瓶 IP地址:检查网络是否连通

特殊IP地址:

  • 本机IP:127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。

IP地址操作类-InetAddress

InetAddress的使用

  • 此类表示Internet协议(IP)协议

InetAddress API如下

名称 说明
public static InteAddress getLocalHost() 返回本机的地址对象
public static InetAddress getByName(String host) 得到指定主机的IP地址对象,参数是域名或者IP地址
public String getHostName() 获取此IP地址的主机名
public String getHostAddress() 返回IP地址字符串
public boolean isReachable(int timeout) 在指定毫秒内连通该IP地址对应的主机,连通返回true
public class InetAddressDemo1 {
    public static void main(String[] args) throws Exception {
        //1.获取本机地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        //2.获取域名IP对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        //3.获取公网IP对象
        InetAddress ip3 = InetAddress.getByName("36.152.44.95");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());

        //4.判断是否能通:ping 5s之前测试是否可通
        System.out.println(ip3.isReachable(5000));
    }
}
output:
WIN-OVMGN94BT74
192.168.91.101
www.baidu.com
36.152.44.95
36.152.44.95
36.152.44.95
true

小结

  1. IP地址的代表类是谁?

    • InetAddress类
  2. 如何获取本机对象?

    • public static InetAddress getLocalHost()
  3. 何如判断与该IP地址对象是否互通?

    • public boolean isReachable(int timeout)

要素二、端口号

端口号

  • 端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是 0-65535。

端口类型

  • 周至端口:0-1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占用8080,MySQL占用3306)
  • 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。

要素三、协议

通信协议

  • 连接和通信数据的规则被称为网络通信协议

网络通信协议有两套参考模型

  • OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广。
  • TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

传输层常见的2个协议

  • TCP(Transmission Control Protocol):传输控制协议
  • UDP(User Datagram Protocol):用户数据报协议

TCP协议特点

  • 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
  • 传输前,采用“三次握手” 方式建立连接,所以是可靠的。
  • 在连接中可进行大数据量的传输。
  • 连接、发送数据都需要确认,且传输完毕后,还需要释放已建立的连接,通信效率低。

TCP协议通信场景

  • 对信息安全要求较高的场景,例如:文件下载、金融等数据通信、

UDP协议

  • UDP是一种无连接、不可靠传输的协议。
  • 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接。
  • 每个数据包的大小限制在64KB内
  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
  • 可以广播发送,发送数据结束时无需释放资源,开销小,速度快。

UDP协议通信场景

  • 语音通话,视频会话等。

UDP通信

快速入门

DatagramPacket:数据包对象(韭菜盘子)

构造器 说明
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 创建发送端数据包对象
buf:要发送的内容,字节数组
length:要发送内容的字节长度
address:接收端的IP地址对象
port:接收端的端口号
public DatagramPacket(byte[ ] buf, int length) 创建接收端的数据端对象
buf:用来存储接收的内容
length:能够接收内容的长度

DatagramPacket常用方法

方法 说明
public int getLength( ) 获得实际接收到的字节个数

DatagramPacket:发送端和接收端对象(人)

构造器 说明
public DatagramSocket( ) 创建发送端的Socket对象,系统会随机分配一个端口号。
public DatagramSocket(int port) 创建接收端的Socket对象并指定端口号

DatagramSocket类成员方法

方法 说明
public void send(DatagramPacket dp) 发送数据包
public void receive(DatagramPacket p) 接收数据包
/**
 * 接收端
 */
public class ServerTest1 {
    public static void main(String[] args) {
        System.out.println("==========服务端启动==========");
        try {
            //1.创建接收端对象,注册端口
            DatagramSocket socket = new DatagramSocket(8888);

            //2.创建一个数据包对象接收数据(韭菜盘子)
            byte[] buffer = new byte[1024 * 64];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            //3.接受数据
            socket.receive(packet);

            //4.取出数据
            //读取多少倒出多少
            int len = packet.getLength();
            String rs = new String(buffer, 0, len);
            System.out.println("收到了:" + rs);

            //获取发送端的IP和端口
            String ip = packet.getSocketAddress().toString();
            int port = packet.getPort();
            System.out.println("对方IP为:" + ip);
            System.out.println("对方端口为:" + port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
==========服务端启动==========
收到了:我是一颗韭菜
对方IP为:/192.168.131.101:52513
对方端口为:52513
/**
    发送端:一发一收
 */
public class ClientTest1 {
    public static void main(String[] args) {
        System.out.println("==========客户端启动==========");
        try {
            //1.创建发送端对象,发送端自带默认端口号(人)
            DatagramSocket socket = new DatagramSocket();

            //2.创建一个数据包对象封装数据(韭菜盘子)
            /**
             public DatagramPacket(byte buf[],  int length,
             InetAddress address, int port)
             参数一:封装发送的数据(韭菜)
             参数二:发送数据的大小
             参数三:服务端的IP地址
             参数四:服务端的端口
             */
            byte[] buffer = "我是一颗韭菜".getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

            //3.发送数据
            socket.send(packet);

            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
==========客户端启动==========

UDP通信:多发多收

案例:使用UDP通信实现:多发多收消息

需求:

  • 使用UDP通信方式开发接收端和发送端

分析:

  1. 发送端可以一致发消息
  2. 接收端可以不断的接收多个发送端的消息展示
  3. 发送端输入了exit则结束发送端程序
/**
    客户端:多发多收
 */
public class CilentDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动,开始发送数据:~~~~");
        //1.创建发送端对象
        DatagramSocket socket = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            //2.创建发送端数据对象
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);

            //3.发送数据
            socket.send(packet);
        }
    }
}
output:
客户端启动,开始发送数据:~~~~
请输入:
我是第二个UDP客户端
请输入:
exit
离线成功!
public class ServerDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动,开始接收数据:~~~~");
        //1.创建客户端对象
        DatagramSocket socket = new DatagramSocket(8888);

        while (true) {
            //2.创建接收数据包对象
            byte[] buffer = new byte[1024 * 6];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            //3.接收数据
            socket.receive(packet);
            int len = packet.getLength();
            String rs = new String(buffer, 0, len);
            System.out.println("接收到来自 ip:" + packet.getSocketAddress() + ",端口号:" + packet.getPort() + "的消息:" + rs);
        }
    }
}
output:
服务端启动,开始接收数据:~~~~
接收到来自 ip:/192.168.31.101:57151,端口号:57151的消息:halo啊
接收到来自 ip:/192.168.31.101:59056,端口号:59056的消息:你好
接收到来自 ip:/192.168.31.101:63080,端口号:63080的消息:我是第二个UDP客户端
接收到来自 ip:/192.168.31.101:64055,端口号:64055的消息:我是第三个

UDP通信-广播、组播

UDP的三种通信方式

  • 单播:单台主机与单台主机之间的通信
  • 广播:当前主机与所在网络中的所有主机通信
    • 组播:当前主机与选定的一组主机通信。

UDP如何实现广播

  • 使用广播地址:255.255.255.255
  • 具体操作:
    • 发送端发送管道数据包的目的地址写的是广播地址、且指定端口(255.255.255.255 9999)
    • 本机所在网段与其他主机的程序只要匹配端口成功既可以收到消息了(9999)
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("=======客户端启动了=====");
        while (true) {
            DatagramSocket socket = new DatagramSocket();
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入:");
            String msg = sc.nextLine();
            byte[] buffer = msg.getBytes();
            if ("exit".equals(msg)) {
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 9999);
            socket.send(packet);
        }
    }
}
output:
=======客户端启动了=====
请输入:
我是一个广播消息
请输入:
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("====服务端启动了======");
        DatagramSocket socket = new DatagramSocket(9999);
        
        while (true) {
            byte[] buffer = new byte[1024 * 64];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            socket.receive(packet);
            int len = packet.getLength();
            String rs = new String(buffer, 0, len);
            System.out.println("接收到来自 ip:" + packet.getSocketAddress()
                    + ",端口号:" + packet.getPort() + "的消息:" + rs);
        }
    }
}
output:
====服务端启动了======
接收到来自 ip:/192.168.31.101:55798,端口号:55798的消息:我是一个广播消息

UDP如何实现组播

  • 使用组播地址:224.0.0.0 ~ 239.255.255.255
  • 具体操作:
    • 发送端的数据包的目的地是组播IP(例如:224.0.0.1,端口:9999)
    • 接收端必须绑定该组播(224.0.0.1),端口还要对应发送端的端口9999,这样既可以接收该组播消息。
    • DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
public class ClientDemo {
    public static void main(String[] args)throws Exception {
        System.out.println("客户端启动,开始发送数据:~~~~");
        //1.创建发送端对象
        DatagramSocket socket = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)){
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            //2.创建发送端数据对象
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
                    InetAddress.getByName("224.0.0.1"),9999);

            //3.发送数据
            socket.send(packet);
        }
    }
}
output:
客户端启动,开始发送数据:~~~~
请输入:
我是一个新的组播消息
请输入:
exit
离线成功!
  public static void main(String[] args)throws Exception {
        System.out.println("服务端启动,开始接收数据:~~~~");
        //1.创建接收端对象,注册端口
        MulticastSocket socket = new MulticastSocket(9999);
        //2.把当前接收端加入到一个组播中去,绑定对于的组播消息的组播IP
//        socket.joinGroup(InetAddress.getByName("224.0.0.1"));
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.0.1"),9999),
                NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));

        while (true) {
            //2.创建接收数据包对象
            byte[] buffer = new byte[1024 * 6];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            //3.接收数据
            socket.receive(packet);
            int len = packet.getLength();
            String rs = new String(buffer, 0, len);
            System.out.println("接收到来自 ip:" + packet.getSocketAddress() + ",端口号:" + packet.getPort() + "的消息:" + rs);
        }
    }
}
output:
服务端启动,开始接收数据:~~~~
接收到来自 ip:/192.168.31.101:63641,端口号:63641的消息:我是一个新的组播消息

TCP通信-快速入门

编写客户端代码

Socket

构造器 说明
public Socket(String host, int port ) 创建发送端的Socket对象与服务端连接,参数为服务端程序的IP和端口

Socket类成员方法

方法 说明
OutputStream getOutputStream( ) 获得字节输出流对象
InputStream getInputStream( ) 获得字节输入流对象

客户端发送消息

需求:客户端实现步骤

  1. 创建客户端的Socket对象,请求与服务端的连接。
  2. 使用socket对象调用getOutputStraem( )方法得到字节输出流。
  3. 使用字节输出流完成数据的发送。
  4. 释放资源:关闭socket管道。
/**
    Socket 的客户端开发
 */
public class ClientDemo1 {
    public static void main(String[] args) {
        System.out.println("客户端启动,开始发送数据:~~~~");
        try {
            //1.创建Socket通信管道请求与服务器进行连接
            //public Socket(String host ,int port)
            //参数一:服务器的IP地址
            //参数二:服务器的端口号:
            Socket socket = new Socket("127.0.0.1",7777);

            //2.从socket通信管道中得到一个字节输出流,负责发送数据
            OutputStream os = socket.getOutputStream();

            //3.包装字节输出流为打印流
            PrintStream ps = new PrintStream(os);

            //4.发送消息
            ps.println("我是TCP的客户端,我已经与你建立连接!");
            ps.flush();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
客户端启动,开始发送数据:~~~~

编写服务端代码

ServerSocket(服务端)

构造器 说明
public ServerSocket(int port) 注册服务端端口

ServerSocket类成员方法

方法 说明
public Socket accpet() 等待接收客户端的Socket通信连接
连接成功返回Socket对象与客户端建立端到端通信
public class ServerDemo1 {
    public static void main(String[] args) {
        System.out.println("服务端启动,开始接收数据:~~~~");
        try {
            //1.注册端口
            ServerSocket serverSocket = new ServerSocket(7777);
            //2.必须调用accept方法,等待接收客户端socket的连接请求,建立Socket通信管道
            Socket socket = serverSocket.accept();
            //3.从Socket管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个字符缓冲输入流进行消息的接收
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            //5.按照行读取信息
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
服务端启动,开始接收数据:~~~~
/127.0.0.1:50871说了:我是TCP的客户端,我已经与你建立连接!

小结

1、TCP通信服务端用的代表雷?

  • ServerSocket类,注册端口
  • 调用accept( )方法阻塞等待接收客户端连接。得到Socket对象。

2、 TCP通信的基本原理?

  • 客户端怎么发,服务端就应该怎么收。
  • 客户端如果没有消息,服务端就会进入阻塞等待
  • Socket一方关闭或者出现异常、对方Socket也会失效或者出错

TCP通信-多发多收消息

需求:使用TCP通信方式实现:多发多收消息。

具体要求:

  1. 可以使用死循环控制服务端收完消息继续等待接收下一个消息
  2. 客户端也可以使用死循环等待用户不断输入消息。
  3. 客户端一旦输入了exit,则关闭客户端程序,并释放资源。
/**
    客户端,重复发
 */
public class ClientDemo2 {
    public static void main(String[] args) {
        System.out.println("===========客户端启动了===========");
        try {
            //1.创建客户端socket管道
            Socket socket = new Socket("127.0.0.1",12345);
            //2.创建一个字节输入流
            OutputStream os = socket.getOutputStream();
            //3.包装成打印流
            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            String msg;
            while (true){
                System.out.println("请输入:");
                msg  = sc.nextLine();
                //4.发送消息
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
===========客户端启动了===========
请输入:
现在是十二点
请输入:
我来研究TCP客户端重复发了
请输入:
exit
请输入:
public class ServerDemo2 {
    public static void main(String[] args) {
        System.out.println("===========服务端启动了===========");
        try {
            //1.注册端口
            ServerSocket serverSocket = new ServerSocket(12345);
            //2.调用accept方法,等到客户端的socket请求,建立一个socket通信管道
            Socket socket = serverSocket.accept();
            //3.从socket管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //包装成字符输入流,进行消息的读取
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while (true) {
                if ((msg = br.readLine())!=null){
                    System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
===========服务端启动了===========
/127.0.0.1:51561说了:现在是十二点
/127.0.0.1:51561说了:我来研究TCP客户端重复发了
/127.0.0.1:51561说了:exit

上面代码实现了多发多收,但是不可以同时接收多个客户端的消息,因为服务端现在只有一个线程,只能与一个客户端进行通信。

引入多线程处理服务端可以与多个客户端之间进行通信

public class ServerReaderThread extends Thread {
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了!");
        }

    }
}
public class ServerDemo4 {
    public static void main(String[] args) {
        System.out.println("============服务端启动了=============");
        try {
            //1.注册端口
            ServerSocket serverSocket = new ServerSocket(6667);

            //a.定义一个死循环来不断接收客户端的socket请求
            while (true) {
                //2.每接收到一个客户端的socket管道,交给一个子线程去负责
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                //3.开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
    多发多收
 */
public class ClientDemo3 {
    public static void main(String[] args) {
        System.out.println("=========客户端启动了=========");
        try {
            //1.创建Socket管道
            Socket socket = new Socket("127.0.0.1",6667);

            OutputStream os = socket.getOutputStream();

            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            String msg;
            while (true){
                System.out.println("请输入:");
                msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 以上代码主线程定义了循环负责接收客户端Socket管道连接
  • 每接收到一个Socket通信管道后分配一个独立的线程负责处理他。

TCP通信-使用线程池优化

public class ServerReaderRunnable implements Runnable {
    private Socket socket;

    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;

    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + " 说了:" + msg);
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() + " 下线了!");
        }
    }
}
public class ClientDemo4 {
    public static void main(String[] args) {
        System.out.println("=======客户端启动了=========");
        try {
            Socket socket = new Socket("127.0.0.1",5555);
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            String msg;
            while (true){
                System.out.println("请输入:");
                msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
=======客户端启动了=========
请输入:
我是第二个客户端,我也拽住了一个核心线程
请输入:
    =======客户端启动了=========
请输入:
我是第三个客户端,我也拽住了一条核心线程
请输入:

public class ServerDemo4 {
    private static final ExecutorService pool = new ThreadPoolExecutor(3,5,6
            , TimeUnit.SECONDS,new ArrayBlockingQueue<>(2)
            ,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        System.out.println("========服务端启动了========");
        try {
            //注册端口
            ServerSocket serverSocket = new ServerSocket(5555);
            //定义一个死循环由主线程负责不断地接收客户端的Socket管道连接
            while (true) {
                //每接收到一个客户端的Socket管道
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+" 上线了!");
                //任务对象负责读取消息
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
========服务端启动了========
/127.0.0.1:51798 上线了!
/127.0.0.1:51798 说了:我是第一个客户端,我拽住了一条核心线程
/127.0.0.1:51810 上线了!
/127.0.0.1:51810 说了:我是第二个客户端,我也拽住了一个核心线程
/127.0.0.1:51834 上线了!
/127.0.0.1:51834 说了:我是第三个客户端,我也拽住了一条核心线程
/127.0.0.1:51840 上线了!
/127.0.0.1:51844 上线了!
/127.0.0.1:51850 上线了!
/127.0.0.1:51850 说了:我是第六个客户端
/127.0.0.1:51853 上线了!
/127.0.0.1:51853 说了:我是第七个客户端
/127.0.0.1:51858 上线了!
java.util.concurrent.RejectedExecutionException: Task com.csl.d8_socket4.ServerReaderRunnable@5b6f7412 rejected from java.util.concurrent.ThreadPoolExecutor@27973e9b[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
/127.0.0.1:51853 下线了!
/127.0.0.1:51840 说了:我是第四个客户端
/127.0.0.1:51850 下线了!
/127.0.0.1:51844 说了:我是第五个客户端
/127.0.0.1:51844 下线了!

  • 服务端可以复用线程处理多个客户端,可以避免系统瘫痪
  • 适合客户端通信时长较短的场景。

TCP通信实战案例-即时通信

  • 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
  • 之前的代码是都发送给服务端的。
  • 即时通信需要进行端口转发的设计思想
  • 服务端需要把在线的Socket管道存储起来
  • 一旦收到一个消息要推送给其他管道
public class ClientDemo5 {
    public static void main(String[] args) {
        System.out.println("==========客户端启动了=========");

        try {
            Socket socket = new Socket("127.0.0.1",5555);

            //创建一个独立的线程负责这个客户端的读消息(服务器随时可能发信息过来)
            new ClientReaderThread(socket).start();

            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            while (true){
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ClientReaderThread extends Thread {
    private Socket socket;

    public ClientReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("收到消息:" + line);
            }
        } catch (Exception e) {
            System.out.println("你已被服务器强制下线");
        }

    }
}
output:
==========客户端启动了=========
我发送了一个即时通信消息
收到消息:我发送了一个即时通信消息

public class ServerDemo5 {
    private static final ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS
            , new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory()
            , new ThreadPoolExecutor.AbortPolicy());
    public static List<Socket> allOnlineSockets = new ArrayList<>();

    public static void main(String[] args) {
        System.out.println("==========服务端启动了========");
        try {
            ServerSocket serverSocket = new ServerSocket(5555);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                allOnlineSockets.add(socket);//上线完成

                pool.execute(new ServerReaderRunnable2(socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ServerReaderRunnable2 implements Runnable{
    private Socket socket;
    public ServerReaderRunnable2(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line=br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"说了:"+line);
                //端口转发给全部客户端Socket管道
                sedMsgToAll(line);
            }

        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了!");
            ServerDemo5.allOnlineSockets.remove(socket);
        }
    }

    private void sedMsgToAll(String msg) throws Exception {
        for (Socket socket : ServerDemo5.allOnlineSockets) {
            PrintStream ps = new PrintStream(socket.getOutputStream());
            ps.println(msg);
            ps.flush();
        }
    }
}
output:
==========服务端启动了========
/127.0.0.1:52109上线了!
/127.0.0.1:52109说了:我发送了一个即时通信消息
/127.0.0.1:52109下线了!

TCP通信实战案例-模拟BS系统

  1. 之前的客户端都是什么样的?
  • 其实就是CS架构,客户端是需要我们自己开发的
  1. BS结构是什么样的,需要开发客户端吗?
  • 浏览器访问服务端,不需要开发客户端。

注意:服务器必须给浏览器相应HTTP协议格式的数据,否则浏览器不识别。

HTTP响应数据的协议格式:就是给浏览器显示的网页信息

public class BSserverDemo {
    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket ss = new ServerSocket(8080);
            //2.创建一个循环接收多个客户端的请求
            while (true){
                Socket socket = ss.accept();
                //3.交给一个独立的线程来处理
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //浏览器已经与本线程建立了Socket管道
            //响应消息给浏览器显示
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //必须响应HTTP协议格式数据,否则浏览器不认识消息
            ps.println("HTTP/1.1 200 OK");//协议类型和版本  响应成功的消息!
            ps.println("Content-Type:text/html;charset-UTF-8");//响应的类型: 文本/网页
            ps.println();//必须发送一个空行

            ps.println("<span style='color:red;font-size:90px'>《独孤患者》</span>");
            ps.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用线程池优化

public class BSserverDemo {
    //使用静态变量记住一个线程池对象
    private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2),Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        try {
            //1.注册端口
            ServerSocket ss = new ServerSocket(8080);
            //2.创建一个循环接收多个客户端的请求
            while (true){
                Socket socket = ss.accept();
                //3.交给一个独立的线程来处理
               pool.execute(new ServerRunnableThread(socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

    @Override
    public void run() {
        try {
            //浏览器已经与本线程建立了Socket管道
            //响应消息给浏览器显示
            PrintStream ps = new PrintStream(socket.getOutputStream());
            //必须响应HTTP协议格式数据,否则浏览器不认识消息
            ps.println("HTTP/1.1 200 OK");//协议类型和版本  响应成功的消息!
            ps.println("Content-Type:text/html;charset-UTF-8");//响应的类型: 文本/网页
            ps.println();//必须发送一个空行

            ps.println("<span style='color:red;font-size:90px'>《独孤患者》</span>");
            ps.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
posted @ 2022-01-27 01:09  tryAgainCs  阅读(122)  评论(0编辑  收藏  举报