Java--网络编程基础

网络编程

网络编程指的是编写跨多个设备(计算机)执行的程序,设备使用网络彼此连接

网络编程中主要解决的问题

  • 如何准确定位网络上一台或多台主机--IP地址
  • 如何定位主机上的特定的应用进程--端口号
  • 如何进行可靠高效的数据传输

J2SE API的java.net包中包含一组类和接口,它们提供低级别的通信详细信息,开发者可编写专注于解决手头问题的程序。java.net包提供对两种常见网络协议的支持

  • TCP - TCP代表传输控制协议,它允许两个应用程序之间的可靠通信。TCP通常用于Internet协议,称为TCP/IP

  • UDP - UDP代表用户数据报协议,这是一种无连接协议,允许在应用程序之间传输数据包


Socket 编程

简介

Socket(套接字)是使用TCP提供通信机制在两台计算机之间进行通信。客户端程序在通信的末尾创建一个套接字,并尝试将套接字连接到服务器。建立连接后,服务器会在通信结束时创建一个套接字对象。客户端和服务器现在可以通过写入和读取套接字进行通信

image-20220505213330285

java.net.Socket类表示一个套接字,java.net.ServerSocket类为服务器程序提供了一种监听客户端并与它们建立连接的机制。使用Socket(套接字)在两台计算机之间建立TCP连接时,会发生以下步骤

  • 服务器实例化ServerSocket对象,表示要在哪个端口号上进行通信

  • 服务器调用ServerSocket类的accept()方法。此方法等待,直到客户端连接到给定端口上的服务器

  • 在服务器等待之后,客户端实例化Socket对象,指定要连接的服务器名称(IP地址)和端口号

  • Socket类的构造函数尝试将客户端连接到指定的服务器和端口号。如果建立了通信,则客户端现在具有能够与服务器通信的Socket对象

  • 在服务器端,accept()方法返回对连接到客户端套接字的服务器上的新套接字的引用

建立连接后,可以使用I/O流进行通信。每个套接字都有一个OutputStream和一个InputStream。客户端的OutputStream连接到服务器的InputStream,客户端的InputStream连接到服务器的OutputStream

TCP是双端通信协议,因此可以同时跨两个流发送数据

image-20220505215024784

Socket通信入门案例

搭建简单的Socket通信入门案例,通过一次性的客户端发送和服务端接收数据,掌握并了解

  • Socket服务端和客户端的基本编程

  • 传输编码统一指定,防止乱码

服务端监听一个端口,等待连接的到来

import java.io.IOException;
 import java.io.InputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 ​
 /**
  * @describe    服务端监听一个端口,等待连接的到来
  */
 public class SocketServer {
 ​
     public static void main(String[] args) throws IOException {
         //服务端监听指定端口
         ServerSocket serverSocket = new ServerSocket(8888);
 ​
         //服务端阻塞等待连接
         System.out.println("服务端阻塞等待连接端口8888……");
         Socket socket = serverSocket.accept();
 ​
         // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
         InputStream inputStream = socket.getInputStream();
         byte[] bytes = new byte[1024];
         int len;
         StringBuilder builder = new StringBuilder();
         while ((len = inputStream.read(bytes)) != -1) {
             //指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
             builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
         }
 ​
         System.out.println("服务端接收到客户端的发来的信息:"+builder.toString());
 ​
         //依次关闭资源
         inputStream.close();
         socket.close();
         serverSocket.close();
     }
 }

客户端指定ip(域名亦可)和端口,连接到指定的服务端,进行数据传输

import java.io.IOException;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 ​
 /**
  * @describe 客户端指定ip和端口,连接到指定的服务端,进行数据传输
  */
 public class SocketClient {
 ​
     public static void main(String[] args) throws IOException {
 ​
         //客户端指定ip(域名亦可)和端口,与服务端建立连接
         Socket socket = new Socket("127.0.0.1", 8888);
 ​
         //获取输出流
         OutputStream outputStream = socket.getOutputStream();
 ​
         //输出数据
         outputStream.write("hello server".getBytes(StandardCharsets.UTF_8));
 ​
         //依次关闭资源
         outputStream.close();
         socket.close();
     }
 }

测试时需要先启动服务端再启动客户端

入门案例优化

双向通信

服务端添加返回响应数据的操作

import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 ​
 /**
  * @describe    服务端监听一个端口,等待连接的到来
  */
 public class SocketServer {
 ​
     public static void main(String[] args) throws IOException {
         //服务端监听指定端口
         ServerSocket serverSocket = new ServerSocket(8888);
 ​
         //服务端阻塞等待连接
         System.out.println("服务端阻塞等待连接端口8888……");
         Socket socket = serverSocket.accept();
 ​
         // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
         InputStream inputStream = socket.getInputStream();
         byte[] bytes = new byte[1024];
         int len;
         StringBuilder builder = new StringBuilder();
         while ((len = inputStream.read(bytes)) != -1) {
             //指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
             builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
         }
 ​
         System.out.println("server get message from client: "+builder.toString());
 ​
         //服务端返回响应的数据
         OutputStream outputStream = socket.getOutputStream();
         outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
 ​
         //依次关闭资源
         outputStream.close();
         inputStream.close();
         socket.close();
         serverSocket.close();
     }
 }

客户端添加接收服务端返回数据的操作

import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 ​
 /**
  * @describe 客户端指定ip和端口,连接到指定的服务端,进行数据传输
  */
 public class SocketClient {
 ​
     public static void main(String[] args) throws IOException {
 ​
         //客户端指定ip(域名亦可)和端口,与服务端建立连接
         Socket socket = new Socket("127.0.0.1", 8888);
 ​
         //获取输出流
         OutputStream outputStream = socket.getOutputStream();
 ​
         //输出数据
         outputStream.write("hello server".getBytes(StandardCharsets.UTF_8));
 ​
         //通过shutdownOutput告诉服务端,客户端已发送完数据,后续只能接受数据
         socket.shutdownOutput();
 ​
         //接收服务端返回的数据
         InputStream inputStream = socket.getInputStream();
         byte[] bytes = new byte[1024];
         int len;
         StringBuilder builder = new StringBuilder();
         while ((len = inputStream.read(bytes)) != -1) {
             //指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
             builder.append(new String(bytes,0,len,StandardCharsets.UTF_8));
         }
 ​
         System.out.println("client get message from server: " + builder.toString());
 ​
 ​
         //依次关闭资源
         inputStream.close();
         outputStream.close();
         socket.close();
     }
 }

服务端并发处理

在上面的例子中,服务端仅仅只是接受了一个Socket请求,并处理了它,然后就结束了,但是在实际开发中,一个Socket服务往往需要服务大量的Socket请求

循环接受请求并处理 -- 不建议

缺点:循环处理多个Socket请求,不过当一个请求的处理比较耗时的时候,后面的请求将被阻塞

import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 ​
 /**
  * @describe    服务端监听一个端口,等待连接的到来
  */
 public class SocketServer {
 ​
     public static void main(String[] args) throws IOException {
         //服务端监听指定端口
         ServerSocket serverSocket = new ServerSocket(8888);
 ​
         //服务端阻塞等待连接
         System.out.println("服务端阻塞等待连接端口8888……");
 ​
         while (true) {
             Socket socket = serverSocket.accept();
             // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
             InputStream inputStream = socket.getInputStream();
             byte[] bytes = new byte[1024];
             int len;
             StringBuilder builder = new StringBuilder();
             while ((len = inputStream.read(bytes)) != -1) {
                 //指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
                 builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
             }
 ​
             System.out.println("server get message from client: "+builder.toString());
 ​
             //服务端返回响应的数据
             OutputStream outputStream = socket.getOutputStream();
             outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
 ​
             //依次关闭资源
             outputStream.close();
             inputStream.close();
             socket.close();
         }
     }
 }
线程池接收请求并处理 -- 推荐

用多线程的方式来处理Socket,即每有一个Socket请求的时候,就创建一个线程来处理它。不过在实际生产中,创建的线程会交给线程池来处理,为了

  • 线程复用,创建线程耗时,回收线程慢

  • 防止短时间内高并发,指定线程池大小,超过数量将等待,方式短时间创建大量线程导致资源耗尽,服务挂掉

import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 ​
 /**
  * @describe 服务端监听一个端口,等待连接的到来
  */
 public class SocketServer {
 ​
     public static void main(String[] args) throws IOException {
         //服务端监听指定端口
         ServerSocket serverSocket = new ServerSocket(8888);
         //设置一个读取的超时时间,当超过指定的时间后,还没有读到数据,就假定这个连接无用
         //然后抛异常,捕获异常后关闭连接就可以了
         serverSocket.setSoTimeout(10000);
 ​
         //服务端阻塞等待连接
         System.out.println("服务端阻塞等待连接端口8888……");
 ​
         //如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源
         ExecutorService threadPool = Executors.newFixedThreadPool(100);
 ​
 ​
         while (true) {
             Socket socket = serverSocket.accept();
 ​
             Runnable runnable = () -> {
 ​
                 InputStream inputStream = null;
                 OutputStream outputStream = null;
 ​
                 try {
                     // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
                     inputStream = socket.getInputStream();
                     byte[] bytes = new byte[1024];
                     int len;
                     StringBuilder builder = new StringBuilder();
                     while ((len = inputStream.read(bytes)) != -1) {
                         //指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
                         builder.append(new String(bytes, 0, len, StandardCharsets.UTF_8));
                     }
 ​
                     System.out.println(String.format("server get message from client: %s,服务处理线程: %s",builder.toString(),Thread.currentThread().getName()));
 ​
                     //服务端返回响应的数据
                     outputStream = socket.getOutputStream();
                     outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
                 } catch (Exception e) {
                     e.printStackTrace();
                 } finally {
                     //依次关闭资源
                     release(inputStream,outputStream,socket);
                 }
             };
 ​
             threadPool.execute(runnable);
         }
     }
 ​
     /**
      * 释放资源
      * @param inputStream
      * @param outputStream
      * @param socket
      */
     public static void release(InputStream inputStream,OutputStream outputStream,Socket socket){
 ​
         try {
             if (inputStream != null) {
                 outputStream.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
 ​
         try {
             if (outputStream != null) {
                 outputStream.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
 ​
         try {
             if (socket != null) {
                 socket.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }

URL网络编程

URL(Uniform Resourse Locator):统一资源定位符,表示Internet上某一个资源的地址

  • 是一种具体的URI,即URL可以用来表示一个资源,而且指明了如何定位这个资源
  • 通过URL可以访问internet上各种网络资源,比如www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或者其他资源

URL基本结构

  • <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
    • 例如:http://192.168.1.20:8080/wen/index.html#a?username=wen&password=123456
    • #片段名即锚点,例如电子书的章节
    • 参数列表格式:参数名=参数值&参数名=参数值……

URL应用实例:从网络中下载某个资源保存在本地

import org.junit.Test;

import java.io.*;
import java.net.*;
import java.util.Objects;

public class DownloadTest {

    @Test
    public void download(){

        InputStream inputStream = null;
        FileOutputStream outputStream = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL("https://www.keaidian.com/uploads/allimg/190424/24110307_0.jpg");
            connection = (HttpURLConnection) url.openConnection();

            inputStream = connection.getInputStream();
            outputStream = new FileOutputStream("24110307_1.jpg");

            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
                outputStream.write(buffer,0,len);
            }
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (Objects.nonNull(outputStream)) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (Objects.nonNull(inputStream)) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (Objects.nonNull(connection)) {
                connection.disconnect();
            }
        }
    }
}
posted @ 2022-08-20 16:03  伊文小哥  阅读(27)  评论(0编辑  收藏  举报