Java网络通信

前言

1. 基本知识




通俗一点就是CS就是要下载应用来访问的,BS是在浏览器上面访问的,不用下载

1.1 IP

IP地址就是你电脑的主机号,一台设备都有唯一的IP,端口就是程序的唯一标识



这上面就是我们的ip地址了,有IPv4和IPv6两种

因为IPv4不够用了,所以才用IPv6的


IP域名和IP就是一个东西

只要有网还有IP地址存在,那么ping就会成功,

127.0.0.1就是我们这个电脑的IP地址,默认是

每个电脑的127.0.0.1都是自己的IP

java 复制代码
        //1.获取本机IP地址对象
        InetAddress ip1=InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());//获取本机IP地址对象的名字
        System.out.println(ip1.getHostAddress());//获取本机IP地址对象的IP


我这个就叫hahaha

java 复制代码
        //2.获取指定IP或者域名的IP地址对象
        InetAddress ip2=InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

然后我直接在网址输入这个IP

就真的变成百度了

java 复制代码
        System.out.println(ip2.isReachable(6000));

这个就是看6秒内,我们本机能否和百度IP建立联系,当然有网就可以了

1.2 端口与通信协议




因为视频语音的话,少了几个包最多只是声音变小,卡了一下而已

三次握手的意思就是,第一次确认了客户端可以发消息,第二次确认了服务器端可以收消息,和发消息,第三次就是确认客户端可以接受消息,这样两边都可以收发消息了,就没有问题了


2. UDP通信

2.1 一发一收


java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个服务器对象(创建一个接菜的人),注册端口
        DatagramSocket socket=new DatagramSocket(6666);//这个对象也就是这个进程的端口是6666,可以指定端口的,不然就是随机的
        //2.创建一个数据包对象,用于接收数据,就是创建一个盘子
        byte[] buffer=new byte[1024*64];//因为一个数据包最大发送的内容就是这么大
        DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
        //3.开始正式使用数据包来接收客户端发来的数据
        socket.receive(packet);//只管接收就可以了,只要把IP和端口告诉客户端,只管输入就可以了,这边只管接收
        //接收到的内容在buffer中,然后就是接收到了多少就倒出多少,不可能倒64kb吧
        int len=packet.getLength();
        String rs=new String(buffer,0,len);
        System.out.println(rs);
    }
}

这个是服务端

java 复制代码
public class Client {
    public static void main(String[] args) throws Exception {
        //1.创建客户端对象(发菜的人)
        DatagramSocket socket=new DatagramSocket();
        //包装数据包对象分装要发出去的数据(创建盘子)
        byte[] bytes="我是快乐的客户端,我爱你abc".getBytes();
//    public DatagramPacket(byte buf[], int length,
//        InetAddress address, int port) {
//            this(buf, 0, length, address, port);
//        }
        //参数一:分装要发出去的数据,参数二:发送出去的数据大小(字节个数),参数三:服务端的IP地址,参数四:服务端程序的端口
        DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子

        socket.send(packet);//发出去
        System.out.println("发送完毕");
        socket.close();
    }
}

这个是客户端

应该先启动服务端,因为如果先启动客户端的话,直接就发送了,也不会管你发送成功了没有,还没等服务端接受就没了


只要服务端启动了,那么就会停在那个receive那里,等待接受

然后就是服务端接受到了的话,也可以得到客户端的IP和端口,还有就是资源不要忘了关闭,关闭客户端,服务端就可以了

java 复制代码
        System.out.println(packet.getAddress().getHostAddress());
        System.out.println(packet.getPort());
        socket.close();

然后就是其实客户端可以创建一个指定端口的对象

java 复制代码
        //1.创建客户端对象(发菜的人)
        DatagramSocket socket=new DatagramSocket(7777);


2.2 多发多收

下面我们来实现多个客户端发送的场景

用个while循环就可以了

java 复制代码
public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket=new DatagramSocket(7777);
        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;
            }
            byte[] bytes=msg.getBytes();
            DatagramPacket packet=new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),6666);//包装菜和盘子
            socket.send(packet);//发出去
        }
    }
}

这样就可以了,那么服务端也要循环来接收

java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个服务器对象(创建一个接菜的人),注册端口
        DatagramSocket socket=new DatagramSocket(6666);
        byte[] buffer=new byte[1024*64];
        DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
        while (true) {
            socket.receive(packet);
            int len=packet.getLength();
            String rs=new String(buffer,0,len);
            System.out.println(rs);
            System.out.println("--------------------");
        }
    }
}

因为服务端一般是不会关闭的,所以不用close

现在我们就可以客户端一直输入,服务端一直接受了

但是如何启动多个客户端呢,我们的编译器默认的是只能启动一个,但是可以改

点编辑配置

点允许多个实例

但是还是会报错,为什么呢,因为我们的客户端的端口指定了为7777

,不可能两个程序用一个端口吧,所以我们还是让系统随机分配端口吧

java 复制代码
        DatagramSocket socket=new DatagramSocket();

这样就可以了






3. TCP通信

3.1 一发一收


java 复制代码
public class Client {
    public static void main(String[] args) throws Exception {
        //创建socket对象,并请求与服务端连接
        Socket socket=new Socket("127.0.0.1",8888);//连接的就是IP为127.0.0.1,端口为8888的服务端
        //2.从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os=socket.getOutputStream();
        //3.把这个低级的字节输出流包装成数据输出流
        DataOutputStream dos=new DataOutputStream(os);
        //4.写数据出去
        dos.writeUTF("在一起毫秒");
        dos.close();
        socket.close();//要释放两个资源,一个网络的,一个IO流的
    }
}
java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        //创建一个ServerSocket的对象,同时为服务端注册端口
        ServerSocket serverSocket=new ServerSocket(8888);
        //使用serverSocket对象调用一个accept方法,等待客户端的连接请求,返回一个socket,与客户端对应,在服务端但是
        Socket socket=serverSocket.accept();
        //从socket通信管道中得到一个字节输入流
        InputStream is=socket.getInputStream();
        //包装成高级的IO流
        DataInputStream dis=new DataInputStream(is);
        //使用数据输入流读取客户端发送过来的消息
        String rs=dis.readUTF();
        System.out.println(rs);
        //打印客户端的IP
        System.out.println(socket.getRemoteSocketAddress());
        dis.close();
        socket.close();
    }
}

这个服务端会先在accept那里等着连接客户端,成功后,就会在readUTF等待读

3.2 多发多收

java 复制代码
public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket=new Socket("127.0.0.1",8888);
        OutputStream os=socket.getOutputStream();
        DataOutputStream dos=new DataOutputStream(os);
        
        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说:");
            String msg=sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();//刷新过去,防止在缓冲区中
        }
    }
}
java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        Socket socket=serverSocket.accept();
        InputStream is=socket.getInputStream();
        DataInputStream dis=new DataInputStream(is);
        while(true){
            String rs=dis.readUTF();
            System.out.println(rs);
        }
    }
}




但是在我们客户端推出的时候,服务端就会异常了,因为一端断开了,read不了了,但是还在等,就会出错,所以要捕获

java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        Socket socket=serverSocket.accept();
        InputStream is=socket.getInputStream();
        DataInputStream dis=new DataInputStream(is);
        while(true){
            try {
                String rs=dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                System.out.println(socket.getRemoteSocketAddress()+"离线了");
                dis.close();
                socket.close();
                break;
            }
        }
    }
}



但是我们这个不能实现服务端与多个用户通信,因为socket始终只有一个,而且又不确定用户端有多少个

所以我们就要用多线程的处理思想了

3.3 与多个用户通信

我们可以这样,用主线程来接收客户端的连接,连接了一个的话,就把这个socket交给一个子线程来处理

java 复制代码
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {

    }
}

先自定义一个线程,因为要把socket交给线程,所以线程的定义要有socket

java 复制代码
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            new ServerReaderThread(socket).start();//交给一个新的线程来处理
        }
    }
}
java 复制代码
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(rs);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样的话,主线程就会在accept那里等着,子线程的话,就会在read那里等着

这样我们就可以开启多个客户端了

3.4 用户与用户之间通信

其实实现逻辑就是客户端发给服务端,服务端发给其他客户端

就是一个客户端发送消息,其他客户端都能看到

socket用集合存着,这个集合只有一个

java 复制代码
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}

接收到一个,就增加一个

对应还有删除,

java 复制代码
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    Server.onLineSockets.remove(socket);//

还要把这个socket发给其他用户

分发给全部客户端就可以了

java 复制代码
                    String rs = dis.readUTF();
                    System.out.println(rs);
                    sendRsToAll(rs);
java 复制代码
    private void sendRsToAll(String rs) throws Exception {
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os=onLineSocket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF(rs);
            dos.flush();
        }
    }

然后对应每个客户端都要接受来自对应服务端的数据

因为socket是对应的,所以一定会对应回去的

每个客户端都要一直读,所以这也是一个死循环,而且还要一直死循环写,所以又是多线程的逻辑

又要建立一个新的线程

这个的逻辑和服务端的读的线程逻辑是差不多的

java 复制代码
public class ClientReaderThread extends Thread{
    Socket socket;
    public ClientReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(socket.getRemoteSocketAddress()+"说"+rs);
                } catch (IOException e) {
                    Server.onLineSockets.remove(socket);//
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
java 复制代码
public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket=new Socket("127.0.0.1",8888);
        new ClientReaderThread(socket).start();//创建一个线程来专门接受读的
        OutputStream os=socket.getOutputStream();
        DataOutputStream dos=new DataOutputStream(os);

        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说:");
            String msg=sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();//刷新过去,防止在缓冲区中
        }
    }
}
java 复制代码
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}
java 复制代码
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true) {
                try {
                    String rs = dis.readUTF();
                    System.out.println(rs);
                    sendRsToAll(rs);
                } catch (IOException e) {
                    System.out.println(socket.getRemoteSocketAddress()+"离线了");//结束这个线程
                    Server.onLineSockets.remove(socket);//
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendRsToAll(String rs) throws Exception {
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os=onLineSocket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF(rs);
            dos.flush();
        }
    }
}



3.5 实现一个简易版的BS架构


我们的服务端只有一个,不同浏览器打开我们的服务端的时候,就会创建不同的子线程

java 复制代码
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        while(true){
            Socket socket=serverSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线了"+socket.getRemoteSocketAddress());
            new ServerReaderThread(socket).start();
        }
    }
}
java 复制代码
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
       //连接到了,就要返回一个'黑马程序员给浏览器'
        try {
            OutputStream os=socket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF("黑马程序员");
            dos.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后我们就可以在浏览器上连接我们的服务端了


但是这样不行呢,为什么呢,因为服务器必须给浏览器响应http协议规定的数据格式,不然浏览器不识别返回的数据。

而且浏览器会疯狂去试探,所以有很多上线了,但是没有一个能认识那些数据,所以会出错

因为换行数据流不好操作,所以我们用打印流

我们返回的数据就要是上面的格式,不然浏览器无法识别,这个就是http协议

java 复制代码
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
       //连接到了,就要返回一个'黑马程序员给浏览器'
        try {
            OutputStream os=socket.getOutputStream();
            PrintStream ps=new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行、、、..后面的就是我们要输入的内容
            ps.println("黑马程序员");
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


可以美化一下

java 复制代码
            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");



换个浏览器也可以

拓展一下,我们可以使用线程池

java 复制代码
        //创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2
        ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

因为为线程池,所以传入的也都是任务对象,就不能是线程对象了

java 复制代码
public class ServerReaderRunnable implements Runnable{
java 复制代码
public class Server {
    public static List<Socket> onLineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket=new ServerSocket(8888);
        //创建一个线程池,,因为这个是IO型任务,所以核心线程数量=CPU核数*2
        ThreadPoolExecutor pool=new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        while(true){
            Socket socket=serverSocket.accept();
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}
java 复制代码
public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        //连接到了,就要返回一个'黑马程序员给浏览器'
        try {
            OutputStream os=socket.getOutputStream();
            PrintStream ps=new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行、、、..后面的就是我们要输入的内容
            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员");
            ps.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样就可以了

总结

后面还是接着讲Java

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck1 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei1 小时前
java的类加载机制的学习
java·学习
Yaml43 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~3 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616883 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7893 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java4 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~4 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果4 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot