一、概述
1、网络架构C/S: Client/Server
客户端、服务端。
特点:
1) 需要在客户端和服务端都需要安装编写的软件。
2) 维护较麻烦。
好处:可以减轻服务端的压力,如网络游戏。
B/S: Browser/Server
浏览器 、服务端。
特点:
1) 客户端不用单独编写软件。因为客户端用的就是浏览器。
2) 对于软件升级,只要考虑服务端即可。
弊端:所有的程序都运行在服务端,客户端的浏览器毕竟解析能力较弱。对游戏等。
2、网络模型:
OSI 参考模型、TCP/IP 参考模型如图所示:
Note:网际层常用的协议是 TCP 或 UDP;传输层使用的协议是 IP;应用层使用的是HTTP 和 FTP 协议。
3、网络通信三要素:
IP 地址、端口号、传输协议(通用协议 TCP/IP)
1)IP地址:
IP 地址 = 网络号码 + 主机地址
A 类 IP 地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
B 类 IP 地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
C 类 IP 地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
特殊地址:
127.0.0.1 回环地址,可用于测试本机的网络是否有问题。ping 127.0.0.1
ipconfig: 查看本机 IP 地址
xxx.xxx.xxx.0 网络地址
xxx.xxx.xxx.255 广播地址
A 类 1.0.0.1---127.255.255.254 10.X.X.X 是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址)
Note: 127.X.X.X 是保留地址,用做循环测试用的。
B 类 128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255 是私有地址。
Note:169.254.X.X 是保留地址。
C 类 192.0.0.1---223.255.255.254 192.168.X.X 是私有地址
D 类 224.0.0.1---239.255.255.254
E 类 240.0.0.1---247.255.255.254
InetAddress 类:没有构造函数,封装的是 IP 地址(具体解说请自行查看 API 文档)
主要方法:
static InetAddress getLocalHost() //返回本地主机。
String getCanonicalHostName() //获取此 IP 地址的完全限定域名
String getHostAddress() //返回 IP 地址的字符串(文本形式)
String getHostName() //获取此 IP 地址的主机名
static InetAddress getByName(String Host)//获取任意一台主机的 IP 地址、名称
eg: InetAddress ia = InetAddress.getByName("thinkpad -sl400");//参数可以是 IP 地址
System.out.println("address:"+ia.getHostAddress());
System.out.println("name:"+ia.getHostName());
static InetAddress getAllByName(String Host) //在给定主机名的情况下根据系统上配置的名称服务返回其 IP 地址所组成的数组。
Note: 如果 IP 地址和对应的主机名,没有在网络上,那么无法解析主机名,将会返回该 IP 地址,有可能返回的 IP 对象不唯一。
示例如下:
import java.net.*;
class IPDemo {
public static void main(String[] args) throws Exception {
// 获取本类对象
InetAddress ia = InetAddress.getLocalHost();
// ip
String address = ia.getHostAddress();
// 主机名
String name = ia.getHostName();
System.out.println("IP=" + address + "\t name=" + name);
// 获取指定主机的ip信息
InetAddress i = InetAddress.getByName("169.254.153.61");
String add = i.getHostAddress();
String na = i.getHostName();
System.out.println("addIP=" + add + "\t name=" + na);
// 获取指定主机名的ip信息,可能主机不唯一所以逐个获取并打印出来
InetAddress[] csdn = InetAddress.getAllByName("www.csdn.net");
for (InetAddress c : csdn) {
String caddress = c.getHostAddress();
String cname = c.getHostName();
System.out.println("csdnIP=" + caddress + "\t csdnname=" + cname);
}
}
}
2)端口号:
a、用于标识进程的逻辑地址,不用进程的标识。
b、有效端口:0~65535,系统使用或保留的端口是:0~1024。
3)传输协议:即通信规则,包含TCP和UDP协议
UDP:是面向无连接,明确了对方的端口,无论在不在网上,只管传输,不在就会丢失数据。只求速度,应用于网络视频会议和聊天等应用程序中。
特点:
a、面向无连接,即将数据及源和目的封装成数据包中,不建立链接的发送
b、每个数据包的大小限制在64K之内
c、因无连接,是不可靠的协议
d、不建立连接,速度快。
弊端:容易丢失数据包。
举例:视频会议、聊天、桌面共享都可以作为 UDP 传输。UDP 相当于步话机。
TCP:是面向连接的,必须连接成功才能传输数据,应用于下载等程序上
特点:
a、面向连接,在建立连接后,形成传输数据的通道
b、在连接中进行大数据量的传输
c、通过三次握手完成连接,是可靠的协议
d、必须建立连接,效率稍慢
举例:打电话。
Note:三次握手:第一次本方发送请求,第二次对方确认连接,第三次本方再次确认连接成功。
4、通信的步骤:
1)找到IP地址
2)数据要发送到对象指定应用程序,为标识这些应用程序,所以给这些网络应用程序都用数字标识,为方便称呼这个数字,叫做端口,即逻辑端口。
3)定义通信规则,称之为协议。国际组织定义了通用协议,即TCP/IP。
Note:必须要有数字标识才能将数据发送到应用程序上。
二、传输协议1、Socket
1)它被称之为插座,相当于港口一样,是网络服务提供的一种机制。
2)通信两端都要有Socket,才能建立服务。
3)网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
2、UDP传输
DatagramSocket: 表示用来发送和接收数据报包的套接字。
方法:
1)创建 UDPSocket发送服务对象:DatagramSocket(),不指定端口。DatagramSocket(int port),指定端口。
2)发送:void send(DatagramPacket p)
3)接收:void receive(DatagramPacket p)
Note:DatagramPacket:表示数据报包,用来实现无连接包投递服务。此包既能用来接收数据,还能用来封装数据。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器中。凡是带地址(InetAddress)的都是用于发送包的。
步骤:
1)发送数据:
a、建立UDPSocket服务,在此无需指定端口,也可以将端口加入。如果不指定的话,系统会随机分配一个端口,如第一次运行端口还没有得到释放,第二次会顺延端口号值。
b、提供数据,并将数据封装到数据包中
c、通过socket服务的发送功能,将数据包发送出去
d、关闭资源
2)接收数据:
a、定义UDPSocket服务。通常会监听一个端口,其实就是给这个接收网路应用程序定义数字标识,方便于明确哪些数据过来该应用程序可以处理。
b、定义一个数据包,用来存储接收到的字节数据,因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。
c、通过socket服务的receive方法接收到的数据存入已定义好的数据包中
d、通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
e、关闭资源
在定义接收数据的方法中,仍会在DatagramSocket构造函数中传入DatagramPacket的参数,这是因为收到的数据太多,需要解析,通过将数据封装成对象,易于解析,所以需要传入参数。
Note:
1、发送端与接收端是两个独立的运行程序。
2、在发送端,要在数据包对象中明确目的地IP及端口。
3、在接收端,要指定监听的端口。
示例如下:import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
class UDPRece {
public static void main(String[] args) {
// 1、定义UDP 接受Socket服务,通常会见厅一个端口,即为此程序定义一个数字标识,方便于明确数据的来源
DatagramSocket ds = null;
DatagramPacket dp = null;
try {
ds = new DatagramSocket(10000);
// 2、定义一个数据包,通过Socket服务的receive方法将收到的数据存入此数据包中
byte[] buf = new byte[1024];
dp = new DatagramPacket(buf, buf.length);
// 3、通过数据包的特有方法,将这些不同的数据去除打印在控制台上
ds.receive(dp);
} catch (Exception e) {
e.printStackTrace();
}
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
int port = dp.getPort();
System.out.println("ip" + ip + "data" + data + "port" + port);
// 4、关闭资源
ds.close();
}
}
public class UDPSend {
public static void main(String[] args) {
// 1、建立UDP Socket服务
DatagramSocket ds = null;
BufferedReader bufr = null;
try {
ds = new DatagramSocket(8888);
// 2、提供数据,并将数据封装到数据包中
bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
byte[] buf = null;
DatagramPacket dp = null;
while ((line = bufr.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// byte[] data = "udp coming".getBytes();
buf = line.getBytes();
// 3、通过Socket服务的发送功能,将数据发送出去
dp = new DatagramPacket(buf, buf.length,
/* InetAddress.getLocalHost() */InetAddress
.getByName("169.254.153.61"), 10000);
ds.send(dp);
}
} catch (Exception e) {
e.printStackTrace();
}
// 4、关闭资源
ds.close();
}
}
练习:编写一个基于UDP传输的聊天小程序,要求可以多线程访问。代码示例如下:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/*
* 聊天程序:
* 有收数据部分和发数据部分,同时运行就用到了多线程技术
* 因为收和发是不一致的,所以要定义两个run方法,而且这两个方法要封装到不同的类中
*/
//发送端线程
class SendThread implements Runnable {
private DatagramSocket ds;// 传入数据包DatagramSocket
// 构造函数传入DatagramSocket用以后期操作
public SendThread(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
String line = null;
while ((line = bufr.readLine()) != null) {
if ("886".equals(line)) {
break;
}
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName("169.254.153.61"), 30000);
ds.send(dp);
}
} catch (Exception e) {
throw new RuntimeException("发送端异常");
}
}
}
// 封装接收端线程任务到Run方法中
class ReceThread implements Runnable {
private DatagramSocket ds;
public ReceThread(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
try {
while (true) {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + "::" + data);
}
} catch (Exception e) {
throw new RuntimeException("接收端异常");
}
}
}
public class ChartDemo {
public static void main(String[] args) throws SocketException {
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(30000);
SendThread send = new SendThread(sendSocket);
ReceThread rece = new ReceThread(receSocket);
new Thread(send).start();
new Thread(rece).start();
}
}
3、TCP协议TCP分客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。
建立TCP:
1) Socket 和 ServerSocket
2) 建立客户端和服务器端
3) 建立连接后,通过 Socket 中的 IO 流进行数据传输
4) 关闭 Socket
主要方法:
1)创建客户端对象:
Socket();创建空参数的客户端对象,一般用于服务端接收数据
Socket(String host,int port);指定要接收的IP地址和端口号
2)创建服务端对象:ServerSocket(int port);指定接收的客户端的端口
3)Socket accept();监听并接受到此套接字的连接
4)void shutdownInput();此套接字的输入流至于“流的末尾”
5)void shutdownOutput();禁用此套接字的输出流
6)InputStream getInputStream();返回此套接字的输入流,Socket对象调用
7)OutputStream getOutputStream();返回套接字的输出流,Socket对象调用
具体步骤:客户端:
1) 建立 socket 服务。指定要连接主机和端口。
2) 获取 socket 流中的输出流。将数据写到该流中。通过网络发送给服务端。
3) 获取 socket 流中的输入流,将服务端反馈的数据获取到,并打印。
4) 关闭客户端资源。
服务端:
1) 建立服务端Socket服务————ServerSocket,并监听一个端口
2) 获取链接过来的客户端对象————ServerSocket的accept方法(阻塞式方法)
3) 客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到客户端的读取流来读取发过来的数据并打印在控制台上
4) 关闭服务端(可选)
Note:客户端与服务器端是两个独立的应用程序。必须先启动服务器端,再连接客户端。在没有数据传输等服务时,服务端必须关闭客户端连接,一般不关闭服务器。
示例1:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 需求:客户端给服务端发送一个文本数据,再定义端点接受数据并打印在控制台上
*
*/
class TCPServer {
public static void main(String[] args) throws IOException {
// 建立服务端Socket服务,监听一个端口
ServerSocket ss = new ServerSocket(20000);
// 获取服务端链接过来的客户端对象
Socket socket = ss.accept();
// 获取客户端发送过来的数据,要使用客户端对象的读取流来读取数据
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String ip = socket.getInetAddress().getHostAddress();
String data = new String(buf, 0, len);
System.out.println(ip + "::" + data);
socket.close();
ss.close();
}
}
public class TCPClient {
public static void main(String[] args) throws UnknownHostException,
IOException {
// 创建客户端Socket服务,并指定主机和端口
Socket socket = new Socket("169.254.153.61", 20000);
// 为了发送数据,就要获取Socket流中的输出流
OutputStream os = socket.getOutputStream();
os.write("tcp client coning".getBytes());
socket.close();
}
}
示例2:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 演示tcp传输的客户端和服务端互访
* 需求:客户端给服务端发送数据,服务端收到后给客户端反馈信息
*
* 客户端:
* 1、建立socket服务,指定要链接的主机和端口
* 2、获取socket流中的输入流,给服务端输出文字
* 3、获取socket流中的读取流,读取服务端传来的反馈信息并打印
* 4、关闭流
*
* 服务端:
* 1、建立服务端Socket服务————ServerSocket,并监听一个端口
* 2、获取链接过来的客户端对象————ServerSocket的accept方法(阻塞式方法)
* 3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到客户端的读取流来读取发过来的数据并打印在控制台上
* 4、关闭服务端(可选)
*/
class TcpClient2 {
public static void main(String[] args) throws UnknownHostException,
IOException {
Socket s = new Socket("169.254.153.61", 20010);
OutputStream os = s.getOutputStream();
os.write("服务器,你好".getBytes());
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
s.close();
}
}
class TcpServer2 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(20010);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "...connecting");
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
OutputStream os = s.getOutputStream();
os.write("你好,我收到了".getBytes());
s.close();
ss.close();
}
}
4、应用a、上传图片
客户端
a、服务端点。
b、读取客户端已有的图片数据
c、通过Socket输出流将数据发给服务端
d、读取服务端反馈信息。
e、关闭流资源
服务端
a、服务端服务,并监听指定端口
b、获取客户端对象,并获取客户IP
c、读取客户端输入流数据
d、写入文件
e、用客户端输出流反馈信息
f、关闭流资源
Note:此服务端有局限性,当A客户端连接上之后,被服务端获取到,这是B客户端要获取链接只有等待 那么为了可以让多个客户端同时并发访问客户端。服务端最好就是将每个客户端封装到一个单独的线程中,这样可以同时处理多个客户端请求。所以我们想到了多线程,即多个客户端向服务端传输数据,如何定义线程呢:只要明确每个客户端要执行的代码即可,将其存入Run方法中即可。
代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
class PicThread implements Runnable {
private Socket s;
public PicThread(Socket s) {
this.s = s;
}
@Override
public void run() {
int count = 1;
String ip = null;
try {
ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "...connected");// 在服务端标识连接符
// 避免多个客户端传入同一张照片后照片被覆盖,所以将传入的照片利用IP地址+计数器来重命名
InputStream is = s.getInputStream();
File file = new File(ip + "(" + count + ").jpg");
while (file.exists()) {
file = new File(ip + "(" + count++ + ").jpg");
}
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
OutputStream os = s.getOutputStream();
os.write("上传成功".getBytes());// 上传成功,在客户端给出反馈信息
fos.close();
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + "上传失败");// 上传失败的反馈信息
}
}
}
class PicServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(20020);
Socket s = null;
while (true) {
s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
class PicClient {
public static void main(String[] args) throws UnknownHostException,
IOException {
// 客户端:
// * 1、服务端点
Socket s = new Socket("169.254.153.61", 20020);
// * 2、读取客户端已有的图片数据
FileInputStream fis = new FileInputStream(
"D:\\JDoc\\DemoForLeaning07\\ME.jpg");
// * 3、通过socket输出流将数据发送给服务端
OutputStream os = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
// 书写标记
s.shutdownOutput();
// * 4、读取服务端反馈信息
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int lens = in.read(bufIn);
System.out.println(new String(bufIn, 0, lens));
// * 5、关闭资源
fis.close();
s.close();
}
}
三、URL类1、URL:代表一个统一资源定位符,它是指向互联网“资源”指针。
构造函数:
URL(Stirng spec) //根据 String 表示形式创建 URL 对象
URL(Stirng protocol , String host , int port , String file)
//根据指定 protocol、host、port 号和 file 创建 URL 对象。Protocol 代表协议。
主要方法:
String getFile() //获取此 URL 的文件名。
String getHost() //获取此 URL 的主机名(如果适用)。
String getPath() //获取此 URL 的路径部分。
int getPort() //获取此 URL 的端口号。
String getProtocol() //获取此 URL 的协议名称。
String getQuery() //获取此 URL 的查询部
URLConnection openConnection()
//返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。也就是调用此对象,就会连接 URL 主机,返回主机对象。 每次调用此连接 URL 的协议处理程序的 openConnection()方法都打开一个新的连接。
InputStream OpenStream()
//开流打开此 URL 的连接,并返回一个用于从该连接读入的InputStream,相当于openConnection().getInputStream()。
String getConnectionType() //返回连接的头字段值
Note: 若地址信息中没有指定 port 端口号,则 get port()返回-1。当 port 等于-1 时,指定默认端口号为 80。
示例如下:
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
class URLDemo
{
public static void main(String[] args) throws MalformedURLException
{
URL url = new URL("http://192.168.1.254/myweb/demo.html?name=haha&age=30");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
}
}
class URLConnectionDemo
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://192.168.1.254:8080/myweb/demo.html");
URLConnection conn = url.openConnection();
//有此方法了,Socket就可以不用了
System.out.println(conn);
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
}
}
2、域名解析
将主机名翻译成 IP 地址,需要域名解析——DNS。
当在本地浏览器中输入一个网址,在进行访问的时候,会先在本地的hosts文件(c:\windows\system32\drivers\ext\host)中找对应的映射。若有,则直接返回请求;若无,则到公网的映射列表即DNS中找对应的映射,找到后,将主机名对应的IP地址返回给本机,本机通过这个IP地址找到对应的服务器。
例如:127.0.0.1 localhost
作用:对应的映射关系写入hosts中,将IP地址改为本机的回环地址,当访问到该IP的时候就直接去hosts中寻址,代替类去公网服务器上访问资源,以此来屏蔽恶意网站等等。
以上所述仅代表个人观点,如有出入请谅解。