概述

网络编程是指编写运行在多个设备(计算机)的程序,这些设备之间在网络中进行数据的传递、发送、接受数据

网络基础知识

网络模型是计算机网络通讯规范,然后再做实现

OSI及TCP/IP参考模型

image.png

IP地址

  • IP是每台电脑在互联网上的唯一标识符

IP地址表示

  • IPv4

它是由一个4字节32位的整数表示,为了方便记忆,使用点分10进制表示D.D.D.D,每8位使用一个0-255表示,用 . 分隔。 平时见到的 如192.168.0.123 这种就是 IPv4地址表示。

由于 表示容量的 问题 ,IPv4 早已不够使用,它的下一个版本IPv6 拥有更大的表示范围。

  • IPv6

它是由一个16字节128位的整数表示,表示的范围更大,也就意味着IP地址越多,夸张到 沙漠中每一粒沙子都可以分配到一个IP,它使用8段16进制表示,每段0-65535。X.X.X.X.X.X.X.X 。

InetAddress、InetSocketAddress

  • **InetAddress**:封装了**ip** ```java public class InetAddressDemo { public static void main(String[] args) throws UnknownHostException {

    1. //InetAddress ia = new InetAddress(); 不能直接创建对象,因为InetAddress被default修饰了
    2. InetAddress ia = InetAddress.getByName("localhost");//localhost指本机的ip地址
    3. System.out.println(ia);
    4. InetAddress ia1 = InetAddress.getByName("127.0.0.1");//127.0.0.1指本机的ip地址
    5. System.out.println(ia);
    6. InetAddress ia2 = InetAddress.getByName("www.baidu.com");//封装域名
    7. System.out.println(ia2);
    8. System.out.println(ia2.getHostName());//获取域名
    9. System.out.println(ia2.getHostAddress());//获取ip地址

    } }

  1. <a name="PEtTX"></a>
  2. ##
  3. `**InetSocketAddress**`:封装了`**IP**`、`**端口号**`
  4. ```java
  5. public class Test02 {
  6. public static void main(String[] args) {
  7. InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
  8. System.out.println(isa);
  9. System.out.println(isa.getHostName());
  10. System.out.println(isa.getPort());
  11. InetAddress ia = isa.getAddress();
  12. System.out.println(ia.getHostName());
  13. System.out.println(ia.getHostAddress());
  14. }
  15. }

端口号

通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过**端口号**区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是**0~65535**,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。

常见默认端口

MySQL 3306
Tomcat 8080
HTTP 80
HTTP 443
SSH 22
FTP 21

TCP/UDP

TCP
TCP协议通过三次握手协议将客户端与服务器端连接,两端使用各自的Socket对象。Socket对象中包含了IO流,供数据传输。
image.png

1、建立连接协议(三次握手)
(1) 客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2)服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行 数据通讯。
(3) 客户必须再次回应服务段一个ACK报文,这是报文段3。

2、连接终止协议(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
(4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

TCP 特点

  • TCP是面向连接的运输层协议。应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接。
  • 每一条TCP连接只能有两个端点,每一条 TCP 连接只能是点对点的(一对一)
  • TCP提供可靠交付的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。
  • TCP提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接受缓存,用
    来临时存放双向通信的数据。
  • 面向字节流。TCP中的“流”指的是流入到进程或从进程流出的字节序列

TCP协议实现
Socket客户端和ServerSocket服务器端:
1、建立客户端和服务器端;
2、建立连接后,通过Socket中的IO流(Socket流)进行数据的传输;
(如果是服务器端,则需要添加一步操作:通过Socket服务获取Socket,再获取其中的IO流)
3、关闭socket。

注意点:
1、服务器端开启后等待客户端访问,可以不关闭;
2、一个服务端可以对应多个客户端;
3、不同客户端间通信可以通过服务器端中转信息。

UDP
UDP 特点

  • UDP是无连接的,即发送数据之前不需要建立连接(当然,发送数据结束时也没有连接可以释放),因此减少了开销和发送数据之前的时延。
  • UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表(这里面有很多参数)。
  • UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这就是说,应 用层交给UDP多长 的报文,UDP就照样发送,即一次发送一个报文。
  • UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。但是不使用拥塞控制功能的UDP有可能会引起网络产生严重的拥塞问题。
  • UDP支持一对一、一对多、多对一和多对多的交互通信。
  • UDP的首部开销小,只有8个字节,比TCP的20个字节的首部还要短。

UDP协议实现
发送和接收均可以使用的Socket类:
DatagramSocket
数据打包的相关类:
DatagramPacket
使用UDP发送或接收数据的步骤:
1、建立发送端,接收端;
2、创建数据;
3、建立数据包;
4、调用Socket的发送、接收方法;
5、关闭Socket。

Socket编程

套接字使用**TCP**提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信
第17章:网络编程 - 图4

两台计算机之间使用套接字建立TCP连接步骤

  • 服务器实例化一个 ServerSocket对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket 类的 **accept()** 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个 Socket对象,指定服务器IP端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,**accept() **方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
  • 连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

单向通信

客户端:

  1. public class TestClient {
  2. public static void main(String[] args) throws IOException {
  3. // 1.创建套接子,指定服务器ip和端口号
  4. Socket s = new Socket("localhost", 8888);
  5. // 2.利用OutputStream向外发送数据,但是没有直接发送String方法
  6. OutputStream os = s.getOutputStream();
  7. // 3. OutputStream外套一层DataOutputStream
  8. DataOutputStream dos = new DataOutputStream(os);
  9. dos.writeUTF("你好啊");
  10. // 4.关闭流+关闭网络资源
  11. dos.close();
  12. os.close();
  13. s.close();
  14. }
  15. }

服务端:

  1. public class TestServer {
  2. public static void main(String[] args) throws IOException {
  3. // 1.创建套接子,指定服务器端口号
  4. ServerSocket ss = new ServerSocket(8888);
  5. /*
  6. 2.等待客服端发来信息
  7. 阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
  8. 接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
  9. */
  10. Socket s = ss.accept();
  11. // 3.操作流
  12. InputStream is = s.getInputStream();
  13. DataInputStream dis = new DataInputStream(is);
  14. // 4.读取客户端发来的数据
  15. String str = dis.readUTF();
  16. System.out.println("客户端发来的数据:"+str);
  17. // 5.关闭流+关闭网络资源
  18. dis.close();
  19. is.close();
  20. s.close();
  21. ss.close();
  22. }
  23. }

双向通信

客户端:

  1. public class TestClient {
  2. public static void main(String[] args) throws IOException {
  3. // 1.创建套接子,指定服务器ip和端口号
  4. Socket s = new Socket("localhost", 8888);
  5. // 2.利用OutputStream向外发送数据,但是没有直接发送String方法
  6. OutputStream os = s.getOutputStream();
  7. // 3. OutputStream外套一层DataOutputStream
  8. DataOutputStream dos = new DataOutputStream(os);
  9. dos.writeUTF("你好啊");
  10. // 4.接受服务端的回话
  11. InputStream is = s.getInputStream();
  12. DataInputStream dis = new DataInputStream(is);
  13. String str = dis.readUTF();
  14. System.out.println("服务端对我说:"+str);
  15. // 5.关闭流+关闭网络资源
  16. dis.close();
  17. is.close();
  18. dos.close();
  19. os.close();
  20. s.close();
  21. }
  22. }

服务端:

  1. public class TestServer {
  2. public static void main(String[] args) throws IOException {
  3. // 1.创建套接子,指定服务器端口号
  4. ServerSocket ss = new ServerSocket(8888);
  5. /*
  6. 等待客服端发来信息
  7. 阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
  8. 接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
  9. */
  10. Socket s = ss.accept();
  11. // 2.操作流
  12. InputStream is = s.getInputStream();
  13. DataInputStream dis = new DataInputStream(is);
  14. // 3.读取客户端发来的数据
  15. String str = dis.readUTF();
  16. System.out.println("客户端发来的数据:"+str);
  17. // 4.向客户端输出话
  18. OutputStream os = s.getOutputStream();
  19. DataOutputStream dos = new DataOutputStream(os);
  20. dos.writeUTF("你好,我是服务端,我接受到你的请求了");
  21. // 5.关闭流+关闭网络资源
  22. dos.close();
  23. os.close();
  24. dis.close();
  25. is.close();
  26. s.close();
  27. ss.close();
  28. }
  29. }

对象流传送

客户端:

  1. public class TestClient {
  2. public static void main(String[] args) throws IOException {
  3. // 1.创建套接子,指定服务器ip和端口号
  4. Socket s = new Socket("localhost", 8888);
  5. // 2.录入用户账号密码
  6. Scanner sc = new Scanner(System.in);
  7. System.out.println("请输入你的账号");
  8. String name = sc.next();
  9. System.out.println("请输入你的密码");
  10. String pwd = sc.next();
  11. // 3.将账号和密码封装为一个User对象
  12. User user = new User(name, pwd);
  13. // 4.利用OutputStream向外发送数据,但是没有直接发送String方法
  14. OutputStream os = s.getOutputStream();
  15. ObjectOutputStream oos = new ObjectOutputStream(os);
  16. oos.writeObject(user);
  17. // 5.OutputStream外套一层DataOutputStream
  18. DataOutputStream dos = new DataOutputStream(os);
  19. dos.writeUTF("你好啊");
  20. // 6.接受服务端的回话
  21. InputStream is = s.getInputStream();
  22. DataInputStream dis = new DataInputStream(is);
  23. boolean b = dis.readBoolean();
  24. if(b){
  25. System.out.println("恭喜,登录成功");
  26. }else {
  27. System.out.println("对不起,登录失败");
  28. }
  29. // 7.关闭流+关闭网络资源
  30. dis.close();
  31. is.close();
  32. oos.close();
  33. os.close();
  34. s.close();
  35. }
  36. }

服务端:

  1. public class TestServer {
  2. public static void main(String[] args) throws IOException, ClassNotFoundException {
  3. // 1.创建套接子,指定服务器端口号
  4. ServerSocket ss = new ServerSocket(8888);
  5. /*
  6. 2.等待客服端发来信息
  7. 阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
  8. 接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
  9. */
  10. Socket s = ss.accept();
  11. // 3.操作流
  12. InputStream is = s.getInputStream();
  13. ObjectInputStream ois = new ObjectInputStream(is);
  14. // 4.读取客户端发来的数据
  15. User user = (User) ois.readObject();
  16. // 5.对数据进行验证
  17. boolean flag = false;
  18. if(user.getName().equals("嘿嘿")&& user.getPwd().equals("1234567")){
  19. flag = true;
  20. }
  21. // 6.向客户端输出话
  22. OutputStream os = s.getOutputStream();
  23. DataOutputStream dos = new DataOutputStream(os);
  24. dos.writeBoolean(flag);
  25. // 7.关闭流+关闭网络资源
  26. dos.close();
  27. os.close();
  28. ois.close();
  29. is.close();
  30. s.close();
  31. ss.close();
  32. }
  33. }

user类

  1. public class User implements Serializable {
  2. private String name;
  3. private String pwd;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public String getPwd() {
  11. return pwd;
  12. }
  13. public void setPwd(String pwd) {
  14. this.pwd = pwd;
  15. }
  16. public User(String name, String pwd) {
  17. this.name = name;
  18. this.pwd = pwd;
  19. }
  20. }