zoukankan      html  css  js  c++  java
  • Socket通信,基本方法介绍

    Socket是什么呢?

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

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

    TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
     UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

    Socket 原理

    Socket 连接,至少需要一对套接字,分为 clientSocket,serverSocket 连接分为3个步骤:

    (1) 服务器监听:服务器并不定位具体客户端的套接字,而是时刻处于监听状态;

    (2) 客户端请求:客户端的套接字要描述它要连接的服务器的套接字,提供地址和端口号,然后向服务器套接字提出连接请求;

    (3) 连接确认:当服务器套接字收到客户端套接字发来的请求后,就响应客户端套接字的请求,并建立一个新的线程,把服务器端的套接字的描述发给客户端。一旦客户端确认了此描述,就正式建立连接。而服务器套接字继续处于监听状态,继续接收其他客户端套接字的连接请求.

    过程图解:

    先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。

    在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。

    客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

    实例展示:

    服务器端:

    package com.socket.test;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class SocketServer {
      public static void main(String[] args) throws Exception {
        // 监听指定的端口
        int port = 55533;
        ServerSocket server = new ServerSocket(port);
        
        // server将一直等待连接的到来
        System.out.println("server将一直等待连接的到来");
        Socket socket = server.accept();
        // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        //只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1
        while ((len = inputStream.read(bytes)) != -1) {
          // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
          sb.append(new String(bytes, 0, len, "UTF-8"));
        }
        System.out.println("get message from client: " + sb);
      // 发送信息给客户端
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
    
        inputStream.close();
        outputStream.close();
        socket.close();
        server.close();
      }
    }

    当读取完客户端的消息后,打开输出流,将指定消息发送回客户端

    客户端:

    package com.socket.test
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    public class SocketClient {
      public static void main(String args[]) throws Exception {
        // 要连接的服务端IP地址和端口
        String host = "127.0.0.1";
        int port = 55533;
        // 与服务端建立连接
        Socket socket = new Socket(host, port);
        // 建立连接后获得输出流
        OutputStream outputStream = socket.getOutputStream();
        String message = "你好  fuwuqiduan";
        socket.getOutputStream().write(message.getBytes("UTF-8"));
        //通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据
        socket.shutdownOutput();
        
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytes)) != -1) {
          //注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
          sb.append(new String(bytes, 0, len,"UTF-8"));
        }
        System.out.println("get message from server: " + sb);
        
        inputStream.close();
        outputStream.close();
        socket.close();
      }
    }

    客户端也有相应的变化,在发送完消息时,调用关闭输出流方法,然后打开输出流,等候服务端的消息。

    服务器段优化

    在上面的例子中,服务端仅仅只是接受了一个Socket请求,并处理了它,然后就结束了,但是在实际开发中,一个Socket服务往往需要服务大量的Socket请求,那么就不能再服务完一个Socket的时候就关闭了,这时候可以采用循环接受请求并处理的逻辑:

    package com.socket.test
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class SocketServer {
      public static void main(String args[]) throws IOException {
        // 监听指定的端口
        int port = 55533;
        ServerSocket server = new ServerSocket(port);
        // server将一直等待连接的到来
        System.out.println("server将一直等待连接的到来");
        
        while(true){
          Socket socket = server.accept();
          // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
          InputStream inputStream = socket.getInputStream();
          byte[] bytes = new byte[1024];
          int len;
          StringBuilder sb = new StringBuilder();
          while ((len = inputStream.read(bytes)) != -1) {
            // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
            sb.append(new String(bytes, 0, len, "UTF-8"));
          }
          System.out.println("get message from client: " + sb);
          inputStream.close();
          socket.close();
        }
        
      }
    }

    这种一般也是新手写法,但是能够循环处理多个Socket请求,不过当一个请求的处理比较耗时的时候,后面的请求将被阻塞,所以一般都是用多线程的方式来处理Socket,即每有一个Socket请求的时候,就创建一个线程来处理它。

      不过在实际生产中,创建的线程会交给线程池来处理,为了:

    • 线程复用,创建线程耗时,回收线程慢
    • 防止短时间内高并发,指定线程池大小,超过数量将等待,方式短时间创建大量线程导致资源耗尽,服务挂掉
    package com.socket.test
    
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SocketServer {
      public static void main(String args[]) throws Exception {
        // 监听指定的端口
        int port = 55533;
        ServerSocket server = new ServerSocket(port);
        // server将一直等待连接的到来
        System.out.println("server将一直等待连接的到来");
    
        //如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        
        while (true) {
          Socket socket = server.accept();
          
          Runnable runnable=()->{
            try {
              // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
              InputStream inputStream = socket.getInputStream();
              byte[] bytes = new byte[1024];
              int len;
              StringBuilder sb = new StringBuilder();
              while ((len = inputStream.read(bytes)) != -1) {
                // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
                sb.append(new String(bytes, 0, len, "UTF-8"));
              }
              System.out.println("get message from client: " + sb);
              inputStream.close();
              socket.close();
            } catch (Exception e) {
              e.printStackTrace();
            }
          };
          threadPool.submit(runnable);
        }
    
      }
    }

    借鉴网址:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html

  • 相关阅读:
    【队列应用一】随机迷宫|随机起点终点*最短路径算法
    【堆栈应用二】迷宫算法,可直接动态运行
    【堆栈应用一】一个数divided=几个最小质因数的乘积(时间复杂度On)
    MyEclipse2014中Java类右键Run as没有JUnit Test
    C++中break/Continue,exit/return的理解
    windows下用C++修改本机IP地址
    windows下注册表的操作
    详解Java的Spring框架中的注解的用法
    什么是Java Bean
    JS windows对象的top属性
  • 原文地址:https://www.cnblogs.com/dk2557/p/9958423.html
Copyright © 2011-2022 走看看