1、网络编程#
1.1 网络架构模型#
网络架构模型主要有OSI模型和TCP/IP模型
1.1.1 OSI模型#
OSI(Open System Interconnect),开放式系统互联。OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)。它实际上并没有人推广,只是一种理想化的模型。
1.1.2 TCP/IP模型#
-
应用层
应用层最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。
-
传输层
建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
-
网络层
本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。
-
数据链路层
-
物理层
1.2 网络结构模式#
1.2.1 B/S#
即浏览器/服务器模式,B/S架构采取浏览器请求,服务器响应的工作模式。客户则在需要服务时向服务器进行请求。服务器响应后及时返回,不需要实时监听端口。
1.2.2 C/S#
即客户端/浏览器模式,通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。
1.3 TCP/UDP协议#
1.3.1 TCP协议#
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
建立起一个TCP连接需要经过“三次握手”:
-
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
-
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
-
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
断开连接时服务器和客户端均可以主动发起断开TCP连接的请求。
1.3.2 UDP协议#
UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
1.3.3 TCP与UDP的区别#
TCP
- 面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
- TCP传输数据没有大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
- TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
UDP
- 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
- UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
- UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
2、Socket 网络编程#
2.1 什么是Socket#
Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。
Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
2.2 Socket 的原理#
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
- 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
- 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
- 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
3、Java上的Socket网络编程#
Server端Listen监听某个端口是否有连接请求,Client端向Server 端发出连接请求,Server端向Client端发回Accept接受消息。这样一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
- 创建Socket;
- 打开连接到Socket的输入/出流;
- 按照一定的协议对Socket进行读/写操作;
- 关闭Socket。
3.1 基于TCP#
3.1.1 实现聊天#
客户端
|
import java.io.IOException; |
|
import java.io.OutputStream; |
|
import java.net.InetAddress; |
|
import java.net.Socket; |
|
import java.nio.charset.StandardCharsets; |
|
|
|
//客户端 |
|
public class TCPClient01 { |
|
public static void main(String[] args) { |
|
Socket socket = null; |
|
OutputStream os = null; |
|
try { |
|
//1、要知道服务器的地址 |
|
InetAddress serverIp = InetAddress.getByName("127.0.0.1"); |
|
//2、端口号 |
|
int port = 9999; |
|
//3、创建一个Socket连接 |
|
socket = new Socket(serverIp, port); |
|
//4、发送消息 |
|
os = socket.getOutputStream(); |
|
os.write("你好,欢迎光临喵小决".getBytes(StandardCharsets.UTF_8)); |
|
|
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
}finally { |
|
try { |
|
assert os != null; |
|
os.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
try { |
|
socket.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
|
} |
服务端
|
import java.io.ByteArrayOutputStream; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.net.ServerSocket; |
|
import java.net.Socket; |
|
|
|
//服务端 |
|
public class TCPServer01 { |
|
public static void main(String[] args) { |
|
ServerSocket serverSocket = null; |
|
Socket socket = null; |
|
InputStream is = null; |
|
ByteArrayOutputStream baos = null; |
|
|
|
try { |
|
//1、拥有一个地址 |
|
serverSocket = new ServerSocket(9999); |
|
//2、等待客户端连接过来 |
|
socket = serverSocket.accept(); |
|
//3、读取客户端的消息 |
|
is = socket.getInputStream(); |
|
//管道流 |
|
baos = new ByteArrayOutputStream(); |
|
byte[] buffer = new byte[1024]; |
|
int len; |
|
while ((len = is.read(buffer)) != -1) { |
|
baos.write(buffer, 0, len); |
|
} |
|
System.out.println(baos.toString()); |
|
|
|
|
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
}finally { |
|
//关闭资源 |
|
try { |
|
assert baos != null; |
|
baos.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
try { |
|
is.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
try { |
|
socket.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
try { |
|
serverSocket.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
|
} |
3.1.2 实现文件传输#
客户端
|
import java.io.*; |
|
import java.net.InetAddress; |
|
import java.net.Socket; |
|
//客户端 |
|
public class TCPClient02 { |
|
public static void main(String[] args) throws Exception { |
|
//1、创建一个Socket连接 |
|
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000); |
|
//2、创建一个输出流 |
|
OutputStream os = socket.getOutputStream(); |
|
//3、文件流 |
|
FileInputStream fis = new FileInputStream(new File("D:\\Project\\java\\TCP\\TCP\\src\\bg.png")); |
|
//4、写出文件 |
|
byte[] buffer = new byte[1024]; |
|
int len; |
|
while ((len = fis.read(buffer)) != -1) { |
|
os.write(buffer, 0, len); |
|
} |
|
|
|
//通知服务器,我已经结束了 |
|
socket.shutdownOutput();//我已经传输完了 |
|
|
|
//确定服务器接收完毕,才能够断开连接 |
|
InputStream is = socket.getInputStream(); |
|
//String byte[] |
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
byte[] buffer2 = new byte[1024]; |
|
int len2; |
|
while ((len2 = is.read(buffer2)) != -1) { |
|
baos.write(buffer2, 0, len2); |
|
} |
|
System.out.println(baos.toString()); |
|
|
|
//5、关闭资源 |
|
baos.close(); |
|
is.close(); |
|
fis.close(); |
|
os.close(); |
|
socket.close(); |
|
} |
|
} |
服务端
|
import java.io.*; |
|
import java.net.ServerSocket; |
|
import java.net.Socket; |
|
import java.nio.charset.StandardCharsets; |
|
|
|
//服务端 |
|
public class TCPServer02 { |
|
public static void main(String[] args) throws Exception { |
|
//1、创建服务 |
|
ServerSocket serverSocket = new ServerSocket(9000); |
|
//2、监听客户端的连接 |
|
Socket socket = serverSocket.accept();//阻塞式监听,会一直等待客户端连接 |
|
//3、获取输入流 |
|
InputStream is = socket.getInputStream(); |
|
//4、文件输出 |
|
FileOutputStream fos = new FileOutputStream(new File("receive.png")); |
|
byte[] buffer = new byte[1024]; |
|
int len; |
|
while ((len = is.read(buffer)) != -1) { |
|
fos.write(buffer, 0, len); |
|
} |
|
|
|
|
|
//通知客户端我接受完毕了 |
|
OutputStream os = socket.getOutputStream(); |
|
os.write("我接收完毕了,你可以断开了".getBytes(StandardCharsets.UTF_8)); |
|
|
|
//关闭资源 |
|
fos.close(); |
|
is.close(); |
|
socket.close(); |
|
serverSocket.close(); |
|
} |
|
} |
3.2 基于UDP#
3.2.1 消息发送#
客户端
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
import java.net.InetAddress; |
|
|
|
//不需要连接服务器 |
|
public class UdpClient01 { |
|
public static void main(String[] args) throws Exception { |
|
//1、建立一个Socket |
|
DatagramSocket socket = new DatagramSocket(); |
|
//2、建一个包 |
|
String msg = "你好!,服务器"; |
|
//发送给谁 |
|
InetAddress localhost = InetAddress.getByName("localhost"); |
|
int port = 9090; |
|
//数据,数据的长度起始,要发送给谁 |
|
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port); |
|
//3、发送一个包 |
|
socket.send(packet); |
|
//4、关闭流 |
|
socket.close(); |
|
} |
|
} |
服务器
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
|
|
//还是要等待客户端的连接 |
|
public class UdpServer01 { |
|
public static void main(String[] args) throws Exception { |
|
//开放端口 |
|
DatagramSocket socket = new DatagramSocket(9090); |
|
//接收数据包 |
|
byte[] buffer = new byte[1024]; |
|
//接收 |
|
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); |
|
socket.receive(packet);//阻塞接收 |
|
System.out.println(new String(packet.getData(),0,packet.getLength())); |
|
//关闭 |
|
socket.close(); |
|
} |
|
} |
3.2.2 聊天实现#
发送方
|
import java.io.BufferedReader; |
|
import java.io.InputStreamReader; |
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
import java.net.InetSocketAddress; |
|
import java.nio.charset.StandardCharsets; |
|
|
|
public class UdpSender { |
|
public static void main(String[] args) throws Exception { |
|
DatagramSocket socket = new DatagramSocket(8888); |
|
|
|
//准备数据:控制台读取System.in |
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); |
|
|
|
while (true) { |
|
String data = reader.readLine(); |
|
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); |
|
DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("localhost", 6666)); |
|
socket.send(packet); |
|
|
|
if (data.equals("bye")) { |
|
break; |
|
} |
|
} |
|
|
|
socket.close(); |
|
} |
|
} |
接收方
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
|
|
public class UdpReceive { |
|
public static void main(String[] args) throws Exception { |
|
DatagramSocket socket = new DatagramSocket(6666); |
|
|
|
while (true) { |
|
//准备接收的包裹 |
|
byte[] buffer = new byte[1024]; |
|
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); |
|
socket.receive(packet);//阻塞式接收包裹 |
|
|
|
//断开连接 bye |
|
byte[] data = packet.getData(); |
|
String receiveData = new String(data, 0, data.length); |
|
|
|
System.out.println(receiveData); |
|
|
|
if (receiveData.equals("bye")) { |
|
break; |
|
} |
|
} |
|
socket.close(); |
|
} |
|
} |
4、Socket和Thread结合#
使用Socket
和Thread
来实现多线程在线质询聊天
老师
|
public class TalkTeacher { |
|
public static void main(String[] args) { |
|
new Thread(new TalkSend(6666, "localhost", 8888)).start(); |
|
new Thread(new TalkReceive(9999, "学生")).start(); |
|
} |
|
} |
|
学生
|
public class TalkStudent { |
|
public static void main(String[] args) { |
|
new Thread(new TalkSend(7777, "localhost", 9999)).start(); |
|
new Thread(new TalkReceive(8888, "老师")).start(); |
|
} |
|
} |
发送方
|
import java.io.BufferedReader; |
|
import java.io.InputStreamReader; |
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
import java.net.InetSocketAddress; |
|
import java.nio.charset.StandardCharsets; |
|
|
|
public class UdpSender { |
|
public static void main(String[] args) throws Exception { |
|
DatagramSocket socket = new DatagramSocket(8888); |
|
|
|
//准备数据:控制台读取System.in |
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); |
|
|
|
while (true) { |
|
String data = reader.readLine(); |
|
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); |
|
DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("localhost", 6666)); |
|
socket.send(packet); |
|
|
|
if (data.equals("bye")) { |
|
break; |
|
} |
|
} |
|
|
|
socket.close(); |
|
} |
|
} |
接收方
|
import java.io.IOException; |
|
import java.net.DatagramPacket; |
|
import java.net.DatagramSocket; |
|
import java.net.SocketException; |
|
|
|
public class TalkReceive implements Runnable { |
|
DatagramSocket socket = null; |
|
private int port; |
|
private String msgFrom; |
|
|
|
public TalkReceive(int port, String msgFrom) { |
|
this.msgFrom = msgFrom; |
|
this.port = port; |
|
try { |
|
socket = new DatagramSocket(port); |
|
} catch (SocketException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
|
|
public void run() { |
|
while (true) { |
|
|
|
try { |
|
byte[] container = new byte[1024]; |
|
DatagramPacket packet = new DatagramPacket(container, 0, container.length); |
|
socket.receive(packet); |
|
byte[] data = packet.getData(); |
|
String receiveData = new String(data, 0, data.length); |
|
System.out.println(msgFrom + ":" + receiveData); |
|
if (receiveData.equals("bye")) { |
|
break; |
|
} |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
socket.close(); |
|
} |
|
} |