一、网络协议的概述
多台计算机的连接和“交流”离不开网络的支持,而就像道路一样,为了保证传输的效率和安全,网络的传输也需
要一定得规则。只有双方都满足这个规则才能建立联系。现在应用最广泛的就是目前应用最广泛的是TCP/IP协议
(Transmission Control Protocal/Internet Protoal),中文全名为:传输控制协议/英特网互联协议。它包含了
TCP协议和IP协议,UDP协议等。而为了保证传输前后数据的一致性,在传输的过程中需要在原数据的头尾部添加一
些数据。TCP/IP协议一共分为4层:
链路层:链路层主要规定了物理传输通道的规则、通常是某些网络链接设备的驱动协议,如:光纤、网线。
网络层:网络层是TCP/IP协议的核心,它主要用于将传输的数据进行分组并将分组数据发送到目标计算机或者网络
(进行数据的分包)。
传输层:主要用于程序之间得的网络通讯,可以采用TCP协议或者是UDP协议(建立链接)。
应用层:主要用于应用程序的协议,如http协议。
二、IP地址和端口号
想要进行应用程序之间通信,和我们平时串门的思想是一样的,你首先要知道你的朋友住在哪个小区(IP地址)
,知道了他家的小区地址之后你还需要知道你朋友家的具体门牌号(端口号)才能进入家门进行深入的交流。目前当
前IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示。但是随着互联网普及的越来越广IPv4的方式
已经不能满足我们的需求了,因此IPv6就应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4
的8×1028倍,这样解决了IP地址不够用的问题。通过IP地址可以连接到指定计算机,如果想访问目标计算机中的某个应
用程序,则需要指定对应端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二
进制数)表示的,它的取值范围是0~65535,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通
应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
三、InetAddress类
Java为了我们编程更加的方便,为我们的网络编程提供了一些技术支持。InetAddress类用于封装一个IP地址并提供了
一些关于IP地址常用的方法:
1、getByName:在给定主机名的情况下确定主机的 IP 地址 - 返回值为InetAddress对象。
2、getHostName():获取此 IP 地址的主机名 - 返回值为字符串。
3、getHostAddress():返回 IP 地址字符串。
4、getLocalHost(): 返回本地主机 - 返回值为InetAddress对象。
举例:
1 //给定主机名返回主机IP 2 InetAddress i=InetAddress.getByName("DESKTOP-4DDBUKG"); 3 System.out.println(i); 4 //获取此IP地址的主机名 5 String hostName = i.getHostName(); 6 System.out.println(hostName); 7 //返回IP地址的字符串 8 String address = i.getHostAddress(); 9 System.out.println(address); 10 //返回本地主机 11 InetAddress localHost = InetAddress.getLocalHost(); 12 System.out.println(localHost);
四、UDP协议
UDP是无连接通信协议,即在发送数据时不管接收端接不接收他都发送数据,而接收端在接收数据时也不会给发送端一
个反馈。因为UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用
UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在传输重要数据时不建议使用
UDP协议,因为UDP是无连接通信协议不能保证数据的完整性。
(一)、DatagramSocket类
DatagramSocket类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。所以
DatagramSocket类可以理解为发送快递的收件人和寄件人,在创建发送端和接收端的DatagramSocket对象时使用的
是不同的构造方法:
1、DatagramSocket():该构造方法用于创建发送端的DatagramSocket对象,系统会给他分配一个没有被其它网络程
序所使用的端口号。
2、DatagramSocket(int port) :该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的
DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。
DatagramSocket类的常用方法:
1、从此套接字接收数据报包:receive(DatagramPacket p)。
2、从此套接字发送数据报包:send(DatagramPacket p) 。
(二)DatagramPacket类
UDP通信的过程其实和发快递很像,知道了收件人和寄件人后还需要对“货物”进行打包,而DatagramPacket类就是
相当于“快递员”,用于封装UDP通信中发送或者接收的数据。DatagramPacket类的构造方法与DatagramSocket类的类似
接收端的构造方法只需要接收收到的数据,而发送端的构造方法不仅要存放要发送的数据,还要指定接收端的IP和端口
号。
1、接收端 - 构造 DatagramPacket,用来接收长度为 length 的数据包:DatagramPacket(byte[] buf, int length)
2、发送端 - 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号:
DatagramPacket(byte[] buf, int length, SocketAddress address)
其常用的方法:
1、返回某台机器的 IP 地址,此IP将要发往该机器或者是从该机器接收到的:getAddress()
2、返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的:getPort()
3、返回数据缓冲区:getData()
4、返回将要发送或接收到的数据的长度:getLength()
举例:
*发送端
1 public static void main(String[] args) throws IOException { 2 // 获取DatagramSocket类的对象ds 3 DatagramSocket ds = new DatagramSocket(); 4 // 死循环发送信息 5 while (true) { 6 // 准备要发送的数据 7 String s = "萨拉黑有"; 8 // 将其转换成byte数组 9 byte[] bs = s.getBytes(); 10 // 获取数组的长度 11 int length = bs.length; 12 // 根据IP地址获取InetAddress对象it 13 InetAddress it = InetAddress.getByName("192.168.1.146"); 14 // 准备端口号 15 int post = 8888; 16 // 创建DatagramPacket对象,传入准备好的数据、数据长度、IP地址、端口号进行打包 17 DatagramPacket datagramPacket = new DatagramPacket(bs, length, it, post); 18 // 调用send方法将准备好的数据数据发送出去,因为在一直发送数据所以没有关闭资源 19 ds.send(datagramPacket); 20 } 21 }
*接收端
1 public static void main(String[] args) throws IOException { 2 // 创建DatagramSocket对象,设置端口号进行监听 3 DatagramSocket ds = new DatagramSocket(8888); 4 // 循环接收数据 5 while (true) { 6 // 准备接受数据的容器 7 byte[] buf = new byte[1024]; 8 // 创建DatagramPacket对象用来接收数据、传入容器和容器的长度 9 DatagramPacket packet = new DatagramPacket(buf, buf.length); 10 // 调用receive方法接收数据 11 ds.receive(packet); 12 // 获取发送端的InetAddress对象 13 InetAddress address = packet.getAddress(); 14 // 获取传入数据的长度 15 int length = packet.getLength(); 16 // 输出发送端IP地址和发送的内容 17 System.out.println("---->" + address.getHostAddress() + " 内容:" + new String(buf, 0, length)); 18 } 19 }
五、TCP协议
TCP作为另一种常用的通信协议,与UDP协议的相同点是都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。
而两者不同的是UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。而TCP通信是严格区分
客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器
端程序需要事先启动,等待客户端的连接。而JAVA为了方便我们的开发提供给我们2个用于实现TCP程序的类,一个是ServerSocket类
,用于表示服务器端,一个是Socket类,用于表示客户端。
(一)、ServerSocket类
TCP协议进行通讯时首先要创建代表服务器端的ServerSocket对象,等待客户端的连接。然后创建代表客户端的Socket对象向服务
器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
ServerSocket类的常用构造方有 - 创建绑定到特定端口的服务器套接字:ServerSocket(int port)
其常用方法:
1、返回值为Socket对象,侦听并接受到此套接字的连接:accept()
2、关闭此套接字:close()
3、返回此服务器套接字的本地地址:getInetAddress()
ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收
来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个
Scoket对象用于和客户端实现通信,程序才能继续向下执行。
(二)、Socket类
Socket类实现客户端套接字(TCP客户端程序),其常用的构造方法有:
1、创建一个流套接字并将其连接到指定 IP 地址的指定端口号 - 根据InetAddress对象和端口号port创建:
Socket(InetAddress address, int port)
2、创建一个流套接字并将其连接到指定主机上的指定端口号 - 根据IP的字符串和端口号port创建:
Socket(String host, int port)
Socket类的常用方法有:
1、返回值为int,返回的是Socket对象与服务器端连接的端口号:getPort()
2、返回值为InetAddress类型的对象,获取Socket对象绑定的本地IP地址:getLocalAddress()
5、关闭此套接字:close()
6、返回值为InputStream类型的输入流对象,返回值为此套接字的输入流,用于读取数据:getInputStream()
7、返回值为OutputStream类型的输出流对象,返回值为此套接字的输出流,用于数据的写入:getInputStream()
当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。
举例:
*客户端
1 public static void main(String[] args) throws IOException, Exception { 2 // 创建Socket客户端对象 3 Socket socket = new Socket("192.168.14.81", 9999); 4 // 获取OutputStream对象,准备发送数据 5 OutputStream stream = socket.getOutputStream(); 6 // 准备发送的数据 7 String s = "我啦啦啦啦啦"; 8 // 将字符转换成byte发送 9 stream.write(s.getBytes()); 10 // 关闭资源 11 socket.close(); 12 stream.close(); 13 }
*服务器端
1 public static void main(String[] args) throws IOException { 2 // 创建ServerSocket服务器端对象,设置端口号进行监听 3 ServerSocket ss = new ServerSocket(9999); 4 // 调用accept方法进行监听,这里以后的代码先不会执行,和客户端建立连接之后在继续执行 5 Socket accept = ss.accept(); 6 // 根据accept对象创建InputStream对象用来接收数据 7 InputStream stream = accept.getInputStream(); 8 // 准备接收数据的容器 9 byte[] b = new byte[1024]; 10 // 接收数据据的长度 11 int len; 12 // 往b里读取数据 13 len = stream.read(b); 14 // 获取客户端的InetAddress对象 15 InetAddress inetAddress = accept.getInetAddress(); 16 // 输出客户端的IP 17 System.out.println("-------->" + inetAddress.getHostAddress()); 18 // 输出接收的数据从0到len 19 System.out.println(new String(b, 0, len)); 20 // 关闭资源,因为服务器在正常情况下不会关闭,所以这里指关闭流资源 21 stream.close(); 22 }
模拟网站登录举例:
*用户类
1 public class User { 2 private String username; 3 private String pwd; 4 5 public User() { 6 super(); 7 // TODO Auto-generated constructor stub 8 } 9 10 public User(String username, String pwd) { 11 super(); 12 this.username = username; 13 this.pwd = pwd; 14 } 15 16 public String getUsername() { 17 return username; 18 } 19 20 public void setUsername(String username) { 21 this.username = username; 22 } 23 24 public String getPwd() { 25 return pwd; 26 } 27 28 public void setPwd(String pwd) { 29 this.pwd = pwd; 30 } 31 @Override 32 public boolean equals(Object obj) { 33 if (this == obj) 34 return true; 35 if (obj == null) 36 return false; 37 if (getClass() != obj.getClass()) 38 return false; 39 User other = (User) obj; 40 if (pwd == null) { 41 if (other.pwd != null) 42 return false; 43 } else if (!pwd.equals(other.pwd)) 44 return false; 45 if (username == null) { 46 if (other.username != null) 47 return false; 48 } else if (!username.equals(other.username)) 49 return false; 50 return true; 51 } 52 }
*模拟的用户数据
1 public class Obj { 2 private static ArrayList<User> arr = new ArrayList<>(); 3 static { 4 arr.add(new User("admin", "123456")); 5 arr.add(new User("lisi", "1111")); 6 arr.add(new User("wang5", "3333")); 7 arr.add(new User("lisi", "000000")); 8 //System.out.println(arr); 9 10 } 11 public static ArrayList<User> get() { 12 return arr; 13 } 14 }
*客户端
1 public static void main(String[] args) throws IOException, IOException { 2 // 根据IP地址和端口号创建客户端对象 3 Socket socket = new Socket("192.168.14.27", 8888); 4 // 键盘录入用户名和密码 5 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 6 System.out.println("请输入用户名"); 7 String username = reader.readLine(); 8 System.out.println("请输入密码"); 9 String pwd = reader.readLine(); 10 // 根据socket对象创建字符输出流,为了方便用PrintWriter类传输数据 11 PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); 12 // 将用户名和密码写到服务器 13 writer.println(username); 14 writer.println(pwd); 15 // 根据socket创建BufferedReader用来接收返回的数据 16 BufferedReader reader2 = new BufferedReader(new InputStreamReader(socket.getInputStream())); 17 // 读取返回的数据 18 String line = reader2.readLine(); 19 // 显示登录提示 20 System.out.println(line); 21 // 关闭资源 22 reader2.close(); 23 reader.close(); 24 writer.close(); 25 socket.close(); 26 }
*服务器
public static void main(String[] args) throws IOException { // 创建服务端ServerSocket对象,设置端口号 ServerSocket socket = new ServerSocket(8888); // 调用accept方法,返回Socket对象 Socket accept = socket.accept(); // 根据Socket对象获取InputStream对象对其转型,创建BufferedReader对象 BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream())); // 读取客户端传来的用户名和密码 String username = reader.readLine(); String pwd = reader.readLine(); // 根据传过来的用户名和密码创建User对象 User user = new User(username, pwd); // 获取OutputStream()对象将其转换,准备把处理的结果返回给客户端 PrintWriter writer = new PrintWriter(accept.getOutputStream(), true); // 获取正确的用户集合 ArrayList<User> list = Obj.get(); // 判断根据传过来的用户名和密码创建User对象是否在集合里存在 if (list.contains(user)) { // 存在,返回登陆成功提示 writer.println("登陆成功"); } else { // 不存在,返回登录失败提示 writer.println("登录失败"); } // 关闭资源 writer.close(); accept.close(); }