简述网络编程

本文主要讲述关于网络编程的一些基本知识。每篇文章都在认真完成,如果文中有出错的地方还请各位及时指出,在下感激不尽。

概述

既然要说网络编程,那么什么是网络呢?简单点说,两台或者两台以上计算机就组成了网络。比如说我想通过 QQ 和你聊天,那么就会涉及到两个问题:第一,如何在众多电脑中找到你?;第二,找到你之后如何找到你的 QQ,然后和你聊天?

要解决这两个问题就需要两个东西:IP 地址和端口号。每台电脑都会有自己的 IP 地址,通过它来找到指定的电脑;每个程序又有自己的端口号,通过 IP 找到指定的电脑之后再通过端口号找到该电脑上指定的程序。

那么聊天过程中底层就会进行数据传输,数据传输肯定是有一定的规范、协议的,不能胡来吧,常见的协议有 TCP、UDP协议。

底层的数据传输的简图如下图所示:
数据传输.png

表面上看是在两台电脑间直接传输,实际过程如红线,经历了底层的实际数据传输过程。

那么底层的实际数据传输过程是怎样的呢?如下图所示:

底层传输.png

上图中的 ISO/OSI 参考模型又叫七层协议,只是理论理解,实际并没有得到应用。比如说电脑 1 的数据传输从上到下依次经过七层,数据到达电脑 2 之后又从下到上依次经过七层(如图数据传输)。

但是实际应用主要是左边的模型。每层都有自己的规则,传输层有 UDP、TCP 协议,网络层有 IP 协议,这是最主要的几个协议,所以这个模型又叫 TCP/IP 协议。这篇文章主要讲的就是传输层的 UDP、TCP 协议。

UDP、TCP 协议如下图:
UDP/TCP 协议.png

  • TCP 协议:如上图所示,比如说两个人打电话,电话1 要把数据传输给电话 2,那么数据可以选择不同的道路然后到达电话 2,也就是说条条大路通罗马。 有人把 TCP 协议称为三次握手,就是说,首先电话 1 把数据传给电话 2 (第一次握手),然后电话 2 再传数据给电话 1 告诉电话 1 我收到你的数据了(第二次握手),然后电话 1 再给电话 2 传数据告诉电话 2 我收到你反馈的数据了(第三次握手)。
  • UDP协议:比如说发短信,短信也是一条数据,UDP 协议就是说可以将数据分割成多份,每份独自进行传递,最后在目的地将分割的数据再收集起来。但是,UDP 有一个风险,就是分割之后的数据可能会存在走丢的情况,那么你发送的短信就存在对方收不到的风险。
TCP 协议 UDP 协议
优点 安全可靠 可靠性低
缺点 消耗资源,效率低 简单,开销小

1. InetAddress,InetSocketAddress

先讲两个类 InetAddress 和 InetSocketAddress。
InetAddress 封装的是 IP,先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 public class TestInetAddress {
2 //InetAddress---IP
3 public static void main(String[] args) throws UnknownHostException {
4 //创建一个InetAddress对象
5 //InetAddress ia=new InetAddress();//The constructor InetAddress() is not visible
6 //根本就没有构造器
7 //1.传入的是IP
8 InetAddress ia = InetAddress.getByName("192.168.1.250");
9 System.out.println("计算机名字:"+ia.getHostName());//计算机名
10 System.out.println("IP地址"+ia.getHostAddress());//IP地址
11 System.out.println(ia.getLocalHost());//计算机名和 IP 地址
12 System.out.println("=================");
13 //2.传入计算机名
14 InetAddress ia2 = InetAddress.getByName("计算机名");
15 System.out.println("计算机名字:"+ia2.getHostName());//计算机名
16 System.out.println("IP地址"+ia2.getHostAddress());//IP 地址
17 System.out.println("=================");
18 //3.传入localhost
19 InetAddress ia3 = InetAddress.getByName("localhost");//相当于你传入本机的IP
20 System.out.println("计算机名字:"+ia3.getHostName());//localhost
21 System.out.println("IP地址"+ia3.getHostAddress());//本机的 IP 地址
22 }
23 }

InetAddress 是一个类,如代码第 5 行所示,你如果想通过这种方式直接创建对象的话会有错误,The constructor InetAddress() is not visible,就是这个类根本就没有构造器。所以可以通过第 8 行的方式,调用 getByName() 方法,传进一个 IP 地址,然后这个方法会返回一个 InetAddress 类型的对象。getByName() 方法中可以传 IP 地址、计算机名、和 localhost(相当于传入本机的 IP 地址),然后通过产生的对象调用不同的方法得到相对应计算机的计算机名、IP 地址。

InetSocketAddress 可以传入 IP 地址和端口,代码如下:

1
2
3
1 //InetSocketAddress---可以支持IP,端口
2 InetSocketAddress isa=new InetSocketAddress("IP地址", 端口号);
3 System.out.println(isa.getHostName());//计算机名

InetSocketAddress 类可以直接创建对象然后传入 IP 地址和计算机名。

2. socket 套接字原理

如下图所示:

socket 原理(单向).png原理(TCP 协议单向传递数据):

客户端:

  1. 首先会根据 Socket 类创建 Socket 对象,该对象指定服务器端的 IP 和端口号。
  • 然后应用层通过创建的 Socket 对象的输出流向对应的服务器端传递数据(因为已经给了 IP 地址和端口号)。传输层指定用什么协议传递 。

服务端:

  1. 会根据 ServerSocket 类创建 ServerSocket 对象,并给该对象传入一个端口号,这个端口号必须和客户端指定的端口号一致。
  • 然后该对象等待接收传递来的信息,接收成功以后会返回一个 Socket 类型的对象(相当于成功接收信息之后创建了一个 Socket 类型的对象)。
  • 然后服务器端通过该对象的输入流读取传递过来的数据。

这就是我个人理解的原理,理解了原理之后再写程序应该就没什么问题了。

3. 基于 TCP 的网络编程

注意:TCP的时候,必须先启动服务端,再启动客户端!!

3.1 单向通信

注意:本文中的代码实现的是用自己的电脑模拟两台计算机。
代码如下:

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 public class TestClient {//客户端
2 public static void main(String[] args) throws UnknownHostException, IOException {
3 System.out.println("。。。客户端开始请求数据。。。");
4 //1.Socket类指定服务器端的IP和端口号
5 Socket s=new Socket("192.168.1.250", 8888);
6 //2.创建输出流向外传送数据:
7 OutputStream os = s.getOutputStream();
8 DataOutputStream dos=new DataOutputStream(os);
9 dos.writeUTF("约吗?");
10 //3.关闭流
11 dos.close();
12 os.close();
13 s.close();
14 }
15 }

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 public class TestServer { //服务器端
2 public static void main(String[] args) throws IOException {
3 System.out.println("服务器启动了。。。。");
4 //1.指定端口号:
5 ServerSocket ss=new ServerSocket(8888);
6 Socket s = ss.accept();//接收等待---阻塞状态
7 //2.创建输入流
8 InputStream is = s.getInputStream();
9 DataInputStream dis=new DataInputStream(is);
10 System.out.println("客户端对我说:"+dis.readUTF());
11 //3.关闭流
12 dis.close();
13 is.close();
14 ss.close();
15 s.close();
16 }
17 }

通过上面讲述的原理,然后再理解这两段代码应该没什么问题。如果有不理解的地方,对照着原理看代码,在此就不再赘述了。

3.2 双向通信

双向通信和单向通信本质上没什么区别,无非就是服务器端不仅能接收数据,还可以给客户端反馈数据;客户端不仅能发送数据,还能接收服务器端反馈回来的数据。

代码如下:

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1 public class TestClient {//客户端
2 public static void main(String[] args) throws UnknownHostException, IOException {
3 System.out.println("。。。客户端开始请求数据。。。");
4 //1.Socket类指定服务器端的IP和端口号
5 Socket s=new Socket("你自己的 IP 地址", 8888);
6 //2.我感受到的应用层 是一个输出流向外传送数据:
7 OutputStream os = s.getOutputStream();
8 DataOutputStream dos=new DataOutputStream(os);
9 dos.writeUTF("约吗?");
10 //3.接收服务器给的反馈:
11 InputStream is = s.getInputStream();
12 DataInputStream dis=new DataInputStream(is);
13 System.out.println("服务器端回应:"+dis.readUTF());
14 //4.关闭流
15 dos.close();
16 os.close();
17 s.close();
18 }
19 }

对比单向通信的客户端会发现,就多了一段接收服务器反馈数据的代码。而接收反馈的数据还是要用 Socket 对象的输入流来接收,然后再结合其他的 IO 流来读取数据。

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1 public class TestServer { //服务器端
2 public static void main(String[] args) throws IOException {
3 System.out.println("服务器启动了。。。。");
4 //1.指定端口号:
5 ServerSocket ss=new ServerSocket(8888);
6 Socket s = ss.accept();//接收等待---阻塞状态
7 //2.应用层感受到的是 你在操纵输入的流
8 InputStream is = s.getInputStream();
9 DataInputStream dis=new DataInputStream(is);
10 System.out.println("客户端对我说:"+s.getInetAddress().getHostAddress()+dis.readUTF());
11 //3.给客户端反应;
12 OutputStream os = s.getOutputStream();
13 DataOutputStream dos=new DataOutputStream(os);
14 dos.writeUTF("叔叔我们不约!");
15 //4.关闭流
16 dis.close();
17 is.close();
18 ss.close();
19 s.close();
20 }
21 }

同样的,对比单向通信中服务端代码可知,多了一段给客户端反馈数据的代码。而反馈数据同样是利用 Socket 对象的输出流,然后再结合其他的 IO 流来输出反馈信息。

3.3 对象流传送

功能:客户端发送用户名和密码给服务端,服务端判断对错之后给客户端进行反馈。

先上代码,如下:

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1 public class TestClient {//客户端
2 public static void main(String[] args) throws UnknownHostException, IOException {
3 System.out.println("。。。客户端开始请求数据。。。");
4 //1.Socket类指定服务器端的IP和端口号
5 Socket s=new Socket("你的 IP 地址", 8888);
6 //2.创建输出流向外传送数据:
7 OutputStream os = s.getOutputStream();
8 ObjectOutputStream dos=new ObjectOutputStream(os);
9 Scanner sc=new Scanner(System.in);
10 System.out.print("请录入账号:");
11 String name=sc.next();
12 System.out.print("请录入密码:");
13 String password=sc.next();
14 dos.writeObject(new Person(name, password));
15 //3.接收服务器给的反馈:
16 InputStream is = s.getInputStream();
17 DataInputStream dis=new DataInputStream(is);
18 System.out.println("服务器端回应:"+dis.readUTF());
19 //4.关闭流
20 dos.close();
21 os.close();
22 s.close();
23 dis.close();
24 is.close();
25 }
26 }

代码第 9 行到第 13 行实现键盘录入的功能。第 14 行通过对象流把对象传递给服务器端。第 16 行到第 18 行接收服务器端的反馈信息。

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1 public class TestServer { //服务器端
2 public static void main(String[] args) throws IOException, ClassNotFoundException {
3 System.out.println("服务器启动了。。。。");
4 //1.指定端口号:
5 ServerSocket ss=new ServerSocket(8888);
6 Socket s = ss.accept();//接收等待---阻塞状态
7 //2.创建输入流接收数据
8 InputStream is = s.getInputStream();
9 ObjectInputStream dis=new ObjectInputStream(is);
10 Person p =(Person)dis.readObject();//向下转型
11 //3.给客户端反应;
12 OutputStream os = s.getOutputStream();
13 DataOutputStream dos=new DataOutputStream(os);
14 //加入对用户的账号和密码进行校验
15 if(p.getName().equals("lili")&&p.getPassWord().equals("1919")){
16 dos.writeUTF("登陆成功!");
17 }else{
18 dos.writeUTF("用户名或者密码不正确,登陆失败!");
19 }
20 //4.关闭流
21 dis.close();
22 is.close();
23 ss.close();
24 s.close();
25 dos.close();
26 os.close();
27 }
28 }

代码第 10 行向下转型,将 Object 类型转化为 Person 类型的数据。第 15 行到第 19 行是对传进来的 Person 对象进行校验并给客户端反馈信息。

4. 基于 UDP 的网络编程

注意啦:

  • TCP 的时候,必须先启动服务器,再启动客户端。但是在 UDP 中并不是这样。
  • UDP 没有客户端和服务器的概念,只有发送方和接收方。
  • UDP通信机制:通过数据包(又称数据报)来传递数据。
  • 两个最重要的类:(1)DatagramSocket 此类用来发送和接收数据包的套接字;(2)DatagramPacket 此类表示数据包。

另外,

UDP 中不涉及 IO 流!
UDP 中不涉及 IO 流!
UDP 中不涉及 IO 流!

重要的事情说三遍。

原理(UDP 协议单向传递数据):

发送方:

  1. 创建套接字DatagramSocket(DatagramSocket ds=new DatagramSocket(xxxx);
    ),然后给套接字对象传入一个端口号,这个端口号是发送方的端口号!
  2. 创建数据包:封装要发送的信息,IP,端口(接收方的端口)等(详见后面代码);
  3. 通过套接字将数据包传出去。
  4. 关闭套接字。

接收方:

  1. 创建套接字 DatagramSocket (DatagramSocket ds=new DatagramSocket(xxxx);),给套接字对象传入接收方的端口号。
  2. 创建数据包;
  3. 套接字对象将发送方传过来的东西接收到这个空的数据包中;
  4. 关闭套接字。

另外需要说的地方就是,与 TCP 不同,在UDP中可以先启动发送方,但是可能会出现丢包现象,就是一部分数据可能会丢失。

4.1 单向通信

代码如下:

发送方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1 public class TestSend {//发送方
2 public static void main(String[] args) throws IOException {
3 System.out.println("发送方。。。。");
4 //1.创建套接字DatagramSocket---是发送方的端口
5 DatagramSocket ds=new DatagramSocket(8888);
6 //在这里可以指定发送方的端口号是8888,假如我不指定的话,那么会随机在我机器中不用的端口号中随机选择一个作为我发送方的端口。
7 //2.创建数据包:封装我要发送的信息,IP,端口等
8 String str="hello";
9 byte[] b = str.getBytes();
10 DatagramPacket dp=new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 9999);
11 //3.通过套接字将数据包传出去
12 ds.send(dp);
13 //4.关闭套接字
14 ds.close();
15 }
16 }

接收方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1 public class TestReceive {//接收方
2 public static void main(String[] args) throws IOException {
3 System.out.println("接收方。。。。");
4 //1.创建套接字 DatagramSocket --接收---接收方的端口号指定为9999
5 DatagramSocket ds=new DatagramSocket(9999);
6 //2.创建数据包
7 byte[] b=new byte[1024];
8 DatagramPacket dp=new DatagramPacket(b, b.length);
9 //3.将发送方传过来的东西接收到这个空的数据包中
10 ds.receive(dp);
11 //4.查看接收的信息
12 System.out.println("发送方说:"+new String(dp.getData(),0,dp.getLength()));
13 //5.关闭套接字
14 ds.close();
15 }
16 }

参考上面的原理来理解这两段代码,其实都是按照上面说的步骤来的,没什么好讲解的地方吧。

4.2 双向通信

双向通信和单向通信本质上也没什么区别,无非就是接收方不仅能接收数据,还可以给发送方反馈数据;发送方不仅能发送数据,还能接收接收方端反馈回来的数据。

发送方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 public class TestSend {//发送方
2 public static void main(String[] args) throws IOException {
3 System.out.println("发送方。。。。");
4 //1.创建套接字DatagramSocket---是发送方的端口
5 DatagramSocket ds=new DatagramSocket(8888);
6 //我在这里可以指定发送方的端口号是8888,假如我不指定的话,那么会随机在我机器中不用的端口号中随机选择一个作为我发送方的端口。
7 //2.创建数据包:封装我要发送的信息,IP,端口等
8 System.out.print("学生说:");
9 Scanner sc=new Scanner(System.in);
10 String str=sc.next();
11 byte[] b = str.getBytes();
12 DatagramPacket dp=new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 9999);
13 //3.通过套接字将数据包传出去
14 ds.send(dp);
15 //4.接收 接收方 的信息
16 byte[] b1=new byte[1024];
17 DatagramPacket dp1=new DatagramPacket(b1, b1.length);
18 ds.receive(dp1);
19 System.out.println("老师说:"+new String(dp1.getData(),0,dp1.getLength()));
20 //5.关闭套接字
21 ds.close();
22 }
23 }

与单向通信相比,多了第 16 行到第 19 行代码,创建数据包,套接字对象将接收方传过来的东西接收到这个空的数据包中,使发送方具备了接收的功能。

接收方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 public class TestReceive {//接收方
2 public static void main(String[] args) throws IOException {
3 System.out.println("接收方。。。。");
4 //1.创建套接字 DatagramSocket --接收---接收方的端口号指定为9999
5 DatagramSocket ds=new DatagramSocket(9999);
6 //2.创建数据包
7 byte[] b=new byte[1024];
8 DatagramPacket dp=new DatagramPacket(b, b.length);
9 //3.将发送方传过来的东西接收到这个空的数据包中
10 ds.receive(dp);
11 //4.查看接收的信息
12 System.out.println("学生说:"+new String(dp.getData(),0,dp.getLength()));
13 //5.给发送方进行回复
14 System.out.print("老师说:");
15 Scanner sc=new Scanner(System.in);
16 String str=sc.next();
17 byte[] b1 = str.getBytes();
18 DatagramPacket dp1=new DatagramPacket(b1, b1.length, InetAddress.getByName("localhost"), 8888);
19 ds.send(dp1);
20 //6.关闭套接字
21 ds.close();
22 }
23 }

代码第 14 行至第 16 行是程序具备了键盘录入的功能,不属于新知识点。第 17 行至第 19 行创建数据包封装要反馈的信息,IP,端口(发送方的端口)等(第 18 行),然后通过套接字将数据包传出去(第 19 行)。


网络编程就先写到这儿,欢迎补充。