<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!
一、网络参考模型:OSI参考模型和TCP/IP参考模型
1,TCP/IP参考模型由应用层、传输层、网际层和主机至网络层构成。
2,网络通讯要素:IP地址、端口号、传输协议
IP地址:网络中设备的标识。端口号:用于标识进程的逻辑地址,有效端口0~65535,其中0~1024为系统保留。
传输协议:通讯的规则,常见协议包括TCP(传输控制协议)、UDP(数据报文协议)
3,UDP特点:a,将数据及源和目的地址封装成数据包,不需要建立连接。UDP与邮局邮包裹类似,将物品,自己和对方地址
留下,但对方不一定能接到。b,数据包大小限制在64K内。c,因为无连接,不可靠。d,不需要连接,因此速度快。
TCP特点:建立连接,形成传输数据的通道,在连接中进行大量数据传输。经过三次握手完成连接,是可靠的协议。
必须建立连接,效率会降低。比如打电话、下载数据。
4,IP地址较为复杂,因此在Java中被封装成对象。
InetAddress类没有构造函数,包括Inet4Address和Inet6Address两个子类。
InetAddress提供了getLocalHost方法,可以获得本地主机的ip地址对象,通过对象调用不同的方法可以获得相应的信息。
此外,还提供了getByName方法,参数为主机名或ip地址的字符串形式,通过指定的主机名或IP地址获得ip地址对象
演示代码:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPDemo {
public static void main(String[] args) throws UnknownHostException {
// 获取本地主机ip地址的对象
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostAddress()+" : "+ip.getHostName());
// 获取局域网内其他主机的ip地址对象
// 通过其他主机的ip或主机的字符串来获得相应的信息
ip = InetAddress.getByName("USERMIC-JGS5KNU");
ip = InetAddress.getByName("192.168.31.149");
System.out.println(ip.getHostAddress());
// 获取百度的ip地址对象
ip = InetAddress.getByName("www.baidu.com");
System.out.println(ip.getHostAddress());<span style="white-space:pre"> </span>// 119.75.218.70
}
}
5,Socket(套接字)为网络服务提供的一种机制。通信的两端都有Socket,网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输(比如港口)。
UDP传输,在java中将UDP封装成对象,DatagramSocket类是用来发送和接收数据报包的套接字。其中数据报包
在java中被封装成对象,用DatagramPacket类描述。
创建一个UDP发送端和UDP接收端
创建UDP发送端思路:先创建UDP的Sokcet服务,将要发送的数据(注意要转成byte格式)封装到数据包中,通过
UDP的Sokcet服务将数据发送(send)出去,关闭资源。
创建UDP接收端思路:先创建UDP的Socket服务,创建数据包对象,用于接收和存储数据(注意要明确使用哪个端口
来接收数据),使用Sokcet的receive方法(阻塞式)接收数据到数据包中,通过数据包对象解析数据,可以得到发送
端的ip、发送端口以及发送的内容等。最后关闭资源。
注意事项:a,发送端在创建Socket服务时,可以明确由哪个端口发送数据,也可以不明确。接收端在创建Socket服务时,
必须明确由哪个端口来接收数据,而且必须与发送端数据报包中封装的端口号一致。
b,发送端数据报包中封装的ip地址最后一位为255时,表示为广播,整个网段都可以接收到数据。
代码如下:
UDP发送端
public class UDPSendDemo {
public static void main(String[] args) throws IOException {
System.out.println("UDP发送端启动。。。。。。");
/*
* 创建UDP传说的发送端
* 思路:1,建立UDP的Socket服务
* 2,将要发送的数据封装到数据包中
* 3,通过UDP的Socket服务将数据包发送出去
* 4,关闭Socket服务
*/
// 建立DatagramSocket服务对象
DatagramSocket ds = new DatagramSocket();
// 将要发送的数据封装到数据包中
String str = "UDP传输演示:哥们来了!";
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);
// 通过Socket服务将数据报包发送出去
ds.send(dp);
// 关闭资源
ds.close();
}
}
UDP接收端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiveDemo {
public static void main(String[] args) throws IOException {
System.out.println("UDP接收端启动......");
/*
* 建立UDP接收端
* 思路: 1,建立Socket服务,因为是要接收数据,必须要明确用哪个端口来接收数据
* 2,创建数据包对象,用于存储和接收数据,方便用数据包对象的方法解析这些数据
* 3,使用Socket中的receive方法,将这些数据接收到数据包中
* 4,通过数据包对象的方法解析数据包中的数据
* 5,关闭资源
*/
// 建立Socket服务,因为是要接收数据,必须要明确用哪个端口来接收数据
DatagramSocket ds = new DatagramSocket(10000);
// 创建数据包对象
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
// 使用Socket中的receive方法,将这些数据接收到数据包中
ds.receive(dp); // 阻塞式方法
// 通过数据包对象的方法解析数据包中的数据,解析其中的数据、地址和端口等
// 此处得到的ip为发送信息的主机的ip,端口是发送信息主机的端口,
// 如果发送主机明确了发送端口,接收端就会显示固定的端口,如果没有明确端口
// 接收端显示的端口是变化的,注意明确端口是在DatagramSocket的构造函数的参数列表中明确
// 注意与DatagramPacket中的端口的区别。
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String str = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"---"+ port+"---"+str);
}
}
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 ChatDemo {
public static void main(String[] args) throws IOException {
// 创建发送端Socket服务,可以明确发送端口,也可以不明确
DatagramSocket send = new DatagramSocket();
// 创建接收端Socket服务,必须明确端口号,即用哪个端口来接收数据,这个端口号
// 与发送端数据包中封装的端口号是一致的。
DatagramSocket rece = new DatagramSocket(10000);
new Thread(new Send(send)).start();
new Thread(new Receive(rece)).start();
}
}
class Send implements Runnable{
private DatagramSocket ds;
// 创建构造函数,用于接收DatagramSocket类的对象
public Send(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
try {
// 创建高效字符输入流
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
// 由键盘录入数据,当录入字符串是”over“时,停止运行
while((line= bufr.readLine())!=null){
byte[] buf = line.getBytes();
// 创建数据包对象,注意参数列表,当ip地址最后一位为255时,表示广播
// 参数列表中的10000指定接收端用10000端口来接收数据,因此,在创建接收端
// Socket服务时,必须明确端口号
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);
ds.send(dp);
if("over".equals(line))
break;
}
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receive implements Runnable{
private DatagramSocket ds;
// 创建构造函数,用于接收DatagramSocket类的对象
public Receive(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
// 只能用try/catch block
try {
while(true){
byte[] buf = new byte[1024];
// 定义数据包对象,用于接收和存储数据
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);// 阻塞式
// 用数据包对象解析所接收的内容,包括发送端ip、发送端口、以及内容
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"---"+port+" : "+text);
// 当接收到的内容是”over“时,停止
if(text.equals("over")){
System.out.println(ip+"exist...");
break;
}
}
} catch (IOException e) {
}
}
}
二、TCP传输
1,Socket(客户端)和ServerSocket(服务端)
创建客户端思路:创建客户端Socket服务,使用的是Socket对象,建议在创建时就明确目的和端口;如果创建
连接成功,说明数据传输通道建立,这个通道称为socket流,是底层建立好的,可以提供getInputStream和
getOutputStream方法获取字节流,使用输出流,将数据写出,使用输入流获得服务端发送来的数据,关闭资源。
创建服务端思路:创建服务端Socket服务,必须对外提供一个端口,否则客户端无法连接;获取连接过来的客户端
对象s,通过s.getInputStream()获取客户端发送来的数据;通过s.getOutputStream()获取输出流,将数据发送到
客户端,最后关闭资源。注意,关闭时不仅要关闭服务端,还要关闭客户端,因为客户端不可控。
此外,要先启用服务端。
演示代码:
创建服务端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
/*
* TCP服务端建立:
* 1,创建TCP服务端Socket服务,使用的是Socket的对象
* 2,服务端必须对外提供一个端口,否则客户端无法连接。
* 3,获取链接过来的客户端对象
* 4,通过客户端对象获取socket流读取客户端发来的数据
* 5,关闭资源。注意:关客户端(客户端关闭资源不可控),关服务端
*/
// 创建服务端对象,对外提供一个端口
ServerSocket ss = new ServerSocket(10002);
// 获取链接过来的客户端对象
Socket s = ss.accept(); // 阻塞式
// 通过socket对象获取输入流,读取客户端发来的数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
String ip = s.getInetAddress().getHostAddress();
int port = s.getPort();
System.out.println(ip+"---"+port+" : "+text);
// 使用客户端输出流给客户端发送数据
OutputStream out = s.getOutputStream();
out.write("收到".getBytes());
// 关闭客户端及服务端
s.close();
ss.close();
}
}
创建客户端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException, IOException {
/*
* TCP传输,客户端建立:
* 1,创建TCP客户端Sockt服务,使用的是Socket的对象,建议创建时就明确目的地址及端口
* 2,如果连接建立成功,说明数据传输通道已建立,这个通道称为Socket流,是底层建立好的,
* 既然是流,说明既能读又能写,想要获得输入或输出流对象,可以通过Socket获取
* 通过getInputStream()和getOutputStream()获取字节流
* 3,使用输出流,将数据写出。
* 4,关闭资源
*/
// 创建客户端Socket服务
Socket socket = new Socket(InetAddress.getLocalHost(),10002);
// 获取Socket流中的输出流
OutputStream out = socket.getOutputStream();
// 使用输出流,将指定的数据写出
out.write("TCP演示".getBytes());
// 读取服务端返回的数据
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
System.out.println(text);
// 关闭资源
socket.close();
}
}
2,TCP协议练习--上传文本和图片
a,上传文本思路:
客户端:创建Socket服务(服务端创建时最好指定服务端IP和端口),将要上传的文件进行流的关联,使用socket的输出流将文件数据发送到服务端,
文件发送完毕后要给服务端文件结束标记;通过socket输入流获得服务端的反馈信息,结束流,释放资源。
演示代码:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
public class UploadClient {
public static void main(String[] args) throws IOException, IOException {
// 创建客服端Socket服务
Socket socket = new Socket(InetAddress.getLocalHost(),10008);
// 将要上传的文件进行封装
BufferedReader bufr = new BufferedReader(new FileReader("demo.txt"));
/*
* 将socket流的输出流封装到PrintWriter中,注意参数列表中要加上true,如果不加true,在写出
* 数据后,一定要调用flush方法,否则数据不会刷新到socket的输出流中。
*/
PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
// 开始上传数据
String line = null;
while((line=bufr.readLine())!=null){
// 此处要使用println方法,因为该方法带有换行标记。如果用print方法,在服务端的readLine方法由于
// 没有读到换行标记,就会一直等待下去。
out.println(line);
}
/*
* 为什么要结束输出流呢?为了告诉服务端一个结束标记,即文本文件已经上传完毕
* 如果没有该条语句,直接关闭socket流也可以
* 但是由于客户端的readLine方法在等待服务端的信息,为阻塞式方法,因此不会执行到socket流关闭的语句
* 因此服务端的while循环就无法结束
* 这样就造成客户端和服务端都在等的情况
*/
socket.shutdownOutput(); // 表示输出结束,
// 接收服务端发来的信息
BufferedReader bufrin = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String strin = bufrin.readLine();
System.out.println(strin);
// 关闭流,释放资源
bufr.close();
socket.close();
}
}
服务端:创建Socket服务(一定要对外提供一个访问端口!!!),获得客户端的输入流,用字符输出流关联目的文件,获得客户端输入流的数据,通过字
符输出流将数据写入目标文件;获取客户端输出流,将反馈信息发送给客户端,关闭流,释放资源,注意:一定要关闭客户端的流。
此外注意事项:阻塞式方法需要判断结束标记。
演示代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class UPloadServer {
public static void main(String[] args) throws IOException {
// 创建服务端Socket服务
ServerSocket ss = new ServerSocket(10008);
// 获得客户端的socket流
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress()+"......connected");
// 将客户端的输入流封装
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 将服务端的目的文件进行封装
BufferedWriter bufw = new BufferedWriter(new FileWriter("server.txt"));
// 接收客户端的数据,并写入目的文件中
String line = null;
/*
* 如果客户端没有传递文件结束标记,while循环无法结束
*/
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
}
// 给服务端发送信息
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("upload success"); // 如果用print方法,客户端无法得到数据
// 关闭流,释放资源,注意一定要关闭客户端的流
bufw.close();
s.close();
ss.close();
}
}
b,上传图片
上传图片与上传文本方法时一致的,不同的是上传图片需要用的是字节流。
客户端演示代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class UploadPhotoClient {
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
Socket socket = new Socket(InetAddress.getLocalHost(),8888);
// 读取客户端要上传的文件
FileInputStream fis = new FileInputStream("a.jpg");
// 获取Socket输出流,将图片数据发送给服务端
OutputStream out = socket.getOutputStream();
byte[] buf = new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1){
out.write(buf, 0, len);
}
// 上传结束标识
socket.shutdownOutput();
// 读取服务端发回的内容
InputStream in = socket.getInputStream();
len = in.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
// 关闭流,释放资源
fis.close();
socket.close();
}
}
服务端演示代码:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadPhotoServer {
public static void main(String[] args) throws IOException {
// 创建Socket服务
ServerSocket ss = new ServerSocket(8888);
// 获得客户端socket流对象
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress()+"......connected");
// 通过socket对象获得客户端输入流
InputStream in = s.getInputStream();
// 封装本地目的文件
File dir = new File("d:\\photo");
if(!dir.exists())
dir.mkdirs();
File file = new File(dir,"copy.jpg");
FileOutputStream fos = new FileOutputStream(file);
// 通过文件输出字节流向目标文件写入数据
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1){
fos.write(buf, 0, len);
fos.flush();
}
// 获取客户端输出流,发送反馈信息
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
// 关闭流,释放资源
fos.close();
s.close();
ss.close();
}
}
3,服务端处理多个客户端的请求
思路:将服务端处理客户端请求的内容封装起来,结合多线程技术,实现多个客户端的同时连接。
演示代码:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ThreadUploadTest {
public static void main(String[] args) throws IOException {
// 创建服务端Socket服务
ServerSocket ss = new ServerSocket(8888);
while(true){
Socket s = ss.accept(); //阻塞式方法
// 开启线程
new Thread(new ThreadTask(s)).start();;
}
}
}
/*
* 将服务端处理内容封装到一个类中,让该类实现Runnable接口
*/
class ThreadTask implements Runnable{
private Socket s = null;
public ThreadTask(Socket s) {
super();
this.s = s;
}
@Override
// 将服务端处理内容封装到线程任务中
public void run() {
try {
String ip = s.getInetAddress().getHostAddress();
// 创建目的文件并完成与流的关联
int count = 0;
File dir = new File("d:\\photo");
if(!dir.exists())
dir.mkdirs();
File file = new File(dir,ip+"copy.jpg");
while(file.exists()){
file = new File(dir,ip+"copy"+(++count)+".jpg");
}
FileOutputStream fos = new FileOutputStream(file);
// 得到客户端的输入流
InputStream in = s.getInputStream();
// 读取客户端数据,并存入目的文件
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1){
fos.write(buf, 0, len);
}
// 得到客户端输出流,输出反馈信息
OutputStream os = s.getOutputStream();
os.write("上传成功".getBytes());
// 关闭流,释放资源
fos.close();
s.close();
} catch (IOException e) {
}
}
}
三、常见客户端和服务端
最常见的客户端:IE
最常见的服务端:Tomcat
Tomecat服务器对外提供了一个Servlet接口
1,模拟一个Tomcat服务器
使用IE浏览器访问模拟的服务器,演示代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTomcat {
public static void main(String[] args) throws IOException {
// 创建服务端Socket,必须对外提供一个端口
ServerSocket ss = new ServerSocket(9090);
// 获得客户端socket流
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress()+"......connected");
// 通过客户端Socket对象获得客户端的输入流
InputStream in = s.getInputStream();
// 读取客户端信息
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
System.out.println(text);
// 给客户端一个信息
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("Welcom");
// 关闭流,释放资源
s.close();
ss.close();
}
}
效果如下:
2,网络结构
a,C/S client/server
特点:该结构的软件,客户端和服务端都需要编写,开发成本高,维护较为麻烦。
好处:客户端在本地可以分担一部分运算
b,B/S browser/server
特点:该结构的软件,只开发服务端,不开发客户端,因为客户端直接由浏览器取代。开发成本低,维护简单。
缺点:所有运算都在服务端完成。