网络编程


​ Java语言作为最流行的网络编程语言,提供了强大的网络编程功能。

​ 使用Java语言可以编写底层的网络通信程序,这是通过java.net包中提供的InetAddress类、URL类、Socket类以及ServerSocket等类实现的。

1、网络地址类InetAddress

​ 要实现网络通信,首先需要知道计算机的地址。连接到Internet上的计算机使用IP地址或域名来唯一标识,在局域网上的计算机则可以使用名称标识。在java.net包中提供了InetAddress类来表示计算机地址。
​ InetAddress类没有提供构造方法,要得到一个InetAddress类对象,需要使用该类的静态方法。

eg:

public class InetAddressDemo {

    public static void main(String[] args) {
        try {

            InetAddress addr = InetAddress.getLocalHost();
            System.out.println(addr.getHostAddress());
            System.out.println(addr.getHostName());
            System.out.println(addr.getAddress().length);

            InetAddress addr2 = InetAddress.getByName("www.acfun.cn");
            System.out.println(addr2.getHostAddress());
            System.out.println(addr2.getHostName());
            System.out.println(addr2.getAddress().length);

        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

2、 URL编程

2.1 URL与URL类

​ URL是统一资源定位器(Uniform Resource Locator)的简写,它是WWW中网络资源定位的表示方法。
​ URL的基本格式为:

  • <协议名://><主机名>[<:端口号>]</资源名>
  • 协议名表示资源使用的协议,如http、ftp、telnet、mailto或file等。
  • 主机名为任何合法的主机域名,如www.njupt.edu.cn。
  • 端口号是可选的,如果使用熟知端口号,则可以省略。
  • 资源名一般用来指定远程主机上文件系统中文件的完整路径,如/index.html。

​ URL类的实现:

static void Url1()
    &#123;
        try &#123;
            String s = "https://www.baidu.com/";
            URL url = new URL(s);

            System.out.println("Protocol:" + url.getProtocol()); //获取协议名
            System.out.println("Host:" + url.getHost());    //获取主机号
            System.out.println("Port:" + url.getPort());//获取端口号,若没有指定端口号则返回-1
            System.out.println("DefaultPort:" + url.getDefaultPort());    //获取默认端口号
            System.out.println("File:" + url.getFile());    //返回URL的文件名及路径

            InputStream is = url.openStream();
            InputStreamReader isr = new InputStreamReader(is,"utf-8");
            int c = 0;

            while( ( c = isr.read() ) != -1)
            &#123;
                System.out.print((char)c);
            &#125;

            isr.close();
            is.close();
            System.out.println("finish");

        &#125; catch (MalformedURLException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;

    &#125;

2.2 URL的Connection类

  • ​ 通过URL的openStream()方法,只能获得InputStream对象。使用该对象只能从网络上读取数据。
  • ​ 如果希望不仅要从URL读取内容,还要向URL对象发送服务请求及参数,可以使用URLConnection类。
  • ​ 利用URL类提供的openConnection()方法,可以建立一个URLConnection对象。可以调用其getInputStream()方法和getOutputStream()方法得到输入流和输出流对象。

eg:

static void Url2()
    &#123;
        String surl = "https://www.baidu.com";
        try &#123;
            URL url = new URL(surl);

            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setRequestMethod("POST");

            conn.setDoOutput(true);  //设置允许上传
            //http请求的一些属性
            conn.setRequestProperty("ContentType", "text/plain");

            //打开连接输出流,输出数据
            OutputStream os = conn.getOutputStream();
            FileInputStream fis = new FileInputStream("test.txt");

            int c = 0;
            System.out.println((char)fis.read());
            while( (c=fis.read()) != -1)
            &#123;
                os.write(c);
            &#125;

            fis.close();
            os.close();

            //当打开输入流的时候,请求发出,并得到响应
            InputStream is = conn.getInputStream();
            InputStreamReader reader = new InputStreamReader(is,"utf-8");
            c = 0;
            while( (c=reader.read()) != -1) &#123;
                System.out.print((char)c);
            &#125;
            reader.close();
            is.close();
            System.out.println("read finish");

        &#125; catch (MalformedURLException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;

3、Java套接字编程

3.1 端口号和套接字

​ 在网络上,很多应用都是采用客户/服务器(C/S)结构的。实现网络通信必须将两台机器连接起来建立一个双向通信链路,这个双向通信链路的每一端称为一个套接字(Socket)。

​ 在Internet上使用IP地址唯一标识一台主机。
​ 一台主机可能提供多种服务,仅用IP地址不能唯一标识一个服务。通常使用一个整数来标识该机器上的某个服务,这个整数就是端口号(Port)。
​ 端口号是用16位整数标识,共有65536个端口号。端口号并不是机器上实际存在的物理位置,而是一种软件上的抽象。

​ 一个TCP连接由它的两个端点来标识,而每一个端点又是由IP地址和端口号决定的。TCP连接的端点称为套接字,套接字是由IP地址和端口号构成的,如下图所示。

套接字的构成

​ 这里,131.6.23.13为IP地址,1500为端口号,因此套接字为131.6.23.13,1500。

3.2 套接字通信

​ 运行在一台特定机器上的某个服务器(如HTTP服务器)都有一个套接字绑定到该服务器上。服务器只是等待、监听客户的连接请求。
​ 在客户端,客户机需要知道服务器的主机名和端口号。为了建立连接请求,客户机试图与服务器机上的指定端口号上的服务连接,这个请求过程如下图所示。

Client与Server连接过程

​ 如果正常,服务器将接受连接请求。一旦接受了请求,服务器将创建一个新的绑定到另一个端口号的套接字,然后使用该套接字与客户通信。这样,服务器可以使用原来的套接字继续监听连接请求,如图所示。

连接成功后的状态

​ 在客户端,如果连接被接受,就会创建一个套接字,客户就可以使用该套接字与服务器通信。注意,客户端的套接字并没有绑定到与服务器连接的端口号上,相反客户被指定客户程序所在机器上的一个端口号上。现在客户与服务器就可以读写套接字进行通信了。

3.3 套接字类

​ 为了实现套接字通信,在java.net包中提供了两个类:Socket和ServerSocket。它们分别实现连接的客户端和服务器端的套接字。

3.3.1 Socket类

Client和Server都需定义出该类的对象。创建对象时,端口号必须对应。

public InputStrean getInputStream() throws IOException

获得套接字绑定的数据输入流。
public OutputStream getOutputStream() throws IOException

获得套接字绑定的数据输出流。

3.3.2 ServerSocket类

​ 用在服务器端。客户与服务器通信,客户向服务器提出请求,服务器监听请求,一旦监听到客户请求,服务器也要建立一个套接字。

3.3.3 客户和服务器通信的实例

包括以下4个基本步骤:
(1)创建套接字对象。
(2)打开连接到套接字的输入输出流。
(3)按照一定协议对套接字进行读写操作。
(4)关闭套接字对象。

通信示意图

eg:

服务器代码:

public class Server &#123;

    public static void main(String[] args) &#123;
        // TODO Auto-generated method stub
        try &#123;
            //服务器监听在本机的8000端口

            ServerSocket server = new ServerSocket(8000);

            System.out.println("server start..");

            //当接受到客户端请求后,生成一个socket,用于和客户端的数据传输
            Socket socket = server.accept();//阻塞,等待客户端连接请求

            System.out.println(socket.getRemoteSocketAddress());//得到Client的Socket地址
            System.out.println(socket.getLocalSocketAddress());//得到Server的Socket地址

            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();
            os.write("hello".getBytes());
            os.flush();

            byte[] bts = new byte[20];

            //采用的是阻塞IO(若没有数据,就一直等待)
            int count = 0;
            String str = "";
            Scanner scanner = new Scanner(System.in);
            while( true )
            &#123;
                count = is.read(bts, 0, 20);
                str = new String(bts,0,count);
                System.out.println("Client say:"+str);
                if( str.equals("bye"))
                    break;


                str = scanner.nextLine();
                os.write(str.getBytes());
                os.flush();
                if( str.equals("bye") )
                    break;
            &#125;
            is.close();
            os.close();


            System.out.println("server stop..");

            server.close();

        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;

&#125;

客户端代码:

public class Client &#123;

    public static void main(String[] args) &#123;
        try &#123;
            //参数是要连接的服务器地址和端口,发出连接请求,建立连接
            Socket socket = new Socket("localhost",8000);


            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();

            byte[] bts = new byte[20];
            int count = is.read(bts, 0, 20);
            System.out.println("Server say:"+new String(bts,0,20));

             count = 0;
            String str = "";
            Scanner scanner = new Scanner(System.in);
            while( true )
            &#123;
                str = scanner.nextLine();
                os.write(str.getBytes());
                os.flush();
                if( str.equals("bye") )
                    break;

                count = is.read(bts, 0, 20);
                str = new String(bts,0,count);
                System.out.println("Server say:"+str);
                if( str.equals("bye"))
                    break;        

            &#125;
            is.close();
            os.close();

            socket.close();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;
&#125;

3.3.4 多个客户端和服务器通信实例

Server:

public class ServerDemo &#123;

    public static void main(String[] args) &#123;
        ServerSocket server = null;
        try &#123;
            server = new ServerSocket(9000);

            while( true )
            &#123;
                Socket socket = server.accept();

                //启动客户端线程,独立完成通信
                new Thread(new ClientThreadDemo(socket)).start();
            &#125;

        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("server start fail..");
        &#125;finally &#123;
                try &#123;
                    if(server !=null)
                    server.close();
                &#125; catch (IOException e) &#123;
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                &#125;
        &#125;
    &#125;
&#125;

Client:

public class Client &#123;

    public static void main(String[] args) &#123;
        try &#123;
            //参数是要连接的服务器地址和端口,发出连接请求,建立连接
            Socket socket = new Socket("localhost",9000);

            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();

            byte[] bts = new byte[20];
            int count = is.read(bts, 0, 20);
            System.out.println("Server say:"+new String(bts,0,20));


             count = 0;
            String str = "";
            Scanner scanner = new Scanner(System.in);
            while( true )
            &#123;
                str = scanner.nextLine();
                os.write(str.getBytes());
                os.flush();
                if( str.equals("bye") )
                    break;

                count = is.read(bts, 0, 20);
                str = new String(bts,0,count);
                System.out.println("Server say:"+str);
                if( str.equals("bye"))
                    break;        

            &#125;
            is.close();
            os.close();

            socket.close();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;
&#125;

ClientThread:

public class ClientThreadDemo implements Runnable
&#123;
    private Socket socket;
    public  ClientThreadDemo(Socket socket) 
    &#123;
        this.socket = socket;
    &#125;

    @Override
    public void run() &#123;
        // TODO Auto-generated method stub
        InputStream is = null;
        OutputStream os = null;
        try
        &#123;
         is = socket.getInputStream();
         os = socket.getOutputStream();
        os.write("hello".getBytes());
        os.flush();

        byte[] bts = new byte[20];

        //采用的是阻塞IO(若没有数据,就一直等待)
        int count = 0;
        String str = "";
        Scanner scanner = new Scanner(System.in);
        while( true )
        &#123;
            count = is.read(bts, 0, 20);
            str = new String(bts,0,count);
            System.out.println("Client say:"+str);
            if( str.equals("bye"))
                break;


            str = scanner.nextLine();
            os.write(str.getBytes());
            os.flush();
            if( str.equals("bye") )
                break;
        &#125;

        &#125;catch (Exception e) &#123;
            e.printStackTrace();
        &#125;finally &#123;
            try &#123;
                if(is != null) is.close();
                if(os != null) os.close();
                if(socket != null) socket.close();

            &#125; catch (IOException e) &#123;
                // TODO Auto-generated catch block
                e.printStackTrace();
            &#125;
        &#125;
    &#125;

&#125;

4、数据报通信

4.1 流式通信和数据报通信

当编写网络程序时,有两种通信可供选择:流式通信和数据报通信。

流式通信 数据报通信
流式通信使用TCP协议,该协议是面向连接的协议。使用这种协议要求发送方和接收方都要建立套接字,一旦两个套接字建立起来,它们就可以进行双向通信,双方都可以发送和接收数据。 数据报通信使用UDP协议,该协议是一种无连接的协议。使用这种协议通信,每个数据报都是一个独立的信息单元,它包括完整的目的地址,数据报在网络上以任何可能的路径传往目的地,因此数据能否到达目的地、到达的时间以及内容的正确性都是不能保证,该协议提供的是不可靠的服务。
对于TCP,是一个面向连接的协议,在通信之前必须建立双方的连接,因此在TCP中多了一个建立连接的时间。 使用UDP时,每个数据报都给出了完整的地址信息,无需建立发送方和接收方的连接。
使用TCP就没有大小限制,一旦连接建立,就可以传输大量数据。 使用UDP传输数据时有大小限制,每个数据报必须不大于64KB。
TCP是可靠协议,确保接收方完全正确地获取发送方发送的数据。 UDP是不可靠协议,发送方发送的数据不一定以相同的次序到达接收方。
TCP使用较广泛,如TELNET远程登录、FTP文件传输都需要不定长度的数据可靠地传输,因此使用TCP协议。 UDP比较简单,需要较少的监护,因此常用于局域网分散系统中的客户/服务器应用程序。

4.2 DatagramSocket和DatagramPacket

​ 用UDP编写客户/服务器程序时,无论是客户方还是服务器方,首先都要建立一个DatagramSocket对象用来接收或发送数据报,然后使用DatagramPacket类对象作为传输数据的载体。

4.2.1 数据报通信实例

Server:

public class UDPServerDemo &#123;

    public static void main(String[] args) &#123;
        try &#123;

            DatagramSocket socket = new DatagramSocket(9090);

            byte[] buf = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf, 1024);

            socket.receive(packet);  //阻塞

            System.out.println("Client address:"+packet.getSocketAddress());
            System.out.println("Client data:"+new String(packet.getData(),0,packet.getLength()));

            String str = "welcome";
            //从接收到的数据报取得对方的地址和端口
            DatagramPacket spacket = new DatagramPacket(str.getBytes(), str.getBytes().length, packet.getAddress(), packet.getPort());

            socket.send(spacket);

            socket.close();

        &#125; catch (SocketException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;
&#125;

Client:

public class UDPClientDemo &#123;

    public static void main(String[] args) &#123;
        try &#123;

            DatagramSocket socket = new DatagramSocket(9091);

            String str = "hello server";

            DatagramPacket spacket = new DatagramPacket(str.getBytes(), str.getBytes().length,InetAddress.getByName("localhost"),9090);

            socket.send(spacket);

            byte[] buf = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf, 1024);

            socket.receive(packet);  //阻塞

            System.out.println(packet.getSocketAddress());
            System.out.println(new String(packet.getData(),0,packet.getLength()));

            socket.close();
        &#125; catch (SocketException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125; catch (UnknownHostException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125; catch (IOException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
        &#125;
    &#125;
&#125;

4.2.2 基于线程实现一对一通信

UDPPeerDemo:

class UDPReceiveThread implements Runnable
&#123;
    private DatagramSocket socket;

    public UDPReceiveThread(DatagramSocket socket) &#123;
        this.socket = socket;
    &#125;

    @Override
    public void run() &#123;
        byte [] buffer = new byte[1024];
        DatagramPacket p = null;
        while( true ) &#123;
            p = new DatagramPacket(buffer, 1024);
            try &#123;
                socket.receive(p);
                System.out.println("Data: " + new String(p.getData(),0,p.getLength()));
            &#125; catch (IOException e) &#123;
                // TODO Auto-generated catch block
                e.printStackTrace();
                break;
            &#125;
        &#125;
    &#125;

&#125;


public class UDPPeerDemo implements Runnable
&#123;
    private DatagramSocket socket;
    private int port;
    private int rport;

    public UDPPeerDemo(int port,int rport) &#123;
        this.port = port;
        this.rport = rport;

        try &#123;
            socket = new DatagramSocket(port);
        &#125; catch (SocketException e) &#123;
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("套接字创建失败");
        &#125;
    &#125;

    @Override
    public void run() &#123;
        new Thread(new UDPReceiveThread(socket)).start();  //启动接收线程
        String s = "";
        Scanner scanner = new Scanner(System.in);
        DatagramPacket packet = null;
        while( scanner.hasNextLine() ) &#123;
            s = scanner.nextLine();
            try &#123;
                packet = new DatagramPacket(s.getBytes(), s.getBytes().length,
                        InetAddress.getByName("localhost"), rport);
            &#125; catch (UnknownHostException e) &#123;
                // TODO Auto-generated catch block
                e.printStackTrace();
                continue;
            &#125;
            try &#123;
                socket.send(packet);
            &#125; catch (IOException e) &#123;
                // TODO Auto-generated catch block
                e.printStackTrace();
                break;
            &#125;
        &#125;

        socket.close();    
    &#125;
&#125;

UDPDemo:

public class UDPDemo &#123;

    public static void main(String[] args) &#123;
        new Thread(new UDPPeerDemo(9090, 9091)).start();
    &#125;
&#125;

UDPDemo2:

public class UDPDemo2 &#123;

    public static void main(String[] args) &#123;
        new Thread(new UDPPeerDemo(9091, 9090)).start();
    &#125;
&#125;

5、小结

​ 通过网络资源定位器可以指向并获取网络上丰富的资源。基于TCP的网络数据传输是一种可靠的有连接的网络数据传输。这种传输方式是目前最常用的网络数据传输方式。在基于TCP的网络程序中,服务器端与客户端的程序编写稍有些不同。
​ 基于UDP的网络数据传输是一种可靠的无连接的网络数据传输。在基于UDP的网络程序设计中,服务器端与客户端的程序编写基本上是相类似的。在Java的包java.net还含有很多用于网络数据传输的类和接口,为网络编程提供了方便。


文章作者: kilig
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kilig !
 上一篇
NIO NIO
1、NIO概念1.1 Unix定义了五种 I/O 模型 阻塞 I/O 非阻塞 I/O I/O 复用 信号驱动 I/O 异步 I/O 1.1.1 阻塞(Block)和非阻塞(NonBlock)阻塞和非阻塞是进程在访问数据的时候,数据是否准备
下一篇 
io流 io流
1、流的概念1.1流(1)流是一个无结构化的数据组成的序列,流中的数据没有任何格式和含义,只是以字节或字符形式进行流入或流出。 (2)数据流的流入和流出都是以程序本身作为核心,流入是指数据从外部数据源流入到程序内部;流出是指数据从程序内部向
  目录