【Java基础】Java网络编程基础知识

什么是网络编程

  网络编程是通过使用套接字来达到进程间通信目的,那什么是套接字呢?其实套接字是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程,具体来看,套接字=IP地址+TCP/UDP + 端口的组合。

 

网络编程的三要素

  网络编程中,通信“双方”要如何找到彼此呢?互联网是通过IP地址来区分上网电脑的,但是通信的“双方”都是物理电脑上跑的进程,在一台电脑上通过端口来区分不同进程和程序,最后,在互联网通信中,我们分UDP和TCP两种协议。所以网络编程的三要素就是:IP地址,端口号,通信协议。

  IP地址

  IP地址是指互联网协议地址,IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。IP地址编址方案将IP地址空间划分为A、B、C、D、E五类,其中A、B、C是基本类,D、E类作为多播和保留使用。IP地址总共是4个字节,32比特位。

  A类:网络号为1个字节,定义最高比特为0,余下7比特为网络号;

  B类:网络号为2字节,定义最高比特为10,余下14比特为网络号;

  C类:网络号为3字节,定义最高三比特为110,余下21比特为网络号;

  D类:不分网络号和主机号,定义最高四比特为1110,表示一个多播地址,即多目的地传输,可用来识别一组主机;

  E类:以1111开始,为将来使用保留。E类地址保留,仅作实验和开发用;

  另外,全0类地址0.0.0.0表示任意网络。全1的IP地址255.255.255.255是当前子网的广播地址。网络号127。TCP/IP协议规定网络号127不可用于任何网络。其中有一个特别地址:127.0.0.1称之为回送地址(Loopback),它将信息通过自身的接口发送后返回,可用来测试端口状态。

  总之:1~126属A类地址,128~191属B类地址,192~223属C类地址,224~239属D类地址。除了以上四类地址外,还有E类地址,但暂未使用。

  端口号

  端口包括物理端口和逻辑端口。物理端口是用于连接物理设备之间的接口,逻辑端口是逻辑上用于区分服务的端口。TCP/IP协议中的端口就是逻辑端口,通过不同的逻辑端口来区分不同的服务。一个IP地址的端口通过16bit进行编号,最多可以有65536个端口。端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535

  协议TCP/UDP--指网络传输层的协议

  TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。

  UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。UDP同样也是传输层的协议,但是由于UDP是无连接的协议,所以没有三次握手过程。

 

Java中网络编程的几个重要类

  Java为网络编程提供了专门的包java.net。下面来看看实现基础网络通信的几个类,也是我们上面总结的几个网络编程要素,除了这些要素外,传输的数据在网络中需要打包等。

InetAddress       此类表示互联网协议 (IP) 地址。
DatagramSocket    此类表示用来发送和接收数据报包的套接字。
ServerSocket      此类实现服务器套接字。
Socket            此类实现客户端套接字(也可以就叫“套接字”)。
DatagramPacket    此类表示数据报包。

 

UDP网络编程

  用UDP协议进行通信,简单的发送数据按照如下4步走:

  A. 创建发送端Socket对象DatagramSocket
  B. 创建数据,并把数据打包成DatagramPacket
  C. 调用Socket对象的发送方法发送数据包
  D. 释放资源

  接收数据按如下5步走: 

  A:创建接收端Socket对象DatagramSocket
  B:创建一个数据包(接收容器)
  C:调用Socket对象的接收方法接收数据包DatagramPacket
  D:解析数据包,并显示在控制台
  E:释放资源

  下面写一个简单的示例程序,先写服务端的接收数据的程序:

  

public class ReceiveServer {
    public static void main(String[] args) throws IOException {
        // 创建接收端的Socket对象
        DatagramSocket ds = new DatagramSocket(12345);

        // 创建一个数据包用来接收数据
        byte[] bys = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bys, bys.length);

        // 接收数据
        ds.receive(dp);

        // 解析数据
        String ipAdd = dp.getAddress().getHostAddress();
        String str = new String(dp.getData(), 0, dp.getLength());
        System.out.println("地址 " + ipAdd + " 传输的数据为 : " + str);

        // 释放资源
        ds.close();
    }
}

客户端的发送程序为:

public class SendClient {
    public static void main(String[] args) throws IOException {
        // 创建发送端的Socket对象
        DatagramSocket ds = new DatagramSocket();

        // 创建数据并打包
        byte[] bys = "这是UDP传输测试".getBytes();
        DatagramPacket dp = new DatagramPacket(bys, bys.length,
                InetAddress.getByName("192.168.1.101"), 12345);

        // 发送数据
        ds.send(dp);

        // 释放资源
        ds.close();
    }
}

  先启动服务端,再启动客户端,最后服务端输出客户端传来的信息:地址 192.168.1.101 传输的数据为 : 这是UDP传输测试
  如果多次启动客户端,则会报错端口被占用。

  上面的程序虽然实现了基本的通信,但是却有一些问题:

  A: 客户端只能传输一条数据,我们希望可以传输多条或者不想传输后再关闭客户端释放资源。
  B:服务端和客户端只能接受单线程

  为此,将代码改进为键盘录入的多线程版本:

  

public class ChatRoom {
    public static void main(String[] args) throws IOException {
        DatagramSocket dsSend = new DatagramSocket();
        DatagramSocket dsReceive = new DatagramSocket(12346);

        SendThread st = new SendThread(dsSend);
        ReceiveThread rt = new ReceiveThread(dsReceive);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(rt);

        t1.start();
        t2.start();
    }
}

class ReceiveThread implements Runnable {
    private DatagramSocket ds;

    public ReceiveThread(DatagramSocket ds) {
        this.ds = ds;
    }

    @Override
    public void run() {
        try {
            while (true) {
                // 创建一个数据包
                byte[] bys = new byte[1024];
                DatagramPacket dp = new DatagramPacket(bys, bys.length);

                // 接收数据
                ds.receive(dp);

                // 解析数据
                String ip = dp.getAddress().getHostAddress();
                String s = new String(dp.getData(), 0, dp.getLength());
                System.out.println("地址: " + ip + " 数据: " + s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

class SendThread implements Runnable {

    private DatagramSocket ds;

    public SendThread(DatagramSocket ds) {
        this.ds = ds;
    }

    @Override
    public void run() {
        try {
            // 封装键盘录入数据
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    System.in));
            String line = null;
            while ((line = br.readLine()) != null) {
                if ("bye".equals(line)) {
                    break;
                }

                // 创建数据并打包
                byte[] bys = line.getBytes();
                DatagramPacket dp = new DatagramPacket(bys, bys.length,
                        InetAddress.getByName("192.168.1.101"), 12346);

                // 发送数据
                ds.send(dp);
            }

            // 释放资源
            ds.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 

TCP网络编程

  TCP网络编程是面向连接的,用到的套接字是Socket和ServerSocket,其中Socket是客户端用的套接字,其构造方法如下:

     Socket() 
          通过系统默认类型的 SocketImpl 创建未连接套接字
     Socket(InetAddress address, int port) 
          创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
     Socket(InetAddress host, int port, boolean stream) 
          已过时。 Use DatagramSocket instead for UDP transport.
     Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 
          创建一个套接字并将其连接到指定远程地址上的指定远程端口。
     Socket(Proxy proxy) 
          创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。
     protected    Socket(SocketImpl impl) 
          使用用户指定的 SocketImpl 创建一个未连接 Socket。
     Socket(String host, int port) 
          创建一个流套接字并将其连接到指定主机上的指定端口号。
     Socket(String host, int port, boolean stream) 
          已过时。 使用 DatagramSocket 取代 UDP 传输。
     Socket(String host, int port, InetAddress localAddr, int localPort) 
          创建一个套接字并将其连接到指定远程主机上的指定远程端口。

  常用的方法如下:

 void    close() 
          关闭此套接字。
 void    connect(SocketAddress endpoint) 
          将此套接字连接到服务器。
 InetAddress    getInetAddress() 
          返回套接字连接的地址。
 InputStream    getInputStream() 
          返回此套接字的输入流。
 OutputStream    getOutputStream() 
          返回此套接字的输出流。
 int    getPort() 
          返回此套接字连接到的远程端口。
 SocketAddress    getRemoteSocketAddress() 
          返回此套接字连接的端点的地址,如果未连接则返回 nullvoid    shutdownInput() 
          此套接字的输入流置于“流的末尾”。
 void    shutdownOutput() 
          禁用此套接字的输出流。

  ServerSocket套接字是服务器端的套接字,服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。其构造方法如下:

ServerSocket() 
          创建非绑定服务器套接字。
ServerSocket(int port) 
          创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog) 
          利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
ServerSocket(int port, int backlog, InetAddress bindAddr) 
          使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

  常用方法如下:

 Socket    accept() 
          侦听并接受到此套接字的连接。
 void    bind(SocketAddress endpoint) 
          将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
 void    bind(SocketAddress endpoint, int backlog) 
          将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
 void    close() 
          关闭此套接字。

  服务器端主要是获取连接上的客户端Socket,然后接可以获取对应的输入输出流进行读取和写入。

  另外,必须先启动服务器,因为TCP是面向连接的,要进行三次握手取得连接后才可以开始写或者读数据。

  下面给出一个客户端上传文件的多线程例子:

 

public class UploadClient {
    public static void main(String[] args) throws IOException {
        // 创建客户端Socket对象
        Socket s = new Socket("192.168.1.101", 12121);

        // 封装文本文件
        BufferedReader br = new BufferedReader(new FileReader(
                "UploadClient.java"));


        // 封装通道内流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                s.getOutputStream()));

        String line = null;
        while ((line = br.readLine()) != null) { // 阻塞读
            bw.write(line);
            bw.newLine();
            bw.flush();//刷新
        }

        // Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
        s.shutdownOutput();

        // 接收反馈
        BufferedReader brClient = new BufferedReader(new InputStreamReader(
                s.getInputStream()));
        String client = brClient.readLine(); // 阻塞
        System.out.println(client);

        // 释放资源
        br.close();
        s.close();
    }
}


public class UploadServer {
    public static void main(String[] args) throws IOException {
        // 创建服务器Socket对象
        ServerSocket ss = new ServerSocket(12121);

        while (true) {
            Socket s = ss.accept();//每接受一个Socket则新建一个对应的线程处理
            new Thread(new UserThread(s)).start();
        }
    }
}

public class UserThread implements Runnable {
    //Socket对象
    private Socket s;

    public UserThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            // 封装通道内的流
            br = new BufferedReader(new InputStreamReader(
                    s.getInputStream()));
            // 封装文本文件,为了防止名称冲突加上当前时间
            String newName = System.currentTimeMillis() + ".txt";
            bw = new BufferedWriter(new FileWriter(newName));

            String line = null;
            while ((line = br.readLine()) != null) { // 阻塞读
                bw.write(line);
                bw.newLine();
                bw.flush();//刷新
            }

            // 给出反馈,客户端可以终止
            BufferedWriter bwServer = new BufferedWriter(
                    new OutputStreamWriter(s.getOutputStream()));
            bwServer.write("文件上传成功");
            bwServer.newLine();
            bwServer.flush();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(bw!=null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(s!=null){
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

 

  

posted @ 2015-12-18 23:28  FightingBoy&Gril  阅读(352)  评论(0编辑  收藏  举报