1. 网络编程概念

1.1 软件结构

  • C/S结构(Client/Server):客户端与服务器结构;
  • B/S结构(Browser/Server):浏览器与服务器结构;

1.2 网络编程三要素

1.2.1 IP地址

  • 互联网协议地址(Internet Protocol Address),网络设备的唯一标识
  • IP地址的两种常用的地址形式:
  • IPv4: 32位的二进制数,分为4个字节,格式为a.b.c.d,每个字节范围是0~255;
  • IPv6: 128位的二进制数,每16个字节一组,共八组,格式为a:b:c:d:e:f:g:h,每个字节范围是0~255.

1.2.2 端口号

  • 设备中进程的唯一标识,实际上是一个十进制的整数。
  • 端口号是用两个字节表示的整数,取值范围0~65535。
  • 注意:0~1024一下的端口号是系统保留使用的,程序员要使用1024以上的端口号。

1.2.3 通信协议

  • 规定计算机之间数据传输的格式。

*通信协议中两种常用的协议:

TCP(Transmission Control Prorocol): 传输控制协议。TCP是面向连接的通信协议,在发送端和接收端监理逻辑连接,再传输数据


  • 特点:保证传输数据的安全

UDP(User Datagram Protocol): 用户数据报包协议。UDP是面向无连接的协议,是不可靠协议,传输速度快,但是容易丢失数据

根据Ip找主机,根据端口号找程序,根据协议确定传输数据的格式

1.3 InetAddress类的概述

  • 一个该类对象代表一个IP地址。

1.3.1常用方法

  • (无构造方法)
  • 静态方法:
- public static InetAddress getLocalHost()
  	  获得本地主机IP地址对象(主机名/IP地址字符串);
- public static InetAddress getName()
  	  根据主机名或IP地址字符串获得IP地址对象。

- public static InetAddress getLocalHost()
  	  获得本地主机IP地址对象(主机名/IP地址字符串);
- public static InetAddress getName()
  	  根据主机名或IP地址字符串获得IP地址对象。
  • 成员方法
- public String getHostName():获得主机名;
- public String getHostAddress():获得IP地址字符串。

- public String getHostName():获得主机名;
- public String getHostAddress():获得IP地址字符串。

2. UDP通信程序

2.1 概述

  • UDP在数据传输时,数据的发送端和接收端不建立逻辑连接。
  • 特点:
  • 面向无连接协议;
  • 资源消耗少,通信效率高,但不可靠;
  • 只负责发送,发送过程不会确认接收端是否接收到;
  • 基于数据包传输数据:将需发送的数据、源和接收端IP地址、端口号等信息封装成数据包发送;
  • 每个数据包的大小限制在64K内;
  • 使用场景:
  • 即时通讯(如qq)
  • 在线视频(如直播)
  • 网络语音通话(如微信语音聊天)

2.2 UDP通讯使用的类

2.2.1 DatagramPacket类(数据包)

2.2.1.1 概述及作用
  • 用来封装发送端或接收端要发送或接收的数据,是数据包对象
2.2.1.2 构造方法
  • 创建发送端数据包:
- DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
		用于创建发送端数据包对象。

- DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
		用于创建发送端数据包对象。

其中:buf:字节数组,封装要发送的数据

length:要发送的内容长度,单位:字节

address:接收端的ip地址对象

port:接收端的端口号

  • 创建接收端数据包:
- DatagramPacket(byte buf[],int length) : 用于创建接收端数据包对象

- DatagramPacket(byte buf[],int length) : 用于创建接收端数据包对象

其中:buf:用于接收数据的数组

length: 能够接受内容的长度,即buf数组的长度,单位:字节

2.2.1.2 常用成员方法
- public int getLength():获得发送端实际发送的字节数或接收端实际接收到的字节数;
- public int getPort():获得发送端或接收端端口号

- public int getLength():获得发送端实际发送的字节数或接收端实际接收到的字节数;
- public int getPort():获得发送端或接收端端口号

2.2.2 DatagramSocket类(发送数据包)

2.2.2.1 概述及作用
  • DatagramSocket类实例对象可用来发送和接收数据包对象
2.2.2.2 构造方法
- DatagramSocket():创建发送端的发送对象(端口号随机生成)
- DatagramSocket(int port):根据指定端口号创建Socket对象(一般用在接收端)

- DatagramSocket():创建发送端的发送对象(端口号随机生成)
- DatagramSocket(int port):根据指定端口号创建Socket对象(一般用在接收端)
2.2.2.3 常用成员方法
- public void send(DatagramPacket p):发送数据包
- public void receive(DatagramPacket p):接收数据包
- public void close():关闭资源,释放端口号

- public void send(DatagramPacket p):发送数据包
- public void receive(DatagramPacket p):接收数据包
- public void close():关闭资源,释放端口号

2.2.3 示例代码

/*
发送端
*/
public class Notes01_UCPsender {
    public static void main(String[] args) throws Exception{
        //定义字符串,包含要发送的内容
        byte[] content = "你好".getBytes();
        //创建数据包对象
        DatagramPacket dp = new DatagramPacket(content, content.length, InetAddress.getLocalHost(),6666);//假设给本机的端口号为6666的程序发送数据
        /*注意,这里的内容长度不能直接用“你好”.length,因为此时的值是字符的个数而不是字节的个数!*/
        //创建发送对象
        DatagramSocket ds = new DatagramSocket();	//不指定端口号会随机生成端口号
        //使用发送对象发送数据包
        ds.send(dp);
        //关闭socket释放端口号
        ds.close();   
    }
}

/*
发送端
*/
public class Notes01_UCPsender {
    public static void main(String[] args) throws Exception{
        //定义字符串,包含要发送的内容
        byte[] content = "你好".getBytes();
        //创建数据包对象
        DatagramPacket dp = new DatagramPacket(content, content.length, InetAddress.getLocalHost(),6666);//假设给本机的端口号为6666的程序发送数据
        /*注意,这里的内容长度不能直接用“你好”.length,因为此时的值是字符的个数而不是字节的个数!*/
        //创建发送对象
        DatagramSocket ds = new DatagramSocket();	//不指定端口号会随机生成端口号
        //使用发送对象发送数据包
        ds.send(dp);
        //关闭socket释放端口号
        ds.close();   
    }
}
/*
接收端
*/
public class Notes01_UCPreceiver {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket
        DatagramSocket ds = new DatagramSocket(6666);//假设端口是6666
        //创建字节数组:存储接收到的实际内容
        byte[] buf = new byte[1024];
        //创建数据包对象,封装接收到的数据
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        //接收数据包
        ds.receive(dp);
        /*
        以下的操作是对接收到的数据进行操作,不执行不会影响文件的传输
        接收到的数据都在dp中,所以需要用dp调用方法
         */
        //获得实际接收到的字节个数
        int len = dp.getLength();
        System.out.println("接收到的字节个数为:" + len);
        //将字节数组转换为字符串输出
        System.out.println(new String(buf,0,len));
        //获得发送端的IP地址
        String sendIP = dp.getAddress().getHostAddress();//getAddress()是获得发送端地址信息
        //获得发送端的端口号
        int sendPort = dp.getPort();
        System.out.println("sendIP = " + sendIP);
        System.out.println("sendPort = " + sendPort);
        //关闭socket释放端口号
        ds.close();
    }
}

/*
接收端
*/
public class Notes01_UCPreceiver {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket
        DatagramSocket ds = new DatagramSocket(6666);//假设端口是6666
        //创建字节数组:存储接收到的实际内容
        byte[] buf = new byte[1024];
        //创建数据包对象,封装接收到的数据
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        //接收数据包
        ds.receive(dp);
        /*
        以下的操作是对接收到的数据进行操作,不执行不会影响文件的传输
        接收到的数据都在dp中,所以需要用dp调用方法
         */
        //获得实际接收到的字节个数
        int len = dp.getLength();
        System.out.println("接收到的字节个数为:" + len);
        //将字节数组转换为字符串输出
        System.out.println(new String(buf,0,len));
        //获得发送端的IP地址
        String sendIP = dp.getAddress().getHostAddress();//getAddress()是获得发送端地址信息
        //获得发送端的端口号
        int sendPort = dp.getPort();
        System.out.println("sendIP = " + sendIP);
        System.out.println("sendPort = " + sendPort);
        //关闭socket释放端口号
        ds.close();
    }
}

2.3 UCP通信图解

java 网络编程实例 java基础网络编程_java 网络编程实例

3. TCP通信程序

3.1 TCP协议概述

  • TCP协议是面向连接的通信协议,即在传输数据前先在客户端和服务器端建立逻辑连接,然后再传输数据
  • 特点:
  • 面向连接的协议;
  • 通过三次握手建立连接,形成传输数据的通道;
  • 通过四次挥手断开连接;
  • 效率稍低但是可靠协议;
  • TCP是基于IO流传输数据;
  • 传输数据大小不受限制。
  • 使用场景:
  • 文件传输
  • 发送或接受邮件
  • 远程登录
  • 流程(三次握手):
  • 客户端向服务器段发出连接请求,等待服务器确认;
  • 服务器端向客户端会送一个响应,通知客户端已收到连接请求;
  • 客户端再次向服务器段发送确认信息,确认连接。
  • 然后客户端和服务器可以开始数据传输

3.2 TCP相关的两个类

3.2.1 Socket类(客户端)

3.2.1.1 概念
  • 该类的对象就代表一个客户端程序;
  • 该类实现客户端套接字

套接字指的是两台设备之间通讯的端点。也就是Socket对象

3.2.1.2 常用构造方法
- public Socket(String host, int port)
		创建套接字对象并将其连接到指定逐级上的指定端口号。

- public Socket(String host, int port)
		创建套接字对象并将其连接到指定逐级上的指定端口号。

其中:host:服务器IP地址(字符串形式)

port:服务器端口号

一旦执行该方法,就会立即连接指定的服务器,如果服务器没有开启,则连接失败抛出异常!!!

3.2.1.3 成员方法
- public InputStream getInputStream():返回此套接字的输入流
- public OutputStream getOutputStream():返回此套接字的输出流

- public InputStream getInputStream():返回此套接字的输入流
- public OutputStream getOutputStream():返回此套接字的输出流
  • 如果此Socket具有相关联的通道,则生成的InputStream/OutputStream的所有操作也关联该通道;
  • 关闭生成的InputStream/OutputStream也将关闭相关的Socket。
- public void close() :关闭此套接字。

- public void close() :关闭此套接字。
  • 一旦一个Socket被关闭,它不 可再使用;
  • 关闭此Socket也将关闭相关的InputStream/OutputStream。
- public void shutdownOutput()
		禁用此套接字的输出流。(也就是告知服务器数据已传输完毕)

- public void shutdownOutput()
		禁用此套接字的输出流。(也就是告知服务器数据已传输完毕)
  • 任何先前写出的数据将被发送,随后终止输出流。
3.2.1.4 TCP客户端代码实现步骤
  1. 创建客户端的Socket对象并指定需要连接的服务器的端口号;
  2. 调用Socket对象的getOutputStream方法获得字节输出流对象;
  3. 调用字节输出流对象的write方法往服务器端输出数据;
  4. 调用Socket对象的getInputStream方法获得字节输入流对象;
  5. 调用字节输入流对象的read方法读取服务器端返回的数据;
  6. 关闭Socket对象断开连接。

3.2.2 ServerSocket类(服务器端)

3.2.2.1 概念
  • 该类的对象就代表一个服务器端程序
  • 该类实现了服务器套接字,相当于开启一个服务器,该对象等待通过网络的请求。
3.2.2.2 构造方法
- public ServerSocket(int port) 
		使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。

- public ServerSocket(int port) 
		使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
3.2.2.3 成员方法
- public Socket accept()
		等待客户端连接,并返回一个与客户端相关的Socket对象,用于和客户端实现通信。
		该方法会一直阻塞直到建立连接。
- public void close() :关闭serverSocket,相当于关闭服务器,一般不会关闭

- public Socket accept()
		等待客户端连接,并返回一个与客户端相关的Socket对象,用于和客户端实现通信。
		该方法会一直阻塞直到建立连接。
- public void close() :关闭serverSocket,相当于关闭服务器,一般不会关闭
3.2.2.4 TCP服务器端代码实现步骤
  1. 创建ServerSocket对象并指定端口号;
  2. 调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象;
  3. 调用Socket对象的getInputStream方法获得字节输入流对象;
  4. 调用字节输入流对象的read方法读取客户端发送的数据;
  5. 调用Socket对象的getOutputStream方法获得字节输出流对象;
  6. 调用字节输出流对象的write方法往客户端返回数据;
  7. 调用close方法关闭Socket和ServerSocket对象。(实际开发不会关闭ServerSocket)

3.2.3 TCP示例代码

/*
客户端实现
*/
public class Notes02_TCPclient {
    public static void main(String[] args) throws Exception{
        //创建客户端Socket对象,相当于开启一个客户端程序
        //假设给本地传输数据,端口假设为8888
        //注意,客户端的端口号要与服务器的对应,否则会无法连接而报错
        Socket socket = new Socket("127.0.0.1",8888);
        //调用Socket对象的getOutputStream方法获得字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往服务器端输出数据
        os.write("你好吗?".getBytes());
        //注意要用shutdownOutput方法终止客户端传输数据!!!否则服务器有可能会一直处于接收数据阶段
        socket.shutdownOutput();
        /*
        从服务器端接收信息
         */
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取服务器端返回的数据
        byte[] content = new byte[1024];
        int len = -1;
        while ((len = is.read(content)) != -1) {
            System.out.println(new String(content,0,len));
        }
        //关闭Socket对象断开连接,自动将相关联的流关闭
        socket.close();
    }
}

/*
客户端实现
*/
public class Notes02_TCPclient {
    public static void main(String[] args) throws Exception{
        //创建客户端Socket对象,相当于开启一个客户端程序
        //假设给本地传输数据,端口假设为8888
        //注意,客户端的端口号要与服务器的对应,否则会无法连接而报错
        Socket socket = new Socket("127.0.0.1",8888);
        //调用Socket对象的getOutputStream方法获得字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往服务器端输出数据
        os.write("你好吗?".getBytes());
        //注意要用shutdownOutput方法终止客户端传输数据!!!否则服务器有可能会一直处于接收数据阶段
        socket.shutdownOutput();
        /*
        从服务器端接收信息
         */
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取服务器端返回的数据
        byte[] content = new byte[1024];
        int len = -1;
        while ((len = is.read(content)) != -1) {
            System.out.println(new String(content,0,len));
        }
        //关闭Socket对象断开连接,自动将相关联的流关闭
        socket.close();
    }
}
/*
服务器端实现
*/
public class Notes02_TCPserver {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象并指定端口号
        ServerSocket serverSocket = new ServerSocket(8888);
        //调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象
        Socket socket = serverSocket.accept();
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取客户端发送的数据
        byte[] buf=new byte[1024];
        int len = -1;
        while((len=is.read(buf)) != -1){
            System.out.println(new String(buf,0,len));
        }
        /*
        返回信息给客户端
         */
        //调用Socket对象的getOutputStream方法获取字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往客户端返回数据
        os.write("很好,已经连上了".getBytes());
        //调用close方法关闭Socket和ServerSocket对象
        socket.close();
        //实际上一般不会关闭服务器
        serverSocket.close();
    }
}

/*
服务器端实现
*/
public class Notes02_TCPserver {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象并指定端口号
        ServerSocket serverSocket = new ServerSocket(8888);
        //调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象
        Socket socket = serverSocket.accept();
        //调用Socket对象的getInputStream方法获得字节输入流对象
        InputStream is = socket.getInputStream();
        //调用字节输入流对象的read方法读取客户端发送的数据
        byte[] buf=new byte[1024];
        int len = -1;
        while((len=is.read(buf)) != -1){
            System.out.println(new String(buf,0,len));
        }
        /*
        返回信息给客户端
         */
        //调用Socket对象的getOutputStream方法获取字节输出流对象
        OutputStream os = socket.getOutputStream();
        //调用字节输出流对象的write方法往客户端返回数据
        os.write("很好,已经连上了".getBytes());
        //调用close方法关闭Socket和ServerSocket对象
        socket.close();
        //实际上一般不会关闭服务器
        serverSocket.close();
    }
}

3.3 TCP通信分析

1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。

1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。

此时客户端向服务端发送数据成功

此后服务端向客户端回写数据

6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。

6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。

图解如下:

java 网络编程实例 java基础网络编程_Java_02

4. 疑问

Q1:在传输数据时,什么场景下用shutdownOutput?为何有时不用shutdownOutput终止数据输出?

A:包含但不限于以下的情形,shutdownOutput可以不使用:

  • 数据输出后不需要接受对方放回的数据,直接close的情况。在该情况下socket对象在close时会自动告知对方数据已传输结束。此后这个socket就不能再数据传输;
  • 传输数据时使用字符输出/输入流,在调用wrtie方法输出一行数据后再调用newLine()方法时,newLine()相当于在当行数据末尾加上一个终止的标识,那么对方用readLine()的时候就会识别出该行数据已传输结束。此时不再需要用shutdownOutput关闭输出流来告知对方该行数据已传输结束。

另外要留意:

  • 输出数据时一般需要结束标识告知对方数据已传输完毕,如果没有,则需要调用shutdownOutput来告知。不过一旦调用,该socket就不能输出数据。
  • 一旦调用了shutdownOutput,该socket就不能再使用输出流,再次用getOutputStream也不会再重新打开,只能重新连接(重新获得socket)。
  • 一旦调用close,则整个socket都不能用,包括相关联的输出/输入流

Q2:Socket为何在传输后要关闭?

A:因为Socket的使用会占用端口,而端口的数量是有限的。如果不关闭,则端口一直被占用无法在被使用。所以需要关闭socket释放端口。此外socket关闭的同时也会关闭与之关联的流对象,这样也会释放流对象关联的文件资源。