计算机网络基础知识
通讯协议
协议protocol:通信双方必须遵循的规矩
网络通信协议:网络协议是构成网络的基本组件之一,协议是若干规则和协定的组合。
网络通信协议是分层的:一般指A机器的第n层与B机器的第n层的对话,这种对话中所使用的若干规则和约束便称为第n层网络协议。
TCP/IP协议
在Internet中TCP/IP协议是使用最为广泛的通讯协议(互联网上的一种事实的标准)。TCP/IP是英文Transmission Control Protocol/Internet Protocol的缩写,意思是“传输控制协议/网际协议”
TCP/IP 协议是一个工业标准协议套件,专为跨广域网(WAN)的大型互联网络而设计。
TCP/IP 网络体系结构模型就是遵循TCP/IP 协议进行通信的一种分层体系,现今,Internet和Intranet所使用的协议一般都为TCP/IP 协议。
在了解该协议之前,我们必须掌握基于该协议的体系结构层次,而TCP/IP体系结构分为四层。
- 第一层 网络接口层
包括用于协作IP数据在已有网络介质上传输的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。比如地址解析协议(Address Resolution Protocol, ARP )等。 - 第二层 网络层
对应于OSI模型的网络层,主要包含了IP、RIP等相关协议,负责数据的打包、寻址及路由。还包括网间控制报文协议(ICMP)来提供网络诊断信息。 - 第三层 传输层
对应于OSI的传输层,提供了两种端到端的通信服务,分别是TCP和UDP协议。 - 第四层 应用层
对应于OSI的应用层、表达层和会话层,提供了网络与应用之间的对话接口。包含了各种网络应用层协议,比如Http、FTP等应用协议。
IP地址和端口号
IP地址:网络中每台计算机的一个标识号
是一个逻辑地址 127.0.0.1 代表本机地址 localhost
端口号:具有网络功能的应用软件的标识号 //逻辑上的端口 501
端口是一个软件结构,被客户程序或服务程序用来发送和接收数据,一台服务器有 256*256个端口。 0 - 65535
0-1023是公认端口号,即已经公认定义或为将要公认定义的软件保留的
1024-65535是并没有公共定义的端口号,用户可以自己定义这些端口的作用。端口与协议有关:TCP和UDP的端口互不相干
InetAdress类
Java一切皆对象:这个类是对IP地址的封装
java.net.InetAddress类是java的IP地址封装类,内部隐藏了IP地址,可以通过它很容易的使用主机名以及IP地址。一般供各种网络类使用。直接由Object类派生并实现了序列化接口。该类用两个字段表示一个地址:hostName与address。hostName包含主机名,address包含IP地址。InetAddress支持ipv4与ipv6地址。
一些常用方法如下:
•static InetAddress getLocalHost():返回本地计算机的InetAddress。
•String getHostName():返回指定InetAddress对象的主机名。
•String getHostAddress():返回指定InetAddress对象的主机地址的字符串形式
•static InetAddress getByName(String hostname):使用DNS查找指定主机名或域名为hostname的IP地址,并返回InetAddress。
•byte[] getAddress():返回指定对象的IP地址的以网络字节为顺序的4个元素的字节数组。
应用举例:
InetAddress addr = InetAddress.getByName("java.sun.com")
System.out.println(addr);
以上代码将打印网址域名为java.sun.com的对应主机和IP地址信息。因此,在网络编程中,我们可以很方便的使用InetAddress类实现Ip地址的各种操作。
示例如下:
import java.net.*;
public class TestNet {
public static void main(String[] args) throws Exception{
// 获得本地主机的相关信息
InetAddress ia = InetAddress.getLocalHost();
// 获取本地ip地址
System.out.println(ia.getHostAddress());
// 获取本地主机名
System.out.println(ia.getHostName());
// 获取主机名为xiaoxiao的ip地址
System.out.println(InetAddress.getByName("xiaoxiao").getHostAddress());
// 获得指定域名的主机信息
System.out.println(InetAddress.getByName("java.sun.com"));
// 获得本地PC机名为PC-20131114BGRJ的所有ip地址
InetAddress[] ias = InetAddress.getAllByName("PC-20131114BGRJ");
for(InetAddress i : ias){
System.out.println(i.getHostAddress());
}
}
}
Main.java
public class Main {
public static void main(String[] args) throws IOException {
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost.getHostName());// 获取本机主机名
System.out.println(localHost.getHostAddress());// 获取本机IP地址
InetAddress byName = InetAddress.getByName("192.168.2.12");
System.out.println(byName.isReachable(2000));// 测试这个主机是否存1
for (int i = 1; i < 255; i++) {
final String ip = "192.168.2." + i;
final InetAddress address = InetAddress.getByName(ip);
// if (address.isReachable(2000)) {
// System.out.println(ip);
// }
new Thread(new Runnable() {
@Override
public void run() {
try {
if (address.isReachable(2500)) {
System.out.println(ip + "存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
TCP
TCP协议
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、基于IP的传输层协议。弥补了IP协议的不足,属于一种较高级的协议,它实现了数据包的有力捆绑,通过排序和重传来确保数据传输的可靠(即数据的准确传输以及完整性)。排序可以保证数据的读取是按照正确的格式进行,重传则保证了数据能够准确传送到目的地!
Java对tcp协议的支持
java.net包中定义了两个类ServerSocket 和Socket ,分别用来实现双向连接的server 端和client 端。
Socket
该类为建立连向服务器套接字及启动协议交换而设计,当进程通过网络进行通信的时候,java技术使用流模型来实现数据的通信。
一个Socket包括两个流,分别为一个输入流和一个输出流,一个进程如果要通过网络向另一个进程发送数据,只需简单地写入与Socket相关联的输出流,同样,一个进程通过从与Socket相关联的输入流来读取另一个进程所写的数据。
如果通过TCP协议建立连接,则服务器必须运行一个单独的进程来等待连接,而某一客户机必须试图到达服务器,就好比某人打电话,必须保证另一方等待电话呼叫,这样才能实现两人之间的通信。
示例:客户端
public class ClientSocket {
public static void main(String[] args) throws UnknownHostException,
IOException {
// 创建一个socket
Socket client = new Socket("192.168.1.105", 10000);
// 获取客户端输出流
OutputStream os = client.getOutputStream();
os.write(("客户端连接成功 \n").getBytes());
client.shutdownOutput();
os.close();
}
}
服务器端
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(1314);
Socket socket = server.accept();// 阻塞式方法,返回客户端对象
InputStream is = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(is));
System.out.println(bufferedReader.readLine());
}
}
客户端向服务器发送了一条消息
tcp实现群聊示例
客户端:
public class TcpClinet {
public static void main(String[] args) throws UnknownHostException,
IOException {
// 1、创建客户端套接字
Socket client = new Socket("192.168.1.105", 10000);
new ClientSend(client).start(); // 启动客户端发送信息的线程
new ClientReceive(client).start(); // 启动客户端接受信息的线程
}
}
服务器端:
/*
c/s client server 客户端/服务器
b/s browser server
*/
public class TcpServer {
public static void main(String[] args) throws IOException,
InterruptedException {
List<Socket> list = new ArrayList<>();
// 启动一个新的线程去处理这个客户端的交流
// 创建服务器断的套接字
ServerSocket server = new ServerSocket(10000);
// 等待客户端的链接。这个方法是一个阻塞式方法。当有客户端链接的时候,这个方法就会返回,返回链接的那个客户端
while (true) {
Socket socket = server.accept();
synchronized (list) {
list.add(socket);
}
new HandleSocket(socket, list).start();
}
}
}
客户端发送线程:
/**
* 客户端向服务端发送信息的线程
*
* @author Administrator
*
*/
public class ClientSend extends Thread {
private Scanner scanner;
private Socket socket;
public ClientSend(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
scanner = new Scanner(System.in);
try {
PrintStream ps = new PrintStream(socket.getOutputStream());
String line = "";
while ((line = scanner.nextLine()) != null) {
ps.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端接收线程:
public class ClientReceive extends Thread {
private Socket socket;
public ClientReceive(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器处理请求线程:
/**
* 处理每个链接到服务器的客户端
*
* @author Administrator
*
*/
public class HandleSocket extends Thread {
private Socket socket;
private List<Socket> list;
public HandleSocket(Socket socket, List<Socket> list) {
this.socket = socket;
this.list = list;
}
@Override
public void run() {
InetAddress address = socket.getInetAddress();// 获取连接到客户端的这个socket的地址
String ip = address.getHostAddress();
System.out.println(ip + "上线了");
// 关闭指定IP
if (ip.equals("192.168.1.105")) {
list.remove(socket);
System.out.println("拉入黑名单");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
String msg = ip + "说:" + line;
System.out.println(msg); // 输出到服务器的控制台
sendToAll(msg);
}
} catch (IOException e) {
System.out.println(ip + "下线了");
synchronized (list) {
list.remove(socket);
}
}
}
private void sendToAll(String msg) {
synchronized (list) {
for (Socket s : list) {
if (s != socket) {
try {
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
以上代码可以实现一个简单的群聊功能
UDP
UDP协议
UDP(User Datagrams Protocol)用户数据报协议,是一种使用数据报的机制来传递信息的协议。
数据报(Datagrams)是一种在不同机器之间传递的信息包,该信息包一旦从某一机器被发送给指定目标,那么该发送过程并不会保证数据一定到达目的地,甚至不保证目的地的存在真实性。反之,数据报被接受时,不保证数据没有受损,也不保证发送该数据报的机器仍在等待响应。
由此可见,UDP协议是一种基于数据报的快速的(因为它无需花时间去保证数据是否损坏,无需花时间确定接受方是否存在并等待响应)、无连接的、不可靠的数据报传输协议。
在java中,通过两个特定类来实现UDP协议顶层数据报,分别是DatagramPacket和DatagramSocket,其中类DatagramPacket是一个数据容器,是数据报包,用来保存即将要传输的数据,将地址信息和要发送的数据以字节数组的方式同时压缩入这个类创建的对象中;而类DatagramSocket表示用来发送和接收DatagramPacket的套接字,实现了数据报的通信方式。数据报套接字是包投递服务的发送或接收点。
DatagramSocket
DatagramSocket类常见的构造方法如下:
- •DatagramSocket():创建一个以当前计算机的任意可用端口为发送端口的数据报连接
- •DatagramSocket(int port):创建一个以当前计算机port端口为发送端口的数据报连接
- •DatagramScoket(int port, InetAddress address):创建一个以当前计算机的port端口为发送端口、address为IP地址的发送数据报连接
DatagramSocket类常用的几个方法如下:
- •void close() throws IOException:关闭数据报连接
- •void recieve(DatagramPacket packet):接收来自于packet数据报的信息,阻塞式方法
- •void send(DatagramPacket packet):发送packet数据报
- •void connect(InetAddress address, int port):以指定端口port为发送端口,向IP地址为address的计算机发送数据报连接
- •void disconnect():断开连接
- •DatagramChannel getChannel():和SocketChannel类似
DatagramSocket类在客户端创建自寻址套接字与服务器端进行通信连接,并发送和接受自寻址套接字。虽然有多个构造方法可供选择,但发现创建客户端自寻址套接字最便利的选择是DatagramSocket()方法,而服务器端则是DatagramSocket(int port)方法,如果未能创建自寻址套接字或绑定自寻址套接字到本地端口,那么这两个方法都将抛出一个SocketException对象,一旦程序创建了DatagramSocket对象,那么程序分别调用send(DatagramPacket dgp)和receive(DatagramPacket dgp)来发送和接收自寻址数据包。
DatagramPacket
DatagramPacket类常见的构造方法如下:
- •DatagramPacket(byte[] buff, int length);
- •DatagramPacket(byte[] buf, int offset, int length);
- •DatagramPacket(byte[] buf, int length, InetAddress address, int port);
- •DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);
第一个构造方法用于创建一个指定数据缓冲区大小和信息包的容量大小的DatagramPacket,但没有数据包的地址和端口信息,这些信息可以通过调用方法setAddress(InetAddress addr)和setPort(int port)添加上。
第二个构造方法用于创建一个长度大小为length的缓冲区,并指定数据存储(读取)的偏移地址为offset的DatagramPacket。
第三个创建一个指定缓冲区大小、传送(接受)IP地址、端口号的DatagramPacket。一般情况下,发送地址是由DatagramSocket指定。
DatagramPacket类常用的几个方法如下:- •byte[] getData():用于得到发送过来的DatagramPacket中的数据
- •void setData(byte[] buf):用于将buf中的数据写入DatagramPacket中,以备发送
- •InetAddress getAddress():返回目标的InetAddress
- •int getLength():返回将要发送或接收到的数据的长度
- •int getPort():返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的
- •SocketAddress getSocketAddress():获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为IP地址+端口号)
示例:发送端
public class UdpSender {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 创建一个DatagramsSocket 快递公司
DatagramSocket socket = new DatagramSocket(10000);
byte[] data = "今天晚上洗脚".getBytes();
// 把信息打包 参数1:发送的字节数组 参数2:数组中要发出去多少个字节。 参数3:对方的ip地址 参数四:对方的端口号
DatagramPacket p = new DatagramPacket(data, data.length,
InetAddress.getByName("192.168.1.255"), 20000); // xx.xx.xx.255这个地址是广播地址
// 发送包裹
String line = "";
while ((line = scanner.nextLine()) != null) {
data = line.getBytes();
// 没有发送一次创建一个包裹,只需要每次修改包裹的内容,和内容长度
p.setData(data);
p.setLength(data.length);
socket.send(p);
}
// socket.close();
}
}
接收端:
public class UdpReceiver {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(20000); // 绑定了指定的端口
// 创建一个用来接受发来的数据报的数据报包
DatagramPacket p = new DatagramPacket(new byte[1024], 1024);
// 把收到的信息封装到DatagramPacket
while (true) {
socket.receive(p); // 也是一个阻塞式方法。一直等到有人发来数据报
InetAddress address = p.getAddress(); // 发送人
int port = p.getPort(); // 发送方的端口
byte[] data = p.getData(); // 存储发送过来的数据的字节数组
int length = p.getLength(); // 发送过来的信息的实际长度
System.out.println(address + " " + port + " "
+ new String(data, 0, length));
}
}
}