网络模型
OSI七层参考模型和TCP/IP四层参考模型
主机至网络层(物理层 , 数据链路层) , 网际层 , 传输层 , 应用层(应用层 , 表示层 , 会话层)
物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。
数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP、UDP等。
会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求。
表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。
应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。
网络编程三要素
IP地址:网络中设备的标识
在Java中,InetAddress类表示互联网协议 (IP) 地址。
我么可以使用该类中的一些方法获取主机名,或者IP地址。
端口号: 用于标识进程的逻辑地址,每个网络程序都会有一个逻辑端口。端口的取值范围为0~65535(两个字节),其中0 ~1023系统使用或保留端口。
传输协议: 通讯的规则常见协议有TCP,UDP
传输协议
UDP
将数据、数据源、目的地封装到数据包中,不需要建立连接,每个数据包的大小限制在64k内。在发送数据后,如果目的地存在,就接收,不存在就将数据丢掉。
因为无连接,是不可靠协议,速度快。
TCP
建立连接,形成传输数据的通道,在连接中进行大数据量传输。是可靠协议,效率较低。
Socket就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输
UDP传输
DatagramSocket类表示用来发送和接收数据报包的套接字。
public void receive(DatagramPacket p) throws IOException
从此套接字接收数据报包。
public void send(DatagramPacket p) throws IOException
从此套接字发送数据报包
DatagramPacket类表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
public DatagramPacket(byte[] buf,int length)
构造DatagramPacket,用来接收长度为length的数据包。
步骤:
- 建立UDPSocket服务(找端点)
- 提供数据,将数据封装到数据包中
- 通过Socket服务的发送功能,将数据包发送出去
- 关闭资源
//发送端
import java.io.IOException;
import java.net.*;
public class UDPSendDemo {
public static void main(String[] args) throws IOException {
//创建UDP服务,通过DatagramSocket对象
//发送端可以使用默认端口
DatagramSocket ds = new DatagramSocket();
//将数据封装成数据包
byte[] bytes = "Hello UDP".getBytes();
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.47.102"), 9999);
//通过Socket服务,通过send方法将已有的数据包发送出去
ds.send(dp);
//关闭资源
ds.close();
}
}
步骤:
- 定义UDPSocket服务,通常会监听一个端口,给接收网络应用程序定义一个标识。方便于明确哪些数据过来该应用程序可以处理。如果不指定端口标识,系统会自动分配一个端口,但是该端口与发送端的目标端口可能不一致,最终导致数据丢失。
- 定义一个数据包存储接收到的数据,利用数据包对象的方法提取字节数据的信息
- 通过Socket服务的receive方法将收到的数据存入定义好的数据包中
- 通过数据包对象的特有功能,将数据取出,打印在控制台上
- 关闭资源
//接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiveDemo {
public static void main(String[] args) throws IOException {
//1.创建UDPSocket服务,建立端点
//创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket ds = new DatagramSocket(9999);
//2.定义数据包,用于存储数据
byte[] bytes=new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
//3.通过服务的receive方法,将收到的数据存入数据包中
//阻塞的方法,如果数据没过来,就等待在这里。
ds.receive(dp);
//4.通过数据包的方法获取其中的数据
String address = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
int port = dp.getPort();
System.out.println(address+"::"+data+"::"+port);
//5.关闭资源
ds.close();
}
}
UDP将键盘录入内容从发送端传输到接收端
//发送端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPSendDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line=null;
while ((line=reader.readLine())!=null){
if ("886".equals(line))
break;
byte[] bytes = line.getBytes();
//ip:"192.168.1.255" 为广播地址,给网络段中的所有机器发送消息
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.47.102"),8888);
ds.send(dp);
}
ds.close();
}
}
//接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(8888);
System.out.println("等待接收数据");
while (true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
String str = new String(data,0,dp.getLength());
if (str.equals("over"))
break;
System.out.println(str);
}
ds.close();
}
}
UDP传输通过多线程实现聊天室程序
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
public class ChatDemo {
public static void main(String[] args) throws SocketException {
/*收取数据和发送数据两部分同时进行就需要使用多线程技术。
一个线程收取数据,一个线程发送数据。
因为收发动作不一致,需要定义两个run方法,这两个方法要封装在不同的类中。
*/
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receiveSocket = new DatagramSocket(10000);
new Thread(new Send(sendSocket)).start();
new Thread(new Receive(receiveSocket)).start();
}
}
class Send implements Runnable{
private DatagramSocket ds;
public Send(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line=null;
while ((line=reader.readLine())!=null){
byte[] bytes = line.getBytes();
DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("192.168.0.102"),10000);
ds.send(dp);
}
}catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receive implements Runnable{
private DatagramSocket ds;
public Receive(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
while (true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
String hostName = dp.getAddress().getHostName();
System.out.println(hostName+"..."+new String(dp.getData(),0,dp.getLength()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP传输
public Socket(InetAddress address,int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
public InputStream getInputStream() throws IOException
返回此套接字的输入流。
public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
步骤:
- 创建Socket服务并指定要链接的主机和端口
- 获取Socket流中的输出流,将数据写到该流中,通过网络发送给服务端。
- 关闭客户端资源
//客户端
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
//创建客户端的Socket服务,指定目的主机和端口。
//如果通路建立成功,就会产生Socket流,包含输入流和输出流,可读写数据
Socket socket = new Socket("192.168.0.102", 10001);
//为了发送数据,要获取Socket流中的输出流
OutputStream out = socket.getOutputStream();
out.write("Hello TCP".getBytes());
//输出流通过Socket对象获取到的,Socket流关闭了,输出流自然也会被关闭,此处不需要再次关闭
socket.close();
}
}
步骤
- 建立服务端的Socket服务,通过ServerSocket。并监听一个端口
- 通过accept方法获取连接过来的客户端对象,这个方法是阻塞式的,没有连接就会等待
- 客户端如果发过来数据,服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据
- 关闭服务端(可选操作)
//服务端
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
//建立服务端的Socket服务,并监听一个端口
ServerSocket ss = new ServerSocket(10001);
//通过accept方法获取链接过来的Socket对象
//服务端没有流对象,所以要获取客户端的流对象与客户端进行通讯
Socket socket = ss.accept();
String hostName = socket.getInetAddress().getHostName();
System.out.println(hostName+"...connected");
//获取客户端发送过来的数据,使用客户端对象的读取流来读取数据
//源为网络流,因为数据从网络中读取到的
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len=in.read(buf);
System.out.println(new String(buf,0,len));
socket.close(); //关闭客户端
ss.close();
}
}
注意:在启动时,需要先启动服务端,如果先启动客户端,客户端连接不上服务端就会报错
TCP传输中,客户端和服务端的互访
//客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.0.102", 10002);
OutputStream out = socket.getOutputStream();
out.write("Hello".getBytes());
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len= in.read(buf);
System.out.println(new String(buf,0,len));
socket.close();
}
}
//接收端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10002);
Socket socket = ss.accept();
String hostName = socket.getInetAddress().getHostName();
System.out.println(hostName+"...connected");
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len=in.read(buf);
System.out.println(new String(buf,0,len));
OutputStream out = socket.getOutputStream();
out.write("已接受到数据".getBytes());
socket.close();
}
}
客户端键盘录入数据发送到服务端
import java.io.*;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.0.102", 9999);
//定义字符输出流将数据发送给服务端
OutputStream out = socket.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
//定义字符输入流获取键盘输入的数据
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//定义一个Socket读取流,读取服务端返回的信息
InputStream in = socket.getInputStream();
BufferedReader reader1 = new BufferedReader(new InputStreamReader(in));
String line=null;
while ((line=reader.readLine())!=null){
if ("over".equals(line))
break;
writer.write(line);
//键盘录入时没有换行,readLine读取不到换行符就会一直阻塞,所以要调用newLine方法
writer.newLine();
writer.flush();
String str = reader1.readLine();
System.out.println("Server:"+str);
}
//键盘录入的读取流与Socket无关
reader.close();
socket.close();
}
}
TCP客户端传输文本文件给服务端
import java.io.*;
import java.net.Socket;
public class TextClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.0.102", 8888);
BufferedReader reader = new BufferedReader(new FileReader("a.txt"));
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
String line=null;
while ((line=reader.readLine())!=null){
printWriter.println(line);
}
/*关闭客户端输出流,相当于给流中加入一个结束标记
如果没有标记,客户端因为在等待服务端的反馈数据,
所以处于阻塞状态,无法执行关闭资源的操作
导致服务端的readLine也处于阻塞状态,还在等待服务端发送数据*/
socket.shutdownOutput();
BufferedReader reader1 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str = reader1.readLine();
System.out.println(str);
reader.close();
socket.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TextServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
String hostName = socket.getInetAddress().getHostName();
System.out.println(hostName+"...connected");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter printWriter = new PrintWriter(new FileWriter("b.txt"), true);
String line=null;
while ((line=reader.readLine())!=null){
printWriter.println(line);
}
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
pw.println("上传成功");
printWriter.close();
socket.close();
ss.close();
}
}