第一部分:网络通信概述
一、网络通信三要素
1、IP
IP是网络中计算机的标识,是唯一的。IP分四段(IPV4)组成,每一段都是一个字节,最大值为255。
随着计算机的普及,公网IP已经远远不够,因此电信厂商将不同地区的IP通过子网掩码来划分,同一个小区的人就使用同一个公网IP地址;同时,四段IP不够用后,就出现了六段IP(IPV6),可以加入字母等。为了方便记忆,出现了主机名与IP的映射关系。
常用网段:192.168.*.*
广播地址:192.168.*.255(指定网段的广播地址)
本地回环地址:127.0.0.1,主机名:localhost。本机没有配置任何IP地址的情况下,本机默认的IP地址,可用于dos命令行测试网卡(ping 127.0.0.1)
2、端口号
用于标识进程的逻辑地址,不同进程的标识。
有效端口:0-65535,其中0-1024被系统使用或保留。
常见端口:
web服务:80
Tomcat服务器:8080
数据库:3306
3、传输协议
通信的规则。常见协议:TCP、UDP
二、网络模型
1、OSI参考模型
注意:
1)OSI参考模型中的传输层封装了TCP/UDP协议;网络层封装了IP协议。
2)物理层包括网线、光纤、无线。
3)数据封包与拆包
数据从 A电脑应用程序 发送到 B电脑应用程序,会将数据从应用层到表示层依次封包,封装上每一层特有信息,最后经过物理层发送出去就是数据封包。
B电脑应用程序接收到数据后由物理层到应用层依次拆包,拆掉每一层能识别的信息后,获取到发送的数据,这就是数据拆包。
2、TCP/IP参考模型
注意:
1)开发软件在网际层、传输层、应用层。
2)学习网络编程在网际层与传输层。
第二部分:IP(java.net)
|---- InetAddress
|---- Inet4Address
|---- Inet6Address
一、概述
二、方法Java中提供了描述IP的对象:InetAddress,此类表示互联网协议 (IP) 地址。IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
该类没有构造函数,而且有非静态方法存在,说明该类有静态方法返回本类对象。常用方法:
static InetAddress getLocalHost() 返回本地主机
String getHostAddress() 返回 IP 地址字符串(以文本表现形式)
String getHostName() 获取此 IP 地址的主机名
static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址
static InetAddress[] getAllByName(String host)
在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组String toString() 主机名/字面值 IP 地址
注意:
1、主机名可以是机器名(如:www.baidu.com),也可以是IP地址的字符串表示形式
2、在使用getByName方法时,参数以IP地址为主,因为主机名需要解析。
三、示例
package day.day11; import java.net.*; import java.util.Arrays; import java.util.List; /* * 需求:获取ip演示 */ public class IPDemo { public static void main(String[] args) throws Exception { //获取本机的IP InetAddress ip = InetAddress.getLocalHost(); //获取IP地址与主机 String host = ip.getHostName(); String address = ip.getHostAddress(); //输出本机的ip地址与主机 System.out.println(host+" = "+address); //获取百度的所有IP(主机) InetAddress[] baiduIp = InetAddress.getAllByName("www.baidu.com"); List<InetAddress> list = Arrays.asList(baiduIp);//数组变集合 System.out.println(list); } }
运行结果:
第三部分:TCP/UDP协议
一、UDP(面向无连接)
1、将数据及源和目的封装到数据报包中,不需要建立连接。
2、每个数据包的大小限制在64K以内。
3、因无连接,是不可靠协议。
4、不需要建立连接,速度快。但易丢包(数据)。
注意:
1)UDP传输不用确定对方在不在,直接发送数据,若不在,则数据包丢失。
2)UDP类似对讲机之间的通信,聊天、视频会议、桌面共享都是走UDP协议,数据的丢失不重要。
二、TCP(面向连接)
1、建立连接,形成传输数据的通道。
2、在连接中进行大数据量传输。
3、通过3次握手完成连接,是可靠协议。
4、必须建立连接,效率会降低。
注意:
第四部分:UDP传输1)3次握手:A确定B在不在;B在,反馈给A说在;A知道B已经在。
2)TCP类似打电话,下载走的就是TCP,因其不能丢失数据。
|---- DatagramSocket(端点)
|---- DatagramPacket(数据报包)
一、概述二、基础类及方法1、Socket就是为网络提供的一种机制。
2、通信的两端都有Sockt。
3、网络通信就是Socket之间的通信。
4、数据在两个Socket之间通过IO传输。
由于传输协议不一样,每个传输协议都有自己不同的建立端点的方式。网络编程就是Socket编程。
1、DatagramSocket
此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
构造方法:
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口
DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口
常用方法:
void send(DatagramPacket p) 从此套接字发送数据报包
void receive(DatagramPacket p) 从此套接字接收数据报包
void close() 关闭此数据报套接字
2、DatagramPacket
此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
构造方法:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号
DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf, int offset, int length)
构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量
常用方法:
InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的
int getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的
byte[] getData() 返回数据缓冲区
int getLength() 返回将要发送或接收到的数据的长度
int getOffset() 返回将要发送或接收到的数据的偏移量
三、发送端
1、建立UDP的Socket服务:DatagramSocket(可以发送端指定端口)。
2、提供数据,并将数据、目的主机及端口封装到数据包中。
3、通过DatagramSocket服务的send(发送)功能,将数据包发出去。
4、关闭资源。
四、接收端示例代码:
注意:数据已经发送出去,只是接收端没有开启,故数据包丢失。package demo; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /* 需求:通过UDP传输方式,将一段文字发送出去 */ public class UDPSendDemo{ public static void main(String[] args) throws IOException { //1、建立UDP的Socket服务,即端点:DatagramSocket DatagramSocket socket = new DatagramSocket(); //2、提供数据,并将数据、目的主机及端口封装到数据报包中 byte[] buf = "UDP 传输".getBytes(); DatagramPacket bag = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.104"),10000); //3、通过DatagramSocket的send方法将数据发送出去 socket.send(bag); //4、关闭资源 socket.close(); } }
1、创建UDP的Socket服务,即DatagramSocket,并监听指定的端口(不指定则系统随机分配一个)。
2、定义一个数据包,用于存储接收到的字节数据。(数据包对象中有更多功能可以提取字节数据中的不同数据信息,如端口、地址、数据)。
3、通过DatagramSocket服务的receive方法见收到的数据存储到已定义好的数据包中。
4、通过数据包对象的特有功能将这些不同的数据取出。
5、关闭资源。
示例代码:
运行结果:(先开启接收端,再开启发送端)package demo; /* * 需求:定义一个程序,用于接收和处理UPD协议传输的数据 */ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPReceiveDemo { public static void main(String[] args) throws IOException{ //1、创建UDP的Sockt服务,建立端点,并监听指定的端口 DatagramSocket socket = new DatagramSocket(10000); //2、定于数据包用于存储接收到的数据(借助数组) byte[] buf = new byte[1024]; //缓冲区 DatagramPacket bag = new DatagramPacket(buf,buf.length); //3、通过DatagramSocket的receive方法将接收到的数据存放到数据包中 socket.receive(bag); //4、通过数据包对象的方法取出数据包中的数据 String ip = bag.getAddress().getHostAddress();//ip String data = new String(bag.getData(),0,bag.getLength());//数据 int port = bag.getPort();//端口 System.out.println(ip+"-"+port+":"+data); //5、关闭资源 socket.close(); } }
注意:
1)网络应用程序无论是接收端还是发送端都需要有端口。(发送端也可以指定一个端口)
2)定义UDP接收端的Socket服务,通常会监听一个端口。其实就是给这个接收网络应用程序定义一个数字标识,方便明确哪些数据过来该应用程序可以处理。(不指定端口则系统会随机分配)
3)UDP接收端的receive方法是一个阻塞式方法,没有接收到数据就会一直等。
4)为了能够一直接收数据,可以将接收端的第2、3、4步定义在while(true)中,由于receive方法是阻塞式方法,因此其不会造成死循环的情况。
5)通过多线程技术让发送端与接收端在同一个界面中显示。
6)发送端与接收端是一个独立的应用程序。
7)一般用到java.net包,就会用到java.io包。
五、模拟聊天程序
package demo; import java.io.*; import java.net.*; /* * 需求:模拟聊天程序 */ //描述UDP发送端 class Send implements Runnable{ private DatagramSocket socket; //构造时接收UDP的Socket Send(DatagramSocket socket) { this.socket = socket; } //发送端线程运行代码 public void run(){ try{ //读取键盘录入 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; //将键盘录入的数据封装到数据、目的主机及端口封装到数据报包中 byte[] buf = line.getBytes(); DatagramPacket bag = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.104"),10001); //将数据发送出去 socket.send(bag); } //关闭资源 socket.close(); }catch(Exception e){ throw new RuntimeException("发送失败"); } } } //描述接收端 class Receive implements Runnable{ private DatagramSocket socket; //构造时接收UDP的Socket Receive(DatagramSocket socket) { this.socket = socket; } //接收端线程运行代码 public void run(){ try{ //循环接收数据(接收端一直开启) while(true){ //将接收到的数据存到数据报包中 byte[] buf = new byte[1024]; DatagramPacket bag = new DatagramPacket(buf,buf.length); socket.receive(bag); //解析接收到的数据 String ip = bag.getAddress().getHostAddress(); String data = new String(bag.getData(),0,bag.getLength()); System.out.println(ip+":"+data); } }catch(Exception e){ throw new RuntimeException("接收失败"); } } } public class ChatByUDP { public static void main(String[] args) throws Exception{ //建立发送端的Socket服务 DatagramSocket send = new DatagramSocket(); //建立接收端的Socket服务,并指定监听的端口 DatagramSocket rece = new DatagramSocket(10001); //分别开启发送端与接收端的线程(同时运行) new Thread(new Send(send)).start(); new Thread(new Receive(rece)).start(); } }
运行结果:
注意:
1)如果将发送端封装的IP改为192.168.1.255,则表示向192.168.1.*这个网段中所有存活的机器发广播。(存活的机器的指定端口能接收到该数据)
2)该示例中readLine方法与receive方法均为阻塞式方法。
第五部分:TCP传输
|---- Socket(客户端)
|---- ServerSocket(服务端)
一、基础方法
1、Socket
此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
构造方法:
常用方法:Socket() 通过系统默认类型的 SocketImpl 创建未连接端点
Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号
void connect(SocketAddress endpoint) 将此套接字连接到服务器
InetAddress getInetAddress() 返回套接字连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流
int getPort() 返回此套接字连接到的远程端口
void close() 关闭此套接字
void shutdownInput() 此套接字的输入流置于“流的末尾”
void shutdownOutput() 禁用此套接字的输出流
2、ServerSocket
此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
构造方法:
ServerSocket(int port) 创建绑定到特定端口的服务器端点
ServerSocket(int port, int backlog) 指定最大同时连接该服务端的客户端数
常用方法:
Socket accept() 侦听并接受到此服务端对象
void close() 关闭此套接字
二、客户端
1、创建客户端的Socket服务,因TCP是面向连接的,所以需指定连接的主机与端口。
若创建成功,则其与服务端的通路就建立了,通路一建立就有了Socket流(网络流),Socket流中有输入流与输出流(通过getInputStream与getOutputStream方法获取)
2、获取Socket服务中的输出流(OutputStream),以发送数据。
3、关闭资源。
图解:
示例代码:
注意:在没有开启服务端前开启客户端会发生ConnectException,因为TCP是面向连接的。package demo; import java.net.*; import java.io.*; /* * 需求:给服务端发送一个文本数据 */ public class SocketDemo { public static void main(String[] args) throws IOException{ //1、创建客户端的Socket端点,并监听指定的端口 Socket socket = new Socket("192.168.1.104",10002); //2、获取Socket服务中的输出流(网络流),以发送数据 OutputStream out = socket.getOutputStream(); out.write("TCP 传输演示".getBytes()); //3、关闭资源 socket.close(); } }
三、服务端
1、建立服务端的Socket服务:ServerSocket,并指定监听的端口。
2、获取连接过来的客户端对象。通过ServerSocket的accept方法完成。
3、客户端如果发送过来数据,服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发送过来的数据。
4、关闭服务端。(可选操作)
示例代码:
package demo; import java.io.*; import java.net.*; /* * 需求:定义端点接收数据,并打印到控制台上 */ public class ServerSocketDemo { public static void main(String[] args) throws IOException { //1、建立服务端的ServerSocket服务,并指定监听的端口 ServerSocket serverSocket = new ServerSocket(10002); //2、获取连接过来的客户端对象 Socket socket = serverSocket.accept(); String ip = socket.getInetAddress().getHostAddress();//获取IP System.out.println(ip+"...connect"); //3、获取客户端发送过来的数据,通过读取流获取 InputStream in = socket.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println("data:"+new String(buf,0,len)); //4、关闭资源 socket.close();//关闭客户端,以免占用资源 serverSocket.close();//关闭服务端,可选操作 } }
运行结果:(先启动服务端,再启动客户端)
注意:
1)服务端accept方法是一个阻塞式方法,没有接收到客户端对象就会一直等。
2)获取连接过来的客户端对象时先获取客户端的IP。
3)服务端可以关闭连接过来的客户端,中断网络流,以免占用服务端的资源。
4)服务端可以不用关闭,将服务端的2、3步定义在while(true)中,以方便客户端随时访问。(先启动服务端、再启动客户端)
5)服务端可以通过输出流向客户端发送信息;客户端可以通过输入流来获取服务端发送给客户端的信息。
四、流的结束标记
示例代码1:(定义大写转换服务器)
客户端:
package demo; /* * 需求:客户度键盘录入数据,服务端实时返回大写数据 * 思路: * 1、操作的是文本数据,采用字符流,提高效率,加入缓冲区技术,且其提供了readLine方法等 * 2、注意结束标识,告知服务器,一行数据已经写完 */ import java.net.*; import java.io.*; public class ToUpperCaseSocket { public static void main(String[] args) throws IOException { //创建客户端的Sockt服务,并指定目的主机的10003端口 Socket socket = new Socket("192.168.1.104",10003); //读取键盘录入 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); String line = null; //获取Socket流对象(输入流与输出流) BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); //将键盘录入的数据发送到服务端、并获取服务端返回的数据 while((line=bufr.readLine())!=null){ if("over".equals(line)) break; //向服务端发送数据(不包括\r\n) bufOut.write(line); //写入一行的结束标记 bufOut.newLine(); //将缓冲区中的数据刷到流中 bufOut.flush(); //获取服务端返回的信息并打印 String info = bufIn.readLine(); System.out.println(info); } //关闭键盘录入及网络流 bufr.close(); socket.close(); bufOut.close(); } }
服务端:
运行结果:package demo; /* * 需求:客户度键盘录入数据,服务端实时返回大写数据 * 思路: * 1、操作的是文本数据,采用字符流,提高效率,加入缓冲区技术,且其提供了readLine方法等 * 2、注意结束标识,告知服务器,一行数据已经写完 */ import java.io.*; import java.net.*; public class ToUpperCaseServer { public static void main(String[] args) throws IOException { //建立服务端的Socket服务,并监听10003端口 ServerSocket serverSocket = new ServerSocket(10003); //获取客户端连接的Socket对象及ip Socket socket = serverSocket.accept(); String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"...connect"); //获取网络流、即Sockt流 BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); String line = null; //读取网络输入流中的数据 while((line=bufIn.readLine())!=null){ String message = line.toUpperCase(); //将数据转成大写后返回给客户端,不包括\r\n bufOut.write(message); //写入一行的结束标记 bufOut.newLine(); //用到缓冲区就要刷新 bufOut.flush(); } //关闭客户端 socket.close(); //关闭网络流 bufIn.close(); bufOut.close(); } }
注意:上述代码中的输出流可以使用打印流替换。打印流有自动刷新功能及换行功能(自带结束标记)
示例代码2:(向服务器传送图片)
客户端:
服务端:package demo; /* * 定义客户端,向服务器上传图片 */ import java.net.*; import java.io.*; public class UploadPic { public static void main(String[] args) throws Exception { //连接服务端指定端口 Socket socket = new Socket("192.168.1.104",10004); //将硬盘文件与字节输入流关联 FileInputStream in = new FileInputStream("F:\\11.jpg"); //获取网络输入流与输出流 OutputStream bufOut = socket.getOutputStream(); InputStream bufIn = socket.getInputStream(); //读取图片数据,并将其写到网络输入流中 byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ bufOut.write(buf,0,len); } /* 由于图片数据写完后,while循环结束,但并没有向服务端写入结束标记,服务端的read方法会一直等待。 * 因此手动加入一个结束标记,告知服务端已到流的末尾 */ socket.shutdownOutput(); //读取服务端返回的数据 byte[] tbuf = new byte[1024]; int length = bufIn.read(tbuf); System.out.println(new String(tbuf,0,length)); //关闭流资源 socket.close(); in.close(); } }
运行结果:package demo; /* * 需求:将客户端发送到服务端的图片保存到硬盘中 */ import java.net.*; import java.io.*; public class DownloadPic { public static void main(String[] args) throws IOException { //建立服务端 ServerSocket serverSocket = new ServerSocket(10004); //获取连接的客户端对象 Socket socket = serverSocket.accept(); String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"...connect"); //将字节输出流与硬盘文件关联,并新建一个该文件(不存在则新建) FileOutputStream out = new FileOutputStream("F:\\2.jpg"); //获取网络流 InputStream bufIn = socket.getInputStream(); OutputStream bufOut = socket.getOutputStream(); //将网络输入流中的数据写到硬盘文件中 int len = 0; byte[] buf = new byte[1024]; while((len=bufIn.read(buf))!=-1){ out.write(buf,0,len); } //向客服端反馈信息 bufOut.write("上传成功!".getBytes()); //关闭流资源 out.close(); socket.close(); } }
注意:
1)TCP中程序不容易停止,因为需要结束标记,告知对方已到流的末尾。
2)可以自定义结束标记,如加入时间戳(时间的唯一性),通过DataInputStream与DataOutputStream完成。
3)readLine方法的结束标记是\r\n,在用该方法时要注意。
五、多线程并发访问服务端----服务器原理
第六部分、浏览器与Tomcat服务器现实生活中,服务器是一直开启的,同时多个客户端可以同时访问服务器。可以通过多线程的方式实现:
示例代码1:(并发上传图片)
客户端:
服务端:package demo; /* * 定义客户端,向服务器上传图片 */ import java.net.*; import java.io.*; public class UploadPicByThread { public static void main(String[] args) throws Exception { //先对上传的文件进行一系列的判断 if(args.length==0){ System.out.println("请选择一个图片"); return ; } String fileStr = args[0]; File file = new File(fileStr); if(!file.exists()){ System.out.println("文件不存在"); return ;//结束主函数 } if(!args[0].endsWith(".jpg")){ System.out.println("只支持jpg格式的图片"); return ; } if(file.length()>1024*1024*5){ System.out.println("图片过大!"); return ; } //符合要求后上传图片到服务端 upload(file); } //上传图片 public static void upload(File file) throws IOException{ //客户端的Socket服务 Socket socket = new Socket("192.168.1.104",10005); FileInputStream in = new FileInputStream(file); OutputStream bufOut = socket.getOutputStream(); InputStream bufIn = socket.getInputStream(); //向服务端上传图片 byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ bufOut.write(buf,0,len); } //定义结束标记 socket.shutdownOutput(); //接收服务端反馈 byte[] tbuf = new byte[1024]; int length = bufIn.read(tbuf); System.out.println(new String(tbuf,0,length)); socket.close(); in.close(); } }
运行结果:package demo; /* * 需求:将客户端发送到服务端的图片保存到硬盘中 */ import java.net.*; import java.io.*; public class DownloadPicByThread { public static void main(String[] args) throws IOException { //建立服务端 ServerSocket serverSocket = new ServerSocket(10005); while(true){ //每获取到新的客户端对象,就开启新的线程 Socket socket = serverSocket.accept(); new Thread(new Download(socket)).start(); } } } //并发上传图片 class Download implements Runnable{ private Socket socket; private int count = 1; Download(Socket socket){ this.socket = socket; } public void run(){ String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"...connect"); FileOutputStream out = null; InputStream bufIn = null; OutputStream bufOut = null; try { //判断硬盘中文件是否存在,存在就重命名 File path = new File("F:\\pic\\"+count+".jpg"); while(path.exists()){ path = new File("F:\\pic\\"+(count++)+".jpg"); } out = new FileOutputStream(path); bufIn = socket.getInputStream(); bufOut = socket.getOutputStream(); //将网络输入流中的数据写到硬盘文件中 int len = 0; byte[] buf = new byte[1024]; while((len=bufIn.read(buf))!=-1){ out.write(buf,0,len); } //向客服端反馈信息 bufOut.write("上传成功!".getBytes()); }catch (IOException e) { throw new RuntimeException("上传失败"); }finally{ try{ if(out!=null) out.close(); }catch(IOException e){ throw new RuntimeException("关闭文件写入字节流失败"); } try{ socket.close(); }catch(IOException e){ throw new RuntimeException("关闭网络流字节流失败"); } } } }
示例代码2:(验证登陆用户名)
客户端:
package demo; import java.net.*; import java.io.*; /* * 客户端通过键盘录入用户名 * 服务端对这个用户名进行检验 * * 如果该用户存在,在服务端显示xxx,已登陆 * 在服务端显示,xxx 欢迎光临 * * 如果该用户不存在,在服务端显示xxx,尝试登陆 * 在客户端显示,xxx,该用户不存在 * * 最多就登陆3次 */ //客户端 public class UserLoginClient { public static void main(String[] args) throws Exception{ //客户端的Socket端点,指定目的主机与端口 Socket socket = new Socket("192.168.1.104",10007); //读取键盘录入 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //获取网络流(Socket流) PrintWriter bufOut = new PrintWriter(socket.getOutputStream(),true);//自动刷新功能 BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); for(int x=0;x<3;x++){ //获取键盘录入数据 String line = bufr.readLine(); //没有输入任何数据(ctrl+c) if(line==null) break ; //将这行数据发送的服务端,ln表示换行(结束标记) bufOut.println(line); //获取服务端反馈信息 String info = bufIn.readLine(); System.out.println("info:"+info); if(info.contains("欢迎")){ break; } } bufr.close(); socket.close();//当键盘录入ctrl+c是,for循环结束,读到该句代码,给服务端发一个 -1(socket流) } }
服务端:
运行结果:package demo; import java.io.*; import java.net.*; //多线程并发验证 class UserThread implements Runnable{ private Socket socket; UserThread(Socket socket){ this.socket = socket; } //线程运行代码 public void run(){ //获取IP String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"...connect"); try{ //验证 for(int x=0;x<3;x++){ //获取网络流 BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); String name = bufIn.readLine();//客户端发的-1,readLine返回null,会打印null 尝试登陆 //所以加一个判断判断是否为null的标记 if(name==null){ break; } //创建字符流与硬盘 用户信息文件 相关联 BufferedReader bufi = new BufferedReader(new FileReader("F:\\userInfo.txt")); PrintWriter bufOut = new PrintWriter(socket.getOutputStream(),true); //定义标记用于验证用于信息 boolean flag = false; //验证用户信息 String line = null; while((line = bufi.readLine())!=null){ if(line.equals(name)){ //验证后存在,就更改标记 flag = true; break; } } //向客户端反馈信息 if(flag){ bufOut.println(name+",欢迎光临"); System.out.println(name+",登陆成功"); }else{ bufOut.println(name+",用户名不存在");//记得写ln啊 System.out.println(name+",尝试登陆"); } } socket.close(); }catch(IOException e){ throw new RuntimeException("登陆失败"); } } } //服务端 public class UserLoginServer { public static void main(String[] args) throws Exception{ //创建服务端的Socket对象,并监听指定端口 ServerSocket serverSocket = new ServerSocket(10007); while(true){ Socket socket = serverSocket.accept(); //每获取到一个客户端对象就开启一个新线程,以达到并发验证的目的 new Thread(new UserThread(socket)).start(); } } }
一、浏览器
浏览器是一个客户端,可以访问服务端。
示例代码:
运行结果:package com.itheima; import java.net.*; /* * 需求:自定义服务端让客户端访问 * 客户端:浏览器 * 服务端:自定义 */ import java.io.*; public class HtmlServer { public static void main(String[] args) throws Exception{ //定义服务端的Socket端点 ServerSocket serverSocket = new ServerSocket(11111); //接收客户端对象 Socket socket = serverSocket.accept(); String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"....connect"); //通过网络流给客户端返回信息 PrintWriter out = new PrintWriter(socket.getOutputStream(),true); //html代码,能被浏览器解析 out.println("<font color='red' size='7'>你好欢迎光临</font>"); socket.close(); serverSocket.close(); } }
二、telnet远程登录
telnet是Windows提供的一个远程登录命令,可以访问网络中的任意一台主机,连接完成后可以对这台主机进行命令式配置
注意:
从运行结果可以看出,浏览器可以解析HTML代码,而Telnet命令是将数据原样输出。
三、Tomcat服务器
1、Tomcat是一个服务器软件,纯Java编写。
2、能提供服务说明里面封装了ServerSocket。
3、Tomcat服务器可以读取自定义的资源
4、运行步骤:
1)通过运行bin\startup.bat启动Tomcat服务器
2)启动浏览器,并访问指定主机的tomcat服务器(端口:8080)
注意:访问服务器时要指定端口,否则连接不上。
访问自定义网页:
在 tomcat7.0\webapps 文件下新建一个文件夹myweb,在myweb下创建网页demo.html
在浏览器(客户端)中访问该网页:<html> <body> <h1>这是我的主页</h1> <font size=5 color = red>欢迎光临</font> <div> 盗梦空间</br> 星际穿越</br> 加勒比海盗</br> </div> </body> </html>
浏览器收到的源码为:
注意:自定义的网页一定要存放到webapps文件夹中。
四、浏览器HTTP消息头
将浏览器访问服务器时发送的请求消息打印。
示例代码:
package com.itheima; import java.net.*; /* * 需求:输出客户端访问服务器时的请求信息 */ import java.io.*; public class HtmlServer { public static void main(String[] args) throws Exception{ //定义服务端的Socket端点 ServerSocket serverSocket = new ServerSocket(11111); //接收客户端对象 Socket socket = serverSocket.accept(); String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip+"....connect"); //接收来自客户端发送的请求信息并打印 InputStream in = socket.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf);//一次读完 System.out.println(new String(buf,0,len)); //通过网络流给客户端返回信息 PrintWriter out = new PrintWriter(socket.getOutputStream(),true); out.println("<font color='red' size='7'>你好欢迎光临</font>"); socket.close(); serverSocket.close(); } }
运行结果:
浏览器请求消息头注解:
http://192.168.1.104:11111/myweb/demo.html(主机:端口号/资源路径/资源) Http请求消息头: GET /myweb/demo.html HTTP/1.1(请求行:浏览器向服务器发送get请求,协议版本1.1) Accept: text/html, application/xhtml+xml, */*(接收的数据) Accept-Language: zh-CN(语言) User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6 .0)(用户信息) Accept-Encoding: gzip, deflate(支持的封装形式:gzip压缩,将网页数据压缩后往浏览器发送) Host: 192.168.1.104:11111(指定主机的指定端口,一台服务器可以装多台主机,一个主机可以装多个web应用程序) DNT: 1 Connection: Keep-Alive(连接:保持存活) (最后一行下面,有一个空行) 请求数据体(与请求数据头用空行隔开,代表哪段数据是头,哪段数据是内容)
五、自定义浏览器(传输层)
示例代码:(无图形化界面)
运行结果:package day.day13; import java.io.*; import java.net.*; /* * 需求:根据浏览器的请求消息头,自定义一个浏览器,访问tomcat服务器。(传输层) */ public class MyIE { public static void main(String[] args) throws Exception{ //访问tomcat服务器 Socket socket = new Socket("192.168.1.104",8080); PrintWriter out = new PrintWriter(socket.getOutputStream(),true); //向服务器发送请求消息头 out.println("GET /myweb/demo.html HTTP/1.1"); out.println("Accept: */*"); out.println("Accept-Language: zh-cn"); out.println("Host: 192.168.1.104:8080"); out.println("Connection: closed");//服务器数据发完就结束 out.println();//一定要写空行 out.println();//打两个空行将请求消息头与内容分开 //获取服务器发来的数据 BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = null; while((line=bufIn.readLine())!=null){ System.out.println(line); } socket.close(); } }
注意:相比浏览器,自定义IE多了应答消息头。
服务器应答消息头注解:
自定义浏览器:(图形化界面)Tomcat服务器返回的应答消息头 Http应答消息头: HTTP/1.1 200 OK(支持Http1.1版本;200代表响应状态码:成功(回馈) Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"180-1431137393196"(标记) Last-Modified: Sat, 09 May 2015 02:09:53 GMT(上传修改时间) Content-Type: text/html(html文本) Content-Length: 180(文本大小:180字节) Date: Sat, 09 May 2015 06:53:18 GMT(时间) Connection: close
运行结果:(传输层)package day.day13; /* * 需求:创建浏览器(图形化界面)(传输层) */ import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class MyIEGUI { //外部建立引用 private Frame f; private Button b; private TextField tf;// private TextArea ta; MyIEGUI() { //对象初始化就具备图形化界面 init(); } public void init() { f = new Frame("我的浏览器"); b = new Button("转到"); tf = new TextField(49);//创建一个文本框 ta = new TextArea(20,56);//创建一个文本区域 --- 行与列 //对窗体进行基本设置 f.setBounds(200,200,450,400); f.setLayout(new FlowLayout());//布局 f.add(tf); f.add(b); f.add(ta); //添加事件 myEvent(); f.setVisible(true); } public void myEvent() { //在按钮事件源上添加活动监听 b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { method(); } catch (Exception e1) { e1.printStackTrace(); } } }); //在文本框事件源上添加键盘监听 tf.addKeyListener(new KeyAdapter() { //按下Enter键,运行method方法 public void keyPressed(KeyEvent e) { if(e.getKeyCode()==KeyEvent.VK_ENTER) try { method(); } catch (Exception e1) { e1.printStackTrace(); } } }); //在Frame窗体上添加窗体监听 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void method() throws Exception { //清空文本区域 ta.setText(""); String url = tf.getText();//http://192.168.1.104:8080/myweb/demo.html int index1 = url.indexOf("//")+2; int index2 = url.indexOf("/",index1); String str = url.substring(index1,index2); String path = url.substring(index2); String[] arr = str.split(":"); String host = arr[0]; int port = Integer.parseInt(arr[1]); //访问服务器 goToServer(host,port,path); } public void goToServer(String host, int port, String path) throws Exception { //访问tomcat服务器 Socket socket = new Socket(host,port);//指定主机与端口 PrintWriter out = new PrintWriter(socket.getOutputStream(),true); out.println("GET "+path+" HTTP/1.1");//指定资源路径 out.println("Accept: */*"); out.println("Accept-Language: zh-cn"); out.println("Host: 192.168.1.104:8080"); out.println("Connection: closed");//服务器数据发完就结束 out.println();//一定要写空行 out.println();//打两个空行将请求消息头与内容分开 //获取服务器发来的数据 BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = null; while((line=bufIn.readLine())!=null){ //将服务器返回的数据设置到文本区域中 ta.append(line+"\r\n"); } socket.close(); } public static void main(String[] args) { new MyIEGUI(); } }
第七部分:URL类
一、概述
类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
类URI也代表统一资源定位符,它比URL更强大,包括条形码。
二、方法
构造方法:
URL(String spec) 根据 String 表示形式创建 URL 对象
URL(String protocol, String host, int port, String file) 根据指定 protocol、host、port 号和 file 创建 URL 对象
常用方法:
注意:String getProtocol() 获取此 URL 的协议名称
String getHost() 获取此 URL 的主机名(如果适用)
int getPort() 获取此 URL 的端口号int getDefaultPort() 获取与此 URL 关联协议的默认端口号
String getPath() 获取此 URL 的资源路径部分
String getFile() 获取此 URL 的文件名(path+query)
String getQuery() 获取此 URL 的查询部分
1)在进行地址访问没有指定端口时,getPort()方法返回-1,则将端口号设置为80,因此上网时默认访问的端口都是80。
2)传输层协议(TCP)访问服务器时,会返回 响应头信息 与 正文主体。而浏览器在访问服务器时只返回正文主体,因其把符合应用层协议(HTTP)的消息部分(响应头)给解析(拆包)了,把正文部分显示在了主体区域内。
重点方法:
URL类中:
注意:URLConnection openConnection()
返回一个 URLConnection 对象(子类HttpURLConnection对象),它表示到 URL 所引用的远程对象的连接InputStream openStream()
打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream (先连接再获取输入流)
1)每次调用此 URL 的协议处理程序的 openConnection 方法都打开一个新的连接。也就是说,只要调用该openConnection()方法,就会去连接这台主机,获取与主机连接的对象(客户端对象Socket)。由于内部完成了封装动作,因此不用再写Socket流,且封装动作是带着Http协议封装的。(不用再写请求消息头部分)
2)之前用Socket流手动与服务器建立连接时,使用的是传输层的协议,因此所有操作都在传输层。而现在openConnection方法将Socket流带着Http协议封装到URLConnection的内部,操作就来到了应用层。
URLConnection类中:
InputStream getInputStream() 返回从此打开的连接读取的输入流(Socket流中的方法)
OutputStream getOutputStream() 返回写入到此连接的输出流(Socket流中的方法)
三、新浏览器(应用层)
示例代码:
运行结果:(应用层)package day.day13; /* * 需求:创建浏览器(图形化界面) */ import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class MyNewIEGUI { //外部建立引用 private Frame f; private Button b; private TextField tf;// private TextArea ta; MyNewIEGUI() { //对象初始化就具备图形化界面 init(); } public void init() { f = new Frame("我的浏览器"); b = new Button("转到"); tf = new TextField(49);//创建一个文本框 ta = new TextArea(20,56);//创建一个文本区域 --- 行与列 //对窗体进行基本设置 f.setBounds(200,200,450,400); f.setLayout(new FlowLayout());//布局 f.add(tf); f.add(b); f.add(ta); //添加事件 myEvent(); f.setVisible(true); } public void myEvent() { //在按钮事件源上添加活动监听 b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { method(); } catch (Exception e1) { e1.printStackTrace(); } } }); //在文本框事件源上添加键盘监听 tf.addKeyListener(new KeyAdapter() { //按下Enter键,运行method方法 public void keyPressed(KeyEvent e) { if(e.getKeyCode()==KeyEvent.VK_ENTER) try { method(); } catch (Exception e1) { e1.printStackTrace(); } } }); //在Frame窗体上添加窗体监听 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void method() throws Exception { ta.setText("");//清空文本区域 URL url = new URL(tf.getText());//封装网址 //获取URL连接对象 URLConnection urlConnection = url.openConnection(); //获取来自 服务器 的信息(已经拆包,只获取到正文主体) InputStream in = urlConnection.getInputStream(); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ ta.append(new String(buf,0,len)); } } public static void main(String[] args) { new MyNewIEGUI(); } }
注意:
1、浏览器在访问服务端时,带有请求消息头。服务器对请求消息头进行解析,并找到对应资源后,带着应答消息头将数据返回给浏览器。(使用自定义Socket流时,走的是传输层协议,会将所有的信息都展示出来)
2、使用URLConnection时,走的是应用层。传输层在收到数据后,会向上传递给应用层,应用层中有一个对象HttpURLConncetion,它会将数据包中的 头部分信息 用应用层的(Http)协议拆包,拆完后只将数据主体发给了显示区域。
3、浏览器中封装了许多解析引擎,能够解析HTML代码。
四、其他对象
SocktAddress 抽象类,封装了IP地址与端口号
Socket()需要与connect(SocketAddress endpoint)方法一起使用
ServerSocket(int port, int backlog) backlog队列长度,同时在线连接数,一般最大50。
第八部分:域名解析
1、通过IP地址访问。
直接访问指定的主机。
2、通过主机名访问。
先到本机的host文件(C:\Windows\System32\drivers\etc\hosts)中查找是否存在主机名与IP地址的映射关系,若存在,则拿到IP地址后访问;若不存在则到DNS服务器(该服务器中存放的是国内外知名网站的映射关系)中查找是否存在主机名与IP的映射关系,若存在则获取到IP访问该主机,不存在则可能超时导致访问失败。
3、hosst文件的作用
可以通过getByName(String hostName)方法获取到主机的IP地址,将映射关系存放到本地,以提高上网的速度;也可以将一些垃圾网站的主机名与本地回环地址(127.0.0.1)映射,屏蔽掉这些网站;若不需要更新某些软件,可以将这些软件的主页与本地回环地址映射,不再更新该软件。