网络编程---网络编程入门、UDP通信程序、TCP通信程序

1.网络编程入门

1.网络编程概述

网络编程:

在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据传输

计算机网络:

是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

2. 网络编程三要素

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

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

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

3.IP

IP:全程"互联网协议地址",也称IP地址。是分配给上网设备的数字标签。常见的IP分为:ipv4和ipv6

ipv4:

ipv6:

由于互联网的蓬勃发展, IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节-组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题

特殊情况:如果计算出的16进制表示形式中间有多个连续的0

常用命令

ipconfig: 查看本机P地址

ping IP地址:检查网络是否连通

特殊IP地址127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用

4.InetAddress的使用

为了方便我们对IP地址的获取和操作, Java提供了一个类InetAddress供我们使用

InetAddress:此类表示Internet协议(IP) 地址

方法名 说明
static InetAddress getByName(String host) 确定主机名称的IP地址。 主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
java 复制代码
package com.socketdemo1;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetadressDemo1 {
    public static void main(String[] args) throws UnknownHostException {
        //1,static InetAddress getByName(String host) 	确定主机名称的IP地址。 主机名称可以是机器名称,也可以是IP地址
//        InetAddress address = InetAddress.getByName("YM");
        InetAddress address = InetAddress.getByName("LJB");
        System.out.println(address); //LJB/192.168.56.1

        //2,String getHostName() 	获取此IP地址的主机名
        String hostName = address.getHostName();
        System.out.println("主机名为"+hostName); //LJB

        //3,String getHostAddress() 	返回文本显示中的IP地址字符串
        String ip = address.getHostAddress();
        System.out.println("IP为"+ip); //192.168.56.1
    }
}

5.端口

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

端口号 :用两个字节表示的整数,它的取值范围是0~65535。其中, 0~ 1023之间的端口号用于些知名的网络服务和应用,我们自己使用1024以上的端口号就好了。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

注意:一个端口号只能被一个应用程序使用

6.协议

协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议

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

UDP是面向无连接通信协议, 即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外-台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据

速度快,有大小现在一次最多发送64k,数据不安全,易丢失数据

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频视频和普通数据的传输

例如视频会议通常采用UDP协议, 因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议

传输控制协议 (Transmission Control Protocol)

TCP协议是面向连接 的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过"三次握手"

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

三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

第一次握手,客户端向服务器端发出连接请求,等待服务器确认

第二次握手,服务器端向客户端回送一个响应, 通知客户端收到了连接请求

第三次握手,客户端再次向服务器端发送确认信息,确认连接

2.UDP通信程序

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象, 但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念

Java提供了DatagramSocket类作为基于UDP协议的Socket

UDP发送数据

发送数据的步骤

创建发送端的Socket对象(DatagramSocket):DatagramSocket()

创建数据, 并把数据打包:DatogramPacket (byte[] buf, int Length, InetAddress address, int port)

调用DatagramSocket对象的方法发送数据:void send(DatagramPacket p)

关闭发送端:void close()

java 复制代码
package com.socketdemo2;

import java.io.IOException;
import java.net.*;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1、创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();

        //2、创建数据, 并把数据打包
        //DatagramPacket (byte[] buf, int Length, InetAddress address, int port)
        // 构造一个数据包,发送长度为length的数据 包到指定主机上的指定端口号。
        byte[] bys = "hello,java".getBytes();
        DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("127.0.0.1"),10000);

        //3、调用DatagramSocket对象的方法发送数据
        //void send(DatagramPacket p)
        ds.send(dp);

        //4、关闭发送端
        ds.close();
    }
}

UDP接收数据

接收数据的步骤

创建接收端的Socket对象(DatagramSocket):DatagramSocket(int port)

创建一个数据包, 用于接收数据:DatagramPacket(byte[] buf, int length)

调用DatagramSocket对象的方法接收数据:void receive(DatagramPacketp)

解析数据包, 并把数据在控制台显示:byte[] getData()、int getLength()

关闭接收端:void close()

java 复制代码
package com.socketdemo2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1、创建接收端的Socket对象(DatagramSocket):DatagramSocket(int port)
        DatagramSocket ds = new DatagramSocket(10000);

        //2、创建一个数据包, 用于接收数据
        //DatagramPacket(byte[] buf, int length):构造一个DatagramPacket用来接收长度为length的数据包
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);

        //3、调用DatagramSocket对象的方法接收数据:void receive(DatagramPacket)
        ds.receive(dp);

        //4、解析数据包, 并把数据在控制台显示:
        // byte[] getData():返回数据缓冲区 int getLength()
        byte[] data = dp.getData();
        //int getLength():返回要发送的数据长度或接收的数据长度
        int length = dp.getLength();
        String dataString = new String(data,0,length);
        System.out.println("数据是:"+dataString);

        //5、关闭接收端:void close()
        ds.close();
    }
}

练习

java 复制代码
package com.socketdemo3;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1、创建发送端的Socket对象(DatagramSocket):DatagramSocket()
        DatagramSocket ds = new DatagramSocket();

        //键盘录入数据
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();

        while (true) {
            if ("886".equals(s)) {
                break;
            } else {
                //创建数据并把数据打包
                byte[] bytes = s.getBytes();
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 10000);

                //3、调用DatagramSocket对象的方法发送数据:void send(DatagramPacket p)
                ds.send(dp);
            }
        }
        ds.close();
    }
}
java 复制代码
package com.socketdemo3;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket对象(DatagramSocket):DatagramSocket(int port)
        DatagramSocket ds = new DatagramSocket(10000);

        while(true) {
            //创建一个数据包接收数据
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);

            //调用DatagramSocket对象的方法接收数据:void receive(DatagramPacket)

            ds.receive(dp);

            //解析数据包, 并把数据在控制台显示:byte[] getData()、int getLength()
            System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
        }

        //关闭
//        ds.close();
    }
}

UDP的三种通信方式

单播

组播

广播

组播代码实现

组播地址:224.0.0.0~239.255.255.255

其中224.0.0.0~224.0.0.255为预留的组播地址

单播的发送端:

1.创建发送端的Socket对象(Datagram Socket)

2.创建数据,并把数据打包(DatagramPacket)

3.调用DatagramSocket对象的方法发送数据

4.释放资源

注意:

在单播中,这里是发给指定的IP的电脑

但是在组播当中,这里是发给组播地址

java 复制代码
package com.socketdemo4;

import java.io.IOException;
import java.net.*;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10001;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ds.send(dp);

        ds.close();
    }
}
java 复制代码
package com.socketdemo4;

import java.io.IOException;
import java.net.*;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10001;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ds.send(dp);

        ds.close();
    }
}

广播代码实现

单播的发送端

1.创建发送端的Socket对象(Datagram Socket)

2.创建数据,并把数据打包(DatagramPacket)

3.调用DatagramSocket对象的方法发送数据

4.释放资源

注意:

在单播中,这里是发给指定的IP的电脑

但是在广播当中,这里是发给广播地址

广播地址:255.255.255.255

java 复制代码
package com.socketdemo5;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        String s = "hello 广播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("255.255.255.255");
        int port = 10001;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ds.send(dp);

        ds.close();
    }
}
java 复制代码
package com.socketdemo5;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket(10001);

        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);

        ms.receive(dp);

        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));

        ms.close();
    }
}

3.TCP通信程序

1.TCP通信原理

TCP通信协议是一种可靠的网络协议, 它在通信的两端名建立一个Socke对象, 从而在通信的两端形成网络虚拟链路

一旦建立了 虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信

Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生l0流来进行网络通信

Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

2.TCP发送数据

1.创建客户端的Socket对象(Socket):Socket(String host, int port)

2.获取输出流,写数据:OutputStream对象的getOutputStream()方法

3.释放资源:void close()

java 复制代码
package com.socketdemo6;

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

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1、创建客户端的Socket对象(Socket):Socket(String host, int port)
        Socket socket = new Socket("127.0.0.1",10000);

        //2、获取输出流,写数据:OutputStream对象的getOutputStream()方法
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());

        //3、释放资源:void close()
        socket.close();
    }
}

3.TCP接收数据

1.创建服务 器端的Socket对象(ServerSocket):ServerSocket(int port)

2.监听客户端连接, 返回个Socket对象:Socket accept()

3.获取输入流, 读数据,并把数据显示在控制台:InputStream对象的getInputStream()方法

4.释放资源:void close()

java 复制代码
package com.socketdemo6;

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

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1、创建服务 器端的Socket对象(ServerSocket):ServerSocket(int port)
        ServerSocket ss = new ServerSocket(10001);

        //2、监听客户端连接, 返回个Socket对象:Socket accept()
        System.out.println(111);
        Socket accept = ss.accept();
        System.out.println(222);

        //3、获取输入流, 读数据,并把数据显示在控制台:InputStream对象的getInputStream()方法
        InputStream is = accept.getInputStream();
        int b;
        while ((b = is.read()) != -1){
            System.out.print((char) b);
        }

        //4、释放资源:void close()
        is.close();
        ss.close();
    }
}

4.原理分析

1.accept方法是阻塞的,作用就是等待客户端连接

2.客户端创建对象并连接服务器,此时是通过三次握手协议保证跟服务器之间的连接

3.针对客户端来讲,是往外写的,所以是输出流,针对服务器来讲,是往里读的,所以是输入流

4.read方法也是阻塞的

5.在关流的时候,还多了一个往服务器写结束标记的动作

6.最后一步断开连接,通过四次挥手协议保证连接终止

5.三次握手

6.四次挥手

7.TCP通信程序练习

案例需求:

客户端:发送数据,接受服务器反馈

服务器:收到消息后给出反馈

步骤:

客户端创建对象,使用输出流输出数据

服务端创建对象,使用输入流接受数据

服务端使用输出流给出反馈数据

客户端使用输入流接受反馈数据

java 复制代码
package com.socketdemo7;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端的Socket对象(Socket)
        Socket s = new Socket("127.0.0.1", 10001);

        //获取输出流,写数据
        OutputStream os = s.getOutputStream();
        os.write("hello".getBytes());

        //接收服务器反馈
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys, 0, len);
        System.out.println("客户端:" + data);

        //释放资源
        is.close();
        os.close();
        s.close();
    }
}
java 复制代码
package com.socketdemo7;

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

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建服务器端的Socket对象(ServerSocket)
        ServerSocket ss = new ServerSocket(10001);

        //监听客户端连接,返回一个Socket对象
        Socket accept = ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = accept.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys, 0, len);
        System.out.println("服务器:" + data);

        //给出反馈
        OutputStream os = accept.getOutputStream();
        os.write("数据已经收到".getBytes());

        //释放资源
        accept.close();
        is.close();
        os.close();
        ss.close();
    }
}

8.练习二

客户端:将本地文件上传到服务器。接收服务器的反馈

服务器:接收客户端上传的文件,上传完毕之后给出反馈

java 复制代码
package com.socketdemo8;

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

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端Socket对象
        Socket socket = new Socket("127.0.0.1", 10002);

        //是本地的流,读取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("lib\\OIP-C (4).jpg"));
        //写到服务器--网络中的流
        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);

        int b;
        while ((b = bis.read())!=-1){
            bos.write(b);//通过网络写到服务器中
        }

        //给服务器一个结束标记,告诉服务器文件已经传输完毕
        socket.shutdownOutput();

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line = br.readLine()) != null){
            System.out.println(line);
        }
        bis.close();
        socket.close();

    }
}
java 复制代码
package com.socketdemo8;

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

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10002);
        Socket accept = ss.accept();

        //网络中的流,从客户端读取数据的
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        //本地的IO流,把数据写到本地,实现永久话存储
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("ServerDir\\copy.jpg"));

        int b;
        while ((b = bis.read()) != -1){
            bos.write(b);
        }
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        bos.close();
        accept.close();
        ss.close();
    }

}

9.服务端优化

第一个弊端:

服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了

改进方式:

循环

第二个弊端:

第二次上传文件的时候,会把第一次的文件给覆盖

改进方式:

UUID.randomUUID()方法生成随机的文件名

java 复制代码
package com.socketdemo8;

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

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10002);
        while (true) {
            Socket accept = ss.accept();

            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //本地的IO流,把数据写到本地,实现永久话存储
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("ServerDir\\" + UUID.randomUUID().toString()+".jpg"));

            int b;
            while ((b = bis.read()) != -1){
                bos.write(b);
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();

            bos.close();
            accept.close();
        }
        //ss.close();
    }

}

加入循环以后又引发了一个问题:

使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信

改进方式:

开启多线程处理

新创建一个类ThreadSocket

java 复制代码
package com.socketdemo8;

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

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10002);
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
                    new Thread(ts).start();
        }
        //ss.close();
    }

}
java 复制代码
package com.socketdemo8;

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

public class ThreadSocket implements Runnable{
    private Socket acceptSocket;
    public ThreadSocket(Socket accept) {
        this.acceptSocket = accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos = null;
        try {
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            //本地的IO流,把数据写到本地,实现永久话存储
            bos = new BufferedOutputStream(new FileOutputStream("ServerDir\\" + UUID.randomUUID().toString()+".jpg"));

            int b;
            while ((b = bis.read()) != -1){
                bos.write(b);
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();


        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

加入多线程又引发了一个问题:

使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大

解决方案:

加入线程池

java 复制代码
package com.socketdemo8;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10002);
        ThreadPoolExecutor pool =new ThreadPoolExecutor(
                3,      //核心线程数量
                10,     //线程池的总数量
                60,     //临时线程空闲时间
                TimeUnit.SECONDS,    //临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),    //阻塞队列
                Executors.defaultThreadFactory(),       //创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()    //任务拒绝策略
        );
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
                    //new Thread(ts).start();
            pool.submit(ts);
        }
        //ss.close();
    }

}
相关推荐
SilentSamsara28 分钟前
Python 环境搭建完整指南:从下载安装到运行第一个程序
开发语言·python
曹牧37 分钟前
Spring:@RequestMapping注解,匹配的顺序与上下文无关
java·后端·spring
daixin884839 分钟前
cursor无法正常使用gpt5.5等模型解决方案
java·redis·cursor
小短腿的代码世界41 分钟前
Qt文件系统与IO深度解析:从QFile到异步文件操作
开发语言·qt
AnalogElectronic1 小时前
linux 测试网络和端口是否连通的命令详解
linux·网络·php
韦禾水2 小时前
记录一次项目部署到tomcat的异常
java·tomcat
曦月合一2 小时前
树莓派安装jdk、tomcat、vnc、谷歌浏览器开机自启等环境配置
java·tomcat·树莓派
harder3212 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo2 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社2 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust