网络编程

一,网络编程概述

网络编程就是在网络通信协议下,不同计算机上运行的程序,进行的数据传送。

本质就是不同计算机之间通过网络进行数据传送。

java中可用java.net包下的技术,开发出常见的网络应用程序。

常见的软件架构:

  1. B/S架构:Browser/Server,即浏览器/服务器的架构

    只需要一个浏览器,用户通过不同的网址,客户访问不同的服务器。

    优点:

    1. 不需要开发客户端,只需要页面+服务的
    2. 用户不需要下载,打开浏览器即可使用

    缺点:如果应用过大,用户体验会受到影响

  2. C/S架构:Client/Server,即客户端/服务的架构

    在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。

    优点:画面可做的非常精美,用户体验很好

    缺点:需要开发客户端,也需要开发服务的,维护开发部署很麻烦,用户需要下载和更新的时候很麻烦。

网络编程三要素:ip,端口,协议。(具体去学计网)

二,InetAddress

InetAddress是java用来操作ip的类,它底层有两个子类Inet4Address和Inet6Address根据你传入的ip地址的格式来创建ipv4或ipv6的对象。

InetAddress获取的对象就代表了一台电脑的IP的对象。

常用方法:

image-20240605201126414

举例:

    public static void main(String[] args) throws IOException {
        //1.获取本机ip地址
        InetAddress localHost = InetAddress.getLocalHost();
        //2.根据ip地址或域名返回一个InetAddress对象
        InetAddress addressByName = InetAddress.getByName("www.baidu.com");
        //3.获取该ip地址对象对应的主机名
        String hostName = localHost.getHostName();
        System.out.println(hostName);
        //4.获取ip地址对象对应的ip地址
        String hostAddress = localHost.getHostAddress();
        System.out.println(hostAddress);
        //在指定毫秒内,判断主机与该ip地址对应的主机之间能否连通
        if (addressByName.isReachable(3000)) {
            System.out.println("可以访问");
        }else{
            System.out.println("不可以访问");
        }
    }

三,协议

UDP协议:

  • 用户数据报协议(User Datagram Protocol)

  • UDP是面向无连接通信协议

    速度块,有大小限制一次最多64k,数据不安全,易丢失

面向无连接:数据直接发送,不确认是否连接成功。

应用场景:在线视频,语音通话,网络会议。

TCP协议:

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

  • TCP协议是面向连接的通信协议

    速度慢,没有大小限制,数据安全。

面向连接:数据发送之前会确定是否连接成功再发送数据

应用场景:下载软件,文字聊天,发送邮件。

四,UDP通信程序

  • 特点:无连接、不可靠通信。
  • 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
  • Java提供了一个java.net.Datagramsocket类来实现UDP通信:

DatagramSocket:用于创建客户端、服务端

构造器 说明
public DatagramSocket() 创建客户端的Socket对象,系统会随机分配一个端口号
public DatagramSocket(int port) 创建服务端的Socket对象,并指定端口号

常用方法:

方法 说明
public void send(DatagramPacket dp) 发送数据包
public void receive(DatagramPacket p) 使用数据包接受数据

DatagramPacket:创建数据包

构造器 说明
public DatagramPacket(byte[] buf,int length, InetAddress address, int port) 创建发送出去的数据包对象
public DatagramPacket(byte[] buf,int length) 创建用来接受数据的数据包

常用方法

方法 说明
public int getLength() 获取数据包,实际接收到的字节个数
public InetAddress getAddress() 获取到发送方的ip对象
public int getPort() 获取到发送方的端口

举例:


import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
//接收方
public class ReceiveMessage {
    public static void main(String[] args) {
        //1.创建接受数据的服务端
        try(DatagramSocket ds=new DatagramSocket(8888)){
            //2.创建接受数据的数据包
            byte[] bytes=new byte[1024];
            while(true){
                DatagramPacket dp=new DatagramPacket(bytes,bytes.length);
                ds.receive(dp);
                //3.将接收到的数据转换为字符串并输出
                //获取到发送方的ip对象
                InetAddress address = dp.getAddress();
                //获取到发送方的端口
                int port = dp.getPort();
                //4.输出接收到的数据
                System.out.println("从"+address+":"+port+"接收到的数据是:"+new String(dp.getData(),0,dp.getLength()));
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

import java.net.*;
import java.util.Scanner;
import static java.lang.Thread.sleep;
//发送方
public class SendMessage {
    public static void main(String[] args) {
        //1.创建发送数据的客户端
        try(DatagramSocket ds=new DatagramSocket()){
            //2.创建服务端的InetAddress对象
            InetAddress byName = InetAddress.getByName("127.0.0.1");
            //3.创建要发送数据的数据包
            while(true){
                Scanner sc=new Scanner(System.in);
                String dataStr = sc.nextLine();
                //4.创建要发送给服务端的数据包
                DatagramPacket dp=new DatagramPacket(dataStr.getBytes(),dataStr.getBytes().length,byName,8888);
                //5.发送数据
                ds.send(dp);
            }
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

UDP的三种通信方式

  1. 单播:发送端只给一台设备发送数据(一对一)

    上述写的代码就是单波形式

  2. 组播:发送端给一组设备发送数据(一对多)

    组波地址:224.0.0.0到239.255.255.255,其中224.0.0.0到224.0.0.255为预留的组播地址

    我们只能用这部分预留的组播地址。

    组播和IP的区别是,ip只能表示一台设备,组播可以表示多台设备。

  3. 广播:发送端给局域网所有的设备发送数据(一对所有)

    广播地址:255.255.255.255

组波代码实现:

组播和单波代码实现的区别:

  1. 发送端和接收端使用MulticastSocket对象来代替DatagramSocket
  2. 发送端使用组播地址代替ip地址
  3. 接收端需要额外的将当前本机加入指定组播地址当中
  • 发送数据
package Text2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class Send {
    public static void main(String[] args) throws IOException {
        //1.创建MulticastSocket对象
        //MulticastSocket和DatagramSocket使用方式一样
        MulticastSocket ms=new MulticastSocket(1010);
        //创建DatagramPacket对象
        String s="hello world";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.0.1");//指定的是组播地址
        int port=1000;
        DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port);
        ms.send(dp);
        ms.close();
    }
}

  • 接收数据
package Text2;
import javax.xml.crypto.Data;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class Receive {
    public static void main(String[] args) throws IOException {
        //创建MulticastSocket对象
        MulticastSocket ms=new MulticastSocket(1000);
        //将当前本机,添加到224.0.0.1这一组
        InetAddress address=InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);
        //创建DatagramPacket数据包对象
        byte[] bytes = new byte[1024];
        DatagramPacket dp=new DatagramPacket(bytes,bytes.length);
        //接收数据
        ms.receive(dp);
        //解析数据
        byte[] data = dp.getData();
        int len=dp.getLength();
        String ip=dp.getAddress().getHostAddress();
        String name=dp.getAddress().getHostName();
        System.out.println("ip为:"+ip+"主机名为:"+name+"的人,发送了数据:"+new String(data,len));
        //释放资源
        ms.close();
    }
}

广播的代码实现:

只需要在单波的代码中发送端额发送地址修改为255.255.255.255即可实现广播

五,TCP通信程序

5.1 TCP的三次握手和四次挥手

  • 三次握手:目的是保证连接的建立

    过程如图:

    image

  • 四次挥手:目的是确保连接端口,且通道内数据处理完毕。

    过程如图:

    image

5.2 TCP通信快速入门

  • 特点:面向连接、可靠通信,
  • 通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端
  • Java提供了一个java.net.Socket类来实现TCP通信。

image-20240606143619924

5.2.1 客户端

构造器 说明
public Socket(String host,int port) 根据指定的服务器ip,端口号请求与服务端建立连接,连接通过就获得了客户端socket

方法:

方法 说明
public OutputStream getOutputStream() 获得字节输出流对象
public InputStream getInputStream() 获得字节输入流对象
public InetAddressgetInetAddress() 获取客户端的ip对象

代码实现:

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

public class Client {
    public static void main(String[] args) {
        //1.创建Socket对象,同时请求与服务器程序的连接
        try(Socket socket=new Socket("127.0.0.1",8888)){
            //2.从socket通道中获取输出流
            OutputStream outputStream = socket.getOutputStream();
            //3.包装
            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
            Scanner sc=new Scanner(System.in);
            //4.输入数据
            while(true){
                System.out.print("请输入内容:");
                String msg=sc.nextLine();
                if("exit".equals(msg)){
                    break;
                }
                dataOutputStream.writeUTF(msg);
                //刷新缓存
                dataOutputStream.flush();
            }
        }catch(Exception e){
            System.out.println("服务端访问失败!");
        }

    }
}

5.2.2 服务端

服务端是通过java.net包下的Serversocket类来实现的

ServerSocket:

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

常用方法:

方法 说明
public Socket accept() 阻塞等待客户端的连接请求,一旦与某个客户端成功连接,则返回服务端这边的Socket对象

代码:

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

public class Server {
    public static void main(String[] args) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用serverSocket调用accept方法获取到客户端Socket
            Socket accept = serverSocket.accept();
            //3.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //4.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //5.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    System.out.println("客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!");
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}

当我们客户端直接退出了,服务端就抛出异常

5.3 与多个客户端同时通信

上述快速入门案例只能实现了一对一通信,通过多线程来实现与多个客户端同时通信

代码实现:

只需将服务端接受socket改成循环即然后配套使用多线程即可

import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用循环来接受客户端请求
            while(true){
                Socket accept = serverSocket.accept();
                ServerReadThread serverReadThread = new ServerReadThread(accept);
                serverReadThread.start();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReadThread extends Thread{
    private Socket accept;

    public ServerReadThread(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    System.out.println("客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!");
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

5.4 实现群聊

image-20240606161519057

代码实现:

服务端:

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    //代表所有在线的客户端socket
    public static List<Socket> onlineSocketList=new ArrayList<Socket>();
    public static void main(String[] args) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用循环来接受客户端请求
            while(true){
                Socket accept = serverSocket.accept();
                onlineSocketList.add(accept);
                ServerReadThread serverReadThread = new ServerReadThread(accept);
                serverReadThread.start();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;

public class ServerReadThread extends Thread{

    private Socket accept;

    public ServerReadThread(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    sendMsgAll(msg);
                    // System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    String msg = "客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!";
                    System.out.println(msg);
                    //从集合中删除掉该客户端
                    Server.onlineSocketList.remove(accept);
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 把消息发送给所有在线客户端
     * @param msg
     */
    private void sendMsgAll(String msg) {
        for (Socket socket : Server.onlineSocketList) {
            try {
                //1.从客户端socket中获得输出流
                OutputStream outputStream = socket.getOutputStream();
                //2.包装成数据输出流
                DataOutputStream ds = new DataOutputStream(outputStream);
                //3.发送数据
                ds.writeUTF(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

客户端:

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

public class Client {
    public static void main(String[] args) {
        //1.创建Socket对象,同时请求与服务器程序的连接
        try(Socket socket=new Socket("127.0.0.1",8888)){
            //2.从socket通道中获取输出流
            OutputStream outputStream = socket.getOutputStream();
            //3.包装
            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
            Scanner sc=new Scanner(System.in);

            //创建一个独立的线程随时从服务端接受消息
            new ClientReadThread(socket).start();

            //4.输入数据
            while(true){
                String msg=sc.nextLine();
                if("exit".equals(msg)){
                    break;
                }
                dataOutputStream.writeUTF(msg);
                //刷新缓存
                dataOutputStream.flush();
            }
        }catch(Exception e){
            System.out.println("服务端访问失败!");
        }

    }
}
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ClientReadThread extends Thread{
    private Socket socket;
    public ClientReadThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = socket.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    String msg = "客户端:"+ socket.getRemoteSocketAddress()+"客户端断开连接!";
                    System.out.println(msg);
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

5.5 实现简易BS架构

image-20240606203555009

代码举例:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BsServer {
    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                10,
                20,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            while (true) {
                Socket accept = serverSocket.accept();
                threadPoolExecutor.execute(new BsServerReadThread(accept));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketAddress;

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

    @Override
    public void run() {
        SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
        System.out.println("客户端"+remoteSocketAddress+"已连接");
        try {
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(outputStream);
            //输出http协议
            writer.println("HTTP/1.1 200 OK");//响应行
            writer.println("Content-Type:text/html;charset=utf-8");//响应头
            writer.println("<html><head><title>bs</title></head><body><h1>测试内容</h1></body></html>");//响应体
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

posted @ 2024-06-23 13:47  wdadwa  阅读(12)  评论(0)    收藏  举报