• java网络编程之Socket编程


    概念

    网络编程分为BIO(传统IO)、NIO、AIO。Socket编程属于BIO这种传统IO。

    InetAddress

      java.net.InetAddress是JAVA中管理IP地址的类,常用

      

      

       public static void main(String[] args) throws UnknownHostException {
            InetAdressDemo.getLocalHost();
            System.out.println("---------------------------");
            getHostByName("Lenovo-Autumn");
        }
    
        /**
         * 获取主机ip和主机名
         * @throws UnknownHostException
         */
        public static void getLocalHost() throws UnknownHostException {
            //根据InetAddress获取主机名和主机ip
            InetAddress localHost = InetAddress.getLocalHost();
            System.out.println(localHost);   //打印:Lenovo-Autumn/192.168.56.1
    
            //根据getLocalHost()返回的值获取ip和主机名
            String hostName = localHost.getHostName();
            String hostAddress = localHost.getHostAddress();
            System.out.println(hostName); //打印 Lenovo-Autumn
            System.out.println(hostAddress); //打印 192.168.56.1
    
            //根据切割获取主机名和ip
            String[] str = localHost.toString().split("/");
            System.out.println(str[0]); //打印 Lenovo-Autumn
            System.out.println(str[1]); //打印 192.168.56.1
        }
    
        /**
         * 根据主机名称获取ip地址
         * @param otherName  主机名(可以是局域网中的机器名或者是域名或者是ip)
         * @throws UnknownHostException
         */
        public static void getHostByName(String otherName) throws UnknownHostException {
            InetAddress otherHost = InetAddress.getByName(otherName);
            String hostName = otherHost.getHostName();
            String hostAddress = otherHost.getHostAddress();
            System.out.println(hostName); //打印 Lenovo-Autumn
            System.out.println(hostAddress); //打印 192.168.56.1
            System.out.println(otherHost);   //打印:Lenovo-Autumn/192.168.56.1
        }

      code

    UDP

      发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。

      接收端不需要明确知道数据的来源,只需要接收到数据即可。

      java.net.DatagramPackage

    构造函数:

    第一种是用来接受的数据包,不需要指定IP和端口,第二种是用来发送的数据包需要指定ip和端口

    方法:

    获取ip地址,获取服务器ip或者客户端ip

      

      返回端口号,获取发送方或者

      返回数据缓冲区

      

      返回要发送或接受的数据包大小

      java.net.DatagramSocket

      DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。

    构造函数:

    用来创建发送端的DatagramSocket对象

    用来创建接收端的DatagramSocket对象

      方法:

    发送数据报包

    接收数据报包

    发送端

    1,  创建DatagramSocket对象

    DatagramSocket()

    2,创建DatagramPacket对象,封装数据,并指定ip和端口。

        DatagramPacket(byte[] buf, int length, InetAddress address, int port)  

    3,发送数据

        socket.send(DatagramPacket dp)

    4,释放流资源

    ds.close();

    接收端

    1,创建DatagramSocket对象,只需要指定端口

        DatagramSocket(port)

    2,创建DatagramPacket对象

        DatagramPacket(byte[] data, int length)

    3,接收数据存储到DatagramPacket对象中 

        receive(DatagramPackage dp)

    4,获取DatagramPacket对象的内容   

        new String(data,0,dp.getLength());

    5,释放流资源

        ds.close();

    /**
     * 实现udp发送端
     *  用java.net.DatagramPackage封装数据
     *  用java.net.DatagramSocket发送数据
     *
     * 实现步骤
     *  1.用DatagramPackage对象,封装数据,接受的地址和端口
     *  2.创建DatagramSocket
     *  3.调用DatagramSocket对象send方法,发送数据
     *  4.关闭资源
     *
     *  DatagramPackage构造函数
     *    DatagramPacket(byte[] buf, int length, InetAddress address, int port)
     *  DatagramSocket构造函数
     *    DatagramSocket()
     *    方法:send(DatagramPacket d)
     *  Created by Autumn on 2018/2/5.
     */
    public class UdpSend {
        public static void main(String[] args) throws Exception {
            Scanner scanner = new Scanner(System.in);
            //获取地址
            InetAddress inet = InetAddress.getByName("127.0.0.1");
            //创建DatagramSocket,负责接受和发送数据
            DatagramSocket ds = new DatagramSocket();
            while(true){
                String msg = scanner.nextLine();
                //创建数据包对象对象
                byte[] data = msg.getBytes();
                //封装数据,接受的地址和端口
                DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000);
    
                //发送数据包
                ds.send(dp);
                if(msg.equals("exit")){
                    break;
                }
            }
            //关闭
            ds.close();
        }
    }
    
    
    /**
     *  实现udp接收端
     *  用java.net.DatagramPackage 接受数据
     *  用java.net.DatagramSocket 接受数据包
     *
     *  步骤
     *  1.创建DatagramSocket对象,绑定端口号(要和发送端端口一致)
     *  2.创建字节数组用来接受数据
     *  3.创建数据对象包DatagramPackage
     *  4.创建DatagramSocket
     *    receive(DatagramPackage dp)接受数据,将数据封装如dp中
     *  5.拆包
     *    发送端的ip地址(DatagramPackage.get)
     *    接受到的字节数组
     *    发送的端口号
     *  6.关闭资源
     * Created by Autumn on 2018/2/5.
     */
    public class UdpReceive {
        public static void main(String[] args) throws IOException {
            //创建数据包传输的对象,并绑定端口号
            DatagramSocket ds = new DatagramSocket(6000);
            //创建字节数组
            byte[] data = new byte[1024];
            while(true){
                //创建数据包对象,传递字节数组
                DatagramPacket dp = new DatagramPacket(data,data.length);
                //调用ds对象的receive接受数据包,receive()有线程阻塞效果会一直等待接受数据
                ds.receive(dp);
    
                //获取数据包大小
                int len = dp.getLength();
                //获取发送端的ip地址
                InetAddress sendAddress = dp.getAddress();
                String sendHostAddress = sendAddress.getHostAddress();
                //System.out.println(sendHostAddress);
                //获取发送端端口号
                int port = dp.getPort();
                //System.out.println(port);
    
                //System.out.println(new String(data));   //直接打印1024个字节的字符串,有很多空格
                System.out.println(sendHostAddress+":"+port+"    "+new String(data,0,len));   //这样打印没有多余的空格
    
                if(new String(data,0,len).equals("exit")){
                    break;
                }
            }
            //关闭
            ds.close();
        }
    }

      code

    TCP

    ServerSocket类,用于表示服务器端,Socket类,用于表示客户端。建立连接后用流进行输入和输出

    ServerSocket

       实例化一个ServerSocket类,指定端口号

      监听并接受此套接字的连接

      返回此服务器套接字的本地地址

      Socket

      实例化一个Socket并指定ip和端口

      

    返回一个输出流,用于客户端发送数据

      返回一个输入流,用于服务器端接受数据

      

      返回ip地址(服务器端的地址)

      返回端口号

    客户端

    1,创建客户端的Socket对象,指定服务器IP和端口号

      Socket(String host, int port)      

    2,获取Socket的输出流对象

      getOutputStream(); 

    3,写数据给服务器

      out.write("服务器数据".getBytes());

    4,关闭流资源

      socket.close();

    服务器端

    1,创建服务器端ServerSocket对象,指定服务器端端口号

      ServerSocket(int port)

    2,开启服务器,等待着客户端Socket对象的连接,如有客户端连接,返回客户端的Socket对象

      accept()

    3,通过客户端的Socket对象,获取客户端的输入流,为了实现获取客户端发来的数据

      socket.getInputStream();

    4,通过客户端的输入流,获取流中的数据

    byte[] data = new byte[1024];
    int len = inputStream.read(data);
    System.out.println(new String(data,0,len));

    5,通过客户端的Socket对象,获取客户端的输出流,为了实现给客户端反馈信息

    6,通过客户端的输出流,写数据到流中

    7,关闭流资源

    socket.close();
    serverSocket.close();

    /**
     * 实现TCP服务器程序
     * 表示服务器程序的类java.net.ServerSocket
     * 构造方法:
     *      ServerSocket(int port);   传递端口号
     *
     * Important:必须获得客户端的套接字(Socket)
     *     方法:Socket accept()
     *     服务器可以获取到客户端的套接字
     * Created by Autumn on 2018/2/5.
     */
    public class TCPServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8888);
            //调用服务器套接字对象accept()获取客户端套接字,具有线程等待效果
            Socket socket = serverSocket.accept();    //这里会阻塞等待连接接入    cmd中telnet 127.0.0.1 8888即可连接
            //根据获得的客户端的socket获取输入流
            InputStream inputStream = socket.getInputStream();
            //根据输入流将数据读入到data中
            byte[] data = new byte[1024];
            int len = inputStream.read(data);   //这里会阻塞,等待数据   cmd中ctrl+]进入到telnet操作模式send value发送数据
            System.out.println(new String(data,0,len));
            socket.close();
            serverSocket.close();
        }
    }
    
    
    /**
     * 实现TCP客户端,连接到服务器
     * 和服务器实现数据交换
     * 实现TCP客户端程序的类 java.net.Socket
     *
     * 构造方法:
     *      Socket(String host, int port)传递服务器IP和端口号
     *      注意:构造方法只要运行,就会和服务器进行连接,连接时报,抛出异常
     *
     *      OutputStream    getOutputStream()  返回套接字的输出流
     *          作用:将数据输出,输出到服务器
     *      InputStream    getInputStream()  返回套接字的输入流
     *          作用:从服务器端读取数据
     *
     *      客户端服务器数据交换,必须使用套接字对象Socket中的获取的IO刘,自己new的流不行
     *
     * Created by Autumn on 2018/2/5.
     */
    public class TCPClient {
        public static void main(String[] args) throws IOException {
            //创建Socket对象,连接服务器
            Socket socket = new Socket("127.0.0.1",8888);
            //通过客户端的套接字对象Socket方法,获取字节输出流,将数据写向服务器
            OutputStream out = socket.getOutputStream();
            out.write("这是一条来客户端的数据".getBytes());
            socket.close();
        }
    }

    用cmd命令telnet实现和SocketServer互动

    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServer {
        /*同步阻塞*/
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服务端启动成功...");
            while(true){
                /*一次只能处理一个连接,在一个连接没关闭前无法接收第二个连接,在这里开一个框发送数无问题,开放两个框时会出现第二个无反应*/
                Socket socket = serverSocket.accept();   //这里会阻塞等待连接接入    cmd中telnet 127.0.0.1 8888即代表连接
                System.out.println("新客户端连接成功....");
                InputStream inputStream = socket.getInputStream();
                while(true) {
                    byte[] data = new byte[1024];
                    System.out.println("正在等待数据...");
                    int len = inputStream.read(data);    //这里会阻塞,等待数据,如果直接关闭cmd窗口会因为关闭socket通道导致len返回-1   cmd中ctrl+]进入到telnet操作模式send value发送数据
                    if (len != -1){
                        System.out.println(new String(data, 0,len, "GBK"));   //用GBK是因为CMD窗口命令发送的数据是GBK编码
                    }else{
                        break;
                    }
                }
            }
            //socket.close();
            //serverSocket.close();
            //System.out.println("服务器端关闭....");
        }
    }

    连接服务端 

    向服务端发送数据

    再开一个cmd然后telnet发送数据,发现无反应。必须关闭第一个cmd才能连接成功。

    线程池改进,能并发访问

    用ExecutorService线程池实现每一个连接创建一个新的线程

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TCPServer {
        /*异步阻塞*/
        public static void main(String[] args) throws IOException {
            ExecutorService threadPool = Executors.newCachedThreadPool();   //线程池
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服务端启动成功...");
            while(true){
                /*一次只能处理一个连接,在一个连接没关闭前无法接收第二个连接,在这里开一个框发送数无问题,开放两个框时会出现第二个无反应*/
                System.out.println("等待客户端连接...");
                final Socket socket = serverSocket.accept();   //这里会阻塞等待连接接入    cmd中telnet 127.0.0.1 8888即代表连接
                threadPool.execute(new Runnable() {   //启动一个线程
                    public void run() {
                        try {
                            System.out.println("新客户端连接成功....");
                            InputStream inputStream = socket.getInputStream();
                            while(true) {
                                byte[] data = new byte[1024];
                                System.out.println("正在等待数据...");
                                int len = inputStream.read(data);    //这里会阻塞,等待数据,如果直接关闭cmd窗口会因为关闭socket通道导致len返回-1   cmd中ctrl+]进入到telnet操作模式send value发送数据
                                if (len != -1){
                                        System.out.println(Thread.currentThread()+new String(data, 0,len, "GBK"));   //用GBK是因为CMD窗口命令发送的数据是GBK编码
                                }else{
                                    System.out.println("break循环");
                                    break;
                                }
                            }
                            System.out.println("一次Socket连接关闭");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
    
            }
            //socket.close();
            //serverSocket.close();
            //System.out.println("服务器端关闭....");
        }
    }

    优点:传输质量好(所以BIO适合传输连接少但是数据量大的请求)

    缺点:每一个连接都占用一个线程,很占用系统资源。

    tip:所有的调优都关联到系统资源(IO、存储、内存、CPU)

    code

  • 相关阅读:
    [CF1042F]Leaf Sets
    [CF1051F]The Shortest Statement
    [洛谷P1792][国家集训队]种树
    [CF484E]Sign on Fence
    [洛谷P2216][HAOI2007]理想的正方形
    [洛谷P4389]付公主的背包
    [洛谷P4726]【模板】多项式指数函数
    服务器上Ubuntu系统安装
    删除ubuntu系统
    Win10下安装Ubuntu16.04双系统
  • 原文地址:https://www.cnblogs.com/aeolian/p/8417916.html
Copyright © 2020-2023  润新知