Java高级--Socket网络编程

Socket

什么是TCP/IP

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

TCP/IP协议族包括运输层、网络层、链路层

Socket是什么

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

计算机网络

架构模式:B/S Browser/Server 浏览器/服务器模式,节约开发成本,减少使用难度

           C/S    Client/Server  客户端/服务器模式,用户学习成本更高,但功能更强大,不受限于浏览器

OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

网络编程三要素

协议

计算机网络通信必须遵守的规则

IP地址

俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

端口号

网络的通信,本质上是两个进程(应用程序)的通信。

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

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

利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

五层模型:

  • 应用层 SMTP HTTP HTTPS
  • 传输层 TCP(可靠传输,三次握手,四次握手) UDP 端口号
  • 网络层 IP协议 寻址
  • 数据链路层
  • 物理层

DNS域名解析

1.浏览器会先看自己有没有存域名对应的ip

2.如果找不到,去DNS服务器查询,DNS服务器会返回给浏览器后,浏览器会缓存对应IP地址

3.浏览器发送请求到对应IP的服务器

网址的组成

https://www.baidu.com/?tn=54093922_1_hao_pg

协议类型 域名 端口号(https默认是443 http默认是80/8080) 参数

TCP通信程序

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。

两端通信时步骤:

  1. 服务端程序,需要事先启动,等待客户端的连接。
  2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
image.png

在Java中,提供了两个类用于实现TCP通信程序:

  1. 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
  2. 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

Socket类

实现客户端套接字,套接字指的是两台设备之间通讯的端点。

构造方法

  • public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
Socket client = new Socket("127.0.0.1", 6666);

String host:服务器主机的名称/服务器的IP地址

成员方法

  • public InputStream getInputStream() : 返回此套接字的输入流。
    • 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
    • 关闭生成的InputStream也将关闭相关的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的输出流。
    • 如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
    • 关闭生成的OutputStream也将关闭相关的Socket。
  • public void close() :关闭此套接字。
    • 一旦一个socket被关闭,它不可再使用。
    • 关闭此socket也将关闭相关的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的输出流。
    • 任何先前写出的数据将被发送,随后终止输出流。

实现步骤

    1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号

    2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象

    3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据

    4.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream

    5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据

    6.释放资源(Socket)

注意:

    1.客户端和服务器端进行交互必须使用Socket中提供的网络流.不能使用自己创建的流对象

    2.当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立链接通路

        这时如果服务器没有启动,就会抛出异常.

        如果服务器已经启动,就可以进行交互了

ServerSocket类

实现了服务器套接字,该对象等待通过网络的请求。

构造方法

  • public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
ServerSocket server = new ServerSocket(6666);

成员方法

  • public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接

TCP通信分析图解

  1. 【服务端】启动,创建ServerSocket对象,等待连接。
  2. 【客户端】启动,创建Socket对象,请求连接。
  3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
  4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
  5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
image.png

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 1.创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3.通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 4.一次性读取数据
        // 4.1 创建字节数组
        byte[] b = new byte[1024];
        // 4.2 据读取到字节数组中.
        int len = is.read(b);
        // 4.3 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5.关闭资源.
        is.close();
        server.close();
    }
}

客户端向服务器端发送数据:

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 1.创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3.通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 4.一次性读取数据
        // 4.1 创建字节数组
        byte[] b = new byte[1024];
        // 4.2 据读取到字节数组中.
        int len = is.read(b);
        // 4.3 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5.关闭资源.
        is.close();
        server.close();
    }
}

客户端实现:

public class ClientTCP {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端 发送数据");
        // 1.创建 Socket ( ip , port ) , 确定连接到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2.获取流对象 . 输出流
        OutputStream os = client.getOutputStream();
        // 3.写出数据.
        os.write("你好么? tcp ,我来了".getBytes());
        // 4. 关闭资源 .
        os.close();
        client.close();
    }
}

服务器向客户端回写数据

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 1.创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3.通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 4.一次性读取数据
        // 4.1 创建字节数组
        byte[] b = new byte[1024];
        // 4.2 据读取到字节数组中.
        int len = is.read(b);
        // 4.3 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        // =================回写数据=======================
        // 5. 通过 socket 获取输出流
         OutputStream out = server.getOutputStream();
        // 6. 回写数据
         out.write("我很好,谢谢你".getBytes());
        // 7.关闭资源.
        out.close();
        is.close();
        server.close();
    }
}

客户端实现:

public class ClientTCP {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端 发送数据");
        // 1.创建 Socket ( ip , port ) , 确定连接到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2.通过Scoket,获取输出流对象 
        OutputStream os = client.getOutputStream();
        // 3.写出数据.
        os.write("你好么? tcp ,我来了".getBytes());
        // ==============解析回写=========================
        // 4. 通过Scoket,获取 输入流对象
        InputStream in = client.getInputStream();
        // 5. 读取数据数据
        byte[] b = new byte[100];
        int len = in.read(b);
        System.out.println(new String(b, 0, len));
        // 6. 关闭资源 .
        in.close();
        os.close();
        client.close();
    }
}

使用telnet发送请求

win + R 输入 cmd 打开 dos 窗口

telnet time-A.timefreq.bldrdoc.gov 13

使用socket发送请求

public static void main(String[] args) {
       //链接服务  加在try里省去关闭
   try(Socket socket = new Socket("time-A.timefreq.bldrdoc.gov",13)){
       //获取输入流
       InputStream inputStream= socket.getInputStream();
       //用Scanner包装
       try (Scanner scanner = new Scanner(inputStream)){
           //判断有没有下一行
           while(scanner.hasNextLine()){
               System.out.println(scanner.nextLine());
           }
       }
   } catch (UnknownHostException e) {
       e.printStackTrace();
   } catch (IOException e) {
       e.printStackTrace();
   }
}

使用socket作为服务端接收请求

    public static void main(String[] args) {
        //创建服务,给指定一个端口号
        try(ServerSocket serverSocket = new ServerSocket(8189)){
            //接收请求 获取输入 获取输出
            try (Socket socket = serverSocket.accept()){
                InputStream inputStream =socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream();
                PrintWriter printWriter = new PrintWriter(outputStream,true);
                printWriter.println("Client say:");
                try(Scanner scanner = new Scanner(inputStream)){
                    while(scanner.hasNextLine()){
                        //获取请求数据 输出
                        String inputStr = scanner.nextLine();
                         System.out.println("Client say:"+inputStr);
                        //给一个响应

                        //服务端从控制台输入信息
                        System.out.print("Server say:");
                        try(Scanner serverScanner = new Scanner(System.in)){
                            if(serverScanner.hasNextLine()){
                                String serverSayStr =serverScanner.nextLine();
                                //将服务器输入的信息打印给客户端
                                printWriter.println("Server say:");
                                printWriter.println(serverSayStr);
                            }
                        }
                        printWriter.println("Client say:");
                    }
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

URL

统一资源定位符:定位互联网上的某个资源

协议://ip地址:端口/项目名/资源
    public static void main(String[] args) throws MalformedURLException {
        URL url =new URL("http://localhost:8080/helloworld/index.jsp?username=tsy&password=123");
        url.getProtocol();//协议
        url.getHost();    //主机ip
        url.getPort();    //端口
        url.getPath();    //文件
        url.getFile();    //全路径
        url.getQuery();   //参数
    }

用URL类下载图片

public static void main(String[] args) throws IOException {
        //下载地址
        URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201908%2F28%2F20190828104530_msrvw.thumb.400_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1630136449&t=11d18f4911382cc71de58ec0ec9c8123");
        //链接到这个资源 http
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

        InputStream inputStream = urlConnection.getInputStream();

        FileOutputStream fos = new FileOutputStream("Picture.jpg");

        byte[] buffer = new byte[1024];
        int len;
        while ((len=inputStream.read(buffer))!=-1){
            fos.write(buffer,0,len);//写出数据
        }
        fos.close();
        inputStream.close();
        urlConnection.disconnect();//断开链接
    }
}

注解

注解就是放在java源码的类,方法,字段,参数前的一种特殊的“注释”

分类

  • 第一类编译器使用,不会进入.class文件,编译后被编译器扔掉

    @Override 让编译器检查该方法是否正确的实现覆盖 @SuppressWarnings告诉编译器忽略此处代码产生的警告

  • 第二类由工具处理.class 文件使用的注解,一般被底层使用,不必我们处理

    比如有些工具在加载classs时,对class做动态修改

  • 第三类程序运行期能够读取的注解,加载后一直在JVM中,也是最常用的注解

    例如@PostConstruct在调用构造方法后被自动调用,这是java代码读取该注解实现的哦功能,JVM并不会识别该注解

参数

注解可以由参数,最常用的是value,参数可以有默认值;如果只有一个参数value可以省略名称

@Check(99) -->等同于 @Check(value=99)

定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

元注解:修饰注解的注解,常用的有@Target @Retention

@Target 表示该注解可以放在什么位置

  • 类或接口:ElementType.TYPE

  • 字段:ElementType.FIELD

  • 方法:ElementType.METHOD

  • 构造方法:ElementType.CONSTRUCTOR

  • 方法参数:ElementType.PARAMETER

    @Target({
        ElementType.METHOD,
        ElementType.FIELD
    })   //可以加多个
    public @interface Report {
        ...
    }
    

@Retention 表示该注解的声明周期

  • 仅编译期:RetentionPolicy.SOURCE

  • 仅class文件:RetentionPolicy.CLASS

  • 运行期:RetentionPolicy.RUNTIME

    通常我们自定义的都是 RUNTIME ,所以要加上@Retention(RetentionPolicy.RUNTIME)

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Report {
     int type() default 0;
     String level() default "info";
     String value() default "";
    }
    

@Repeatable 表示注解是否可以重复

@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}

@Inherited 表示注解是否可以集成父类定义的 注解

处理注解

判断注解是否存

Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)

读取注解

Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)

使用

// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

读取方法参数的注解稍显复杂,因为参数是要给数组,每个参数又可以修饰多个注解,所以最终是一个二维数组

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容