Java网络编程之Socket---TCP(一)

1  网络编程的理解

  • 网络编程是指运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

  • 网络编程从大的方面来说就是对信息的发送和接收;

  • 通过操作相应Api调度计算机硬件资源,并利用传输管道(网线)进行数据交换的过程;

  • 更多涉及:网络模型、套接字、数据包。

2  OSI网络模型

应用程、表示层、会话层、传输层、网络层、数据链路层、物理层。

  • 基础层:物理层、数据链路层、网络层;

  • 传输层:TCP-UDP协议层、Socket;

  • 高级层:会话层、表示层、应用层。

3  java.net包提供的两种网络常见协议

        1. TCP:TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP层位于IP层之上,应用层之下的中间层。

  • TCP是面向连接的通信协议;
  • 通过三次握手建立连接,通讯完成时关闭连接;
  • 由于TCP是面向连接的所以只能用于端到端的通讯。

        2. UDP:UDP(User Datagram Protocol,用户数据报协议),位于OSI模型的传输层。一个无连接的协议。提供了应用程序之间要发送的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

  • UDP是面向无连接的通讯协议;
  • UDP数据包括目的的端口号和源端口号信息;
  • 由于通讯不需要连接,所以可以实现广播发送,不局限于端到端。

4  Socket编程

4.1  Socket含义

  • 简单来说是IP地址与端口的结合协议(RFC739);

    扫描二维码关注公众号,回复: 14931843 查看本文章
  • 一种地址与端口的结合描述协议;

  • TCP/IP协议的相关Api总称,是网络Api的集合实现;

  • 涵盖了:Stream Socket(流)和Datagram Socket(数据报)。

4.2  Socket的作用

  • Socket在网络传输中用于唯一标示两个端点之间的连接;

  • 端点:包括IP+Port; ip+port=socket

  • 4个要素:客户端地址、客户端端口、服务器地址、服务器端口。

4.3  Socket编程的理解

        套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

        当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

        java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

        以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。

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

  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。

  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。

  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。

        连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

        TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。

4.4  Socket类的方法

        java.net.Socket类代表客户端和服务端用来互相沟通的套接字。客户端要获得一个Socket对象通过实例化,服务端获得一个Socket对象通过accept()方法的返回值。

  • 构造方法
方法 描述
public Socket(String host, int port) throws UnknownHostException, IOException 创建一个流套接字并将其连接到指定的主机上的指定端口
public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定IP的指定端口
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException 创建一个流套接字并将其连接到指定的远程主机上的指定远程端口
public Socket() 创建未连接的套接字
  • Socket类常用方法
方法 描述
public void connect(SocketAddress host, int timeout) throws IOExceptio 将此套接字连接到指定主机,并指定超时时间
public InetAddress getInetAddress() 返回套接字连接的地址
public int getPort() 返回此套接字连接到的远程端口
public int getLocalPort() 返回此套接字连接的本地端口
public SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点,若无返回null
public InputStream getInputStream() throws IOException 返回此套接字输入流
public OutputStream getOutputStream() throws IOException 返回此套接字输出流
public void close() throws IOException 关闭此套接字
  •  InetAddress类的方法

此类表示互联网协议(IP)地址。Socket编程常用方法,如下:

方法 描述
static InetAddress getByAddress(byte[] addr) 给定原始IP地址,返回InetAddress对象
static InetAddress getByAddress(String host, byte[] addr) 根据主机名和IP地址创建InetAddress对象
static InetAddress getByName(String host) 根据主机名确定主机的IP地址
String getHostAddress() 返回字符串类型的IP地址
String getHostName() 获取此IP地址的主机名
static InetAddress getLocalHost() 返回本地主机
String toString() 将IP地址转化为String

 4.5  ServerSocket类的方法

        服务器应用程序通过使用java.net.ServerSocket类以获取一个端口,侦听等待客户端连接。

  • 构造方法
方法 描述
public ServerSocket(int port) throws IOException 创建绑定到指定端口的服务器套接字
public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
public ServerSocket(int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听backlog和要绑定到的本地IP地址创建服务器
public ServerSocket() throws IOException 创建不绑定服务器的套接字
  •  ServerSocket类常用方法
方法 描述
public int getLocalPort() 返回此套接字在其上侦听的端口
public Socket accept() throws IOException 侦听并等待套接字的连接
public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
public void bind(SocketAddress host, int backlog) 将ServerSocket绑定到指定的地址(ip+port)

4.6  示例

4.6.1  Socket服务端

        Socket相关接口在java.net包中已经存在,所以这里不需要再做额外的引用。

        1. SocketServer.java(Socket服务端核心)

/**
 * socket服务端
 * @author W
 * @createDate 2022/7/14
 * @description:
 */
@Data
@Component
public class SocketServer {
    final static Logger log = LoggerFactory.getLogger(SocketServer .class);
    @Value("${socket.port}")
    private Integer port;
    private boolean started;
    private ServerSocket serverSocket;
    private ExecutorService executorService = Executors.newCachedThreadPool();

    public void start(Integer port){
        log.info("port: {}, {}", this.port, port);
        try {
            serverSocket =  new ServerSocket(port == null ? this.port : port);
            started = true;
            log.info("Socket服务已启动,占用端口: {}", serverSocket.getLocalPort());
        } catch (IOException e) {
            log.error("端口冲突,异常信息:{}", e);
            System.exit(0);
        }

        try {
            while (started) {
                // 接收并等待客户端连接,返回客户端套接字
                Socket socket = serverSocket.accept();
                // 维持连接-长连接
                socket.setKeepAlive(true);
                ClientSocketConnect socketConnect = new ClientSocketConnect(socket);
                Thread thread = new Thread(socketConnect);
                executorService.execute(thread);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

         2. ClientSocketConnect.java(自定义连接客户端Socket类)

/**
 * @author W
 * @createDate 2022/7/14
 * @description: 自定义封装连接客户端
 */
@Slf4j
@Data
public class ClientSocketConnect extends AbstractConnection implements Runnable{
    private Socket socket = null;
    private BufferedReader in = null;
    private BufferedWriter out = null;
    private DataInputStream inputStream = null;
    // socket输出流
    private DataOutputStream outputStream = null;
    private String key;
    private String message;

    public ClientSocketConnect(){

    }

    public ClientSocketConnect(Socket socket){
        this.socket = socket;
        try {
            in = new BufferedReader(new InputStreamReader(socket
                    .getInputStream(), "UTF-8"));
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
            inputStream = new DataInputStream(socket.getInputStream());
            outputStream = new DataOutputStream(socket.getOutputStream());
            clientIp = socket.getInetAddress().getHostAddress();
        } catch (IOException e) {
            System.out.println("客户端连接异常");
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //每5秒进行一次客户端连接,判断是否需要释放资源
        while (true){
            try {
                TimeUnit.SECONDS.sleep(5);
                if (isSocketClosed()){
                    log.info("客户端已关闭,其Key值为:{}", this.getKey());
                    //关闭对应的服务端资源
                    close();
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
   
    /**
     * 服务端发送消息
     * 向指定客户端发送信息
     * @param message
     */
    @Override
    public void sendMessage(String message){
        System.out.println("服务端发送消息==:"+message);
        try {
            out.write(message);
            out.newLine();
            out.flush();
        } catch (Exception e) {
            System.out.println("发送信息异常:{}");
            close();
        }
    }

    /**
     * 服务端接收消息
     * 获取指定客户端的上传信息
     * @return
     */
    @Override
    public String receiveMessage(){
        try {
            String msg = in.readLine();
            return msg;
        } catch (IOException e) {
            e.printStackTrace();
            close();
        }
        return null;
    }

    /**
     * 指定Socket资源回收
     */
    @Override
    public void close(){
        log.info("进行资源回收"+clientIp);
        IOUtils.closeQuietly(in);
        IOUtils.closeQuietly(out);
        try {
            this.socket.close();
        } catch (Exception e) {
            log.warn("close socket get excption:", e);
        }
        log.info("ClientSocketConnect----->close 资源回收成功");
    }

    /**
     * 发送数据包,判断数据连接状态
     * @return
     */
    @Override
    public boolean isSocketClosed(){
        try {
            //socket.sendUrgentData(1);
            inputStream = new DataInputStream(socket.getInputStream());
            outputStream = new DataOutputStream(socket.getOutputStream());
            outputStream.write("测试1".getBytes(StandardCharsets.UTF_8));
            byte[] bytes = new byte[1024];
            inputStream.read(bytes);
            String receive = new String(bytes, "utf-8");
            System.out.println("服务端接收消息===:" + receive);
            return false;
        } catch (Exception e) {
            return true;
        }
    }
}

 4.6.2  Socket客户端    

        Client.java(模拟客户端) 

/**
 * @author W
 * @createDate 2022/7/15
 * @description: 模拟客户端
 */
public class Client {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        Integer port = 8533 ;
        try {
            // 与服务端建立连接
            Socket socket = new Socket(host, port);
            socket.setOOBInline(true);

            // 建立输入输出流
            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
            int i = 0;
            while (true){
                send("客户发送",outputStream);
                receive(inputStream);
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
    /**
     * 发送数据
     * @param str
     * @param outputStream
     */
    public static void send(String str,DataOutputStream outputStream) throws Exception{
        System.out.println("客户端发送消息:=="+str);
        try {
            outputStream.write(str.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收数据
     * @param inputStream
     * @return
     * @throws Exception
     */
    public static String receive(DataInputStream inputStream) throws Exception{
        try {
            byte[] bytes = new byte[1024];
            inputStream.read(bytes);
            String info = new String(bytes,"utf-8");
            System.out.println("客户端接收信息:=="+info);
            return info;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

4.6.3  Socket配置,在启动SpringBoot时启动Socket服务。

@SpringBootApplication
public class SpringbootSocketApplication {

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(SpringbootSocketStudentApplication.class, args);
        applicationContext.getBean(SocketServer.class).start(8533);
    }

}

4.7  测试(双向通信)

        客户端通过ip和端口,连接到指定的server,然后通过Socket获得输出流,并向其输出内容,服务器会获得消息。服务端通过serverSocket.accept()侦听等待接收客户端连接,通过Socket获得输出流,向客户端发送消息,客户端通过Socket获得输入流接收消息。客户端关闭,服务端会进行相关资源回收。

客户端:

服务端:

猜你喜欢

转载自blog.csdn.net/weixin_48568302/article/details/126061376