一、 网络编程概述
网络编程:编写的应用程序可以与网络上其他设备中的应用程序进行数据交互。
网路通信的基本架构:
CS架构(Client/Server):
CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。

BS架构(Browser/Server):
BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。

以后从事的工作方向主要还是BS架构的。
二、 网络编程三要素
IP地址、端口号、通信协议
- IP地址:设备在网络中的唯一标识
- 端口号:应用程序在设备中的唯一标识
- 协议:连接和数据在网络中的传输规则
假设现在要从一台电脑中的微信上,发一句“你愁啥?”到其他电脑的微信上,流程如下
1 2 3
| 1.先通过ip地址找到对方的电脑 2.再通过端口号找到对方的电脑上的应用程序 3.按照双方约定好的规则发送、接收数据
|
2.1 IP地址
IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一表示。IP地址分为:IPV4地址、IPV6地址
PV4地址由32个比特位(4个字节)组成,采用点分十进制。经过不断的发展,现在越来越多的设备需要联网,IPV4地址已经不够用了,所以扩展出来了IPV6地址。IPV6采用128位二进制数据来表示(16个字节),号称可以为地球上的每一粒沙子编一个IP地址。
域名:我们在浏览器上访问某一个网站是,就需要在浏览器的地址栏输入网址,这个网址的专业说法叫做域名。
域名和IP其实是一一对应的,由运营商来管理域名和IP的对应关系。我们在浏览器上敲一个域名时,首先由运营商的域名解析服务,把域名转换为ip地址,再通过IP地址去访问对应的服务器设备。
本地回环测试地址:127.0.0.1
2.2 InetAddress类
Java中也有一个类用来表IP地址,这个类是InetAddress类

1 2 3 4 5 6 7 8 9 10 11 12 13
| InetAddress ip1 = InetAddress.getLocalHost(); System.out.println(ip1.getHostName()); System.out.println(ip1.getHostAdress());
InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress());
System.out.println(ip2.isReachable(6000));
|
2.3 端口号
端口号:指的是计算机设备上运行的应用程序的标识,被规定为一个16位的二进制数据,范围(0~65535)
同一个计算机设备中,不能出现两个应用程序,用同一个端口号
2.4 协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
传输层有两个协议(UDP协议、TCP协议)


TCP协议特点:

三次握手:目的是确认通信双方,收发消息都是正常没问题的

四次挥手:目的是确保双方数据的收发已经完成,没有数据丢失

三、UDP通信
DatagramSocket类:完成基于UDP协议的收发数据,数据要以数据包的形式体现,一个数据包限制在64KB以内。
通信双方都需要有DatagramSocket(扔的行为),还需要有DatagramPacket(装数据的包)
案例:需要有两个程序,一个表示客户端程序,一个表示服务端程序。需求:客户端程序发一个字符串数据给服务端,服务端程序接收数据并打印。
3.1 客户端程序
数据bytes[] 装入 DatagramPacket packet
DatagramSocket socket 发送 packet
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 29 30 31 32 33 34 35 36 37
| import java.io.IOException; import java.net.*; public class Client {
public static void main(String[] args) throws Exception { DatagramSocket socket = new DatagramSocket(7777);
byte[] bytes = "我是客户端abc".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666); socket.send(packet);
System.out.println("客户端数据发送完毕"); socket.close();
} }
|
3.2 服务端程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.net.*; public class Server { public static void main(String[] args) throws Exception{ DatagramSocket socket = new DatagramSocket(6666); byte[] buffer = new byte[1024*64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); int len = packet.getLength();
String res = new String(buffer, 0, len); System.out.println(res); System.out.println(packet.getAddress().getHostAddress()); System.out.println(packet.getPort()); socket.close();
} }
|

四、UDP通信(多发多收)
需求:实现客户端不断的发数据,而服务端能不断的接收数据,客户端发送exit时客户端程序退出。
4.1 客户端程序
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import javax.sound.midi.Soundbank; import java.io.IOException; import java.net.*; import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception { DatagramSocket socket = new DatagramSocket(7777);
Scanner sc = new Scanner(System.in); while(true){ System.out.println("请说:"); String msg = sc.nextLine();
if("exit".equals(msg)){ System.out.println("通信结束!"); socket.close(); break; } byte[] bytes = msg.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);
socket.send(packet);
System.out.println("客户端数据发送中");
}
} }
|
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 24 25 26 27 28
| import java.net.*; public class Server { public static void main(String[] args) throws Exception{ DatagramSocket socket = new DatagramSocket(6666); byte[] buffer = new byte[1024*64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while(true){
socket.receive(packet); int len = packet.getLength();
String res = new String(buffer, 0, len); System.out.println(res); System.out.println(packet.getAddress().getHostAddress()); System.out.println(packet.getPort()); System.out.println("--------------------------------------");
}
} }
|
客户端:

服务端:

五、TCP通信(一发一收)
Java提供了一个java.net.Socket类来完成TCP通信。
- 当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。
- 现在假设客户端要发送一个“在一起”给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端
- 然后服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。
- 服务端处理完数据之后,假设需要把“没感觉”发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端
- 客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。

5.1 TCP客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.net.*; import java.io.*; public class Client1 { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1", 8888); OutputStream os = socket.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); dos.writeUTF("hello"); dos.close(); socket.close(); } }
|
5.2 TCP服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.net.*; import java.io.*;
public class Server1 { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); String rs = dis.readUTF(); System.out.println(rs); System.out.println(socket.getRemoteSocketAddress());
dis.close(); socket.close(); } }
|
六、TCP通信(多发多收)
把客户端代码改写一下,采用键盘录入的方式发消息,为了让客户端能够一直发,我们只需要将发送消息的代码套一层循环就可以了,当用户输入exit时,客户端退出循环并结束客户端。
6.1 TCP客户端
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 29 30 31
| import java.net.*; import java.io.*; import java.util.Scanner;
public class Client1 { public static void main(String[] args) throws Exception { Socket socket = new Socket("127.0.0.1", 8888); OutputStream os = socket.getOutputStream(); DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in); while(true){ System.out.println("请说:"); String msg = sc.nextLine(); if("exit".equals(msg)){ System.out.println("通信结束!"); dos.close(); socket.close(); break; } dos.writeUTF(msg); dos.flush(); }
} }
|
6.2 TCP服务端
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 29 30 31
| import java.net.*; import java.io.*;
public class Server1 { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); while (true){ try{ String rs = dis.readUTF(); System.out.println(rs);
}catch (Exception e){ System.out.println(socket.getRemoteSocketAddress() + "离线了"); dis.close(); socket.close(); break; } }
} }
|
七、TCP通信(多线程改进)
7.1 多线程改进
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。
每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。

服务端读取数据的线程
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 29 30 31 32 33 34
| import java.net.*; import java.io.*; public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; }
public void run(){ try{ InputStream is = socket.getInputStream(); DataInputStream dis = new DataInputStream(is); while(true){ try{ String msg = dis.readUTF(); System.out.println(msg); }catch (Exception e){ System.out.println("有人下线了:"+socket.getRemoteSocketAddress()); dis.close(); socket.close(); break; } }
}catch(Exception e){ e.printStackTrace(); } }
}
|
服务端主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.net.*; import java.io.*;
public class Server1 { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888);
while(true){ Socket socket = serverSocket.accept();
System.out.println("有人上线了:"+socket.getRemoteSocketAddress());
new ServerReaderThread(socket).start();
}
} }
|
八、BS架构程序

在BS结构的程序中,浏览器和服务器通信是基于HTTP协议来完成的,浏览器给客户端发送数据需要按照HTTP协议规定好的数据格式发给服务端,服务端返回数据时也需要按照HTTP协议规定好的数据给是发给浏览器,只有这两双方才能完成一次数据交互。
HTTP协议格式:

8.1 服务端程序
先写一个线程类,用于按照HTTP协议的格式返回数据
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 29 30 31
| import java.net.*; import java.io.*; public class ServerReadThread1 extends Thread{ private Socket socket; public ServerReadThread1(Socket socket){ this.socket = socket; }
public void run(){ try{ OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); ps.println("HTTP/1.1 200 OK"); ps.println("Content-Type:text/html;charset=UTF-8"); ps.println(); ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员<div>"); ps.close(); socket.close();
}catch (Exception e){ e.printStackTrace(); } }
}
|
服务端主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.net.*;
public class Server3 { public static void main(String[] args) throws Exception{ ServerSocket serverSocket = new ServerSocket(8088); while (true){ Socket socket = serverSocket.accept(); System.out.println("有人上线了:"+socket.getRemoteSocketAddress()); new ServerReadThread1(socket).start(); } } }
|
8.2 服务端主程序用线程池改进
先写一个给浏览器响应数据的线程任务
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 29 30 31
| import java.net.*; import java.io.*; public class ServerReaderRunnable implements Runnable{ private Socket socket;
public ServerReaderRunnable(Socket socket){ this.socket = socket;
}
@Override public void run() { try{ OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); ps.println("HTTP/1.1 200 OK"); ps.println("Content-Type:text/html;charset=UTF-8"); ps.println(); ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>"); ps.close(); socket.close();
}catch (Exception e){ e.printStackTrace(); }
} }
|
服务端的主程序,使用ThreadPoolExecutor创建一个线程池,每次接收到一个Socket就往线程池中提交任务就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.net.ServerSocket; import java.util.concurrent.*; import java.net.Socket;
public class Server4 {
public static void main(String[] args) throws Exception{ ServerSocket serverSocket = new ServerSocket(8088); ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2, 16*2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); while(true){ Socket socket = serverSocket.accept(); pool.execute(new ServerReaderRunnable(socket)); }
} }
|