目录
1. 网络编程中最需要解决的两个问题
- 如何准确的定位网络上的一台电脑以及定位电脑上的特定应用。---->对应IP地址和端口号。
- 如何高效的进行数据传输。---->对应网络通信协议。
1.1 IP地址和端口号
- IP用来唯一标识网络上的一台计算机。
- 在Java中用InetAddress类来代表IP。
- 本地回路地址为:127.0.0或者使用localhost。
- IP按不同的分类方式可分为IPV4、IPV6和万维网、局域网。
- 由于IP难以记忆,我们也可以使用域名来代替IP。
- 端口号是用来标识正在计算机上运行的一个进程。规定为0~65535,其中0~1023为公认端口被预先定义的服务通信占用;1024~49151为注册端口,用来分配给用户进程或应用程序;49151~65535为动态端口。
- IP和端口的组合得到一个网络套接字:Socket。
1.2 网络通信协议
根据TCP/IP模型,可以将通信协议分为四层,分别为:应用层、传输层、网络层、物理+数据链路层。其中各层对应的协议如下
- 应用层:HTTP、FTP、DNS......
- 传输层:TCP、UDP......
- 网络层:IP、ICMP、ARP......
- 物理+数据链路层:Link
我们主要关注应用层和传输层。
创建一个IP对象:使用InetAddress的静态方法InetAddress.getByName(String ip)。
例:创建一个本地IP对象和一个IP为本地,端口号为3306的套接字。
InetAddress ip=InetAddress.getByName("127.0.0");
Socket socket=new Socket(ip,3306);
/*这里Socket的构造器第一个参数为InetAddress对象,第二个参数为端口号*/
2. 网络层的通信协议
2.1 TCP协议
- 使用TCP协议前必须先建立TCP连接,形成传输数据通道。
- 传输前,采用“三次握手”方式,点对点通信,是可靠的。
- TCP协议进行通信有两个应用进程:客户端和服务端。
- 在连接中可进行大数据量的传输。
- 在传输完毕后需要释放已建立的连接,效率比较低。
2.1.1 三次握手
- 用来在建立连接时确保连接可靠。
- 第一次是客户端发送syn报文,并置发送序号为X
- 第二次是服务端发送syn+ACK报文,并置发送序号为Y,再确认序号为X+1
- 第三次是客户端发送ACK报文,并置序号为Z,在确认序号问为Y+1。
- 原则上还可以继续进行握手,但是在三次握手以后就可以大概率确保连接可靠,所以不进行更多的握手。
2.1.2 四次挥手
- 用来释放连接。
- 第一次是主动放发送Fin+ACK报文,并置序发送序号为X。
- 第二次是被动放发送ACK报文,并置发送序号为Z,在确认序号为Y。
- 第三次是被动放发送Fin+ACK报文,并置发送序号为Y,在确认序号为X。
- 第四次是主动放发送ACK报文,并置发送序号为X,在确认序号为Y。
2.2 UDP协议
- 将数据、原、目的封装成包,不需要建立连接。
- 每个数据包的大小限制在64K内。
- 发送前不管对方是否准备好,接收方接收到也没有确认,是不可靠的。
- 可以广播发送。
- 发送数据结束后无需释放连接,开销小,速度快。
2.3 TCP协议使用举例
例:使用TCP协议完成如下功能:创建一个客户端和服务端,客户端向服务端发送文字“我是客户端”,服务端将其输入到控制台,同时向客户端发送“我已收到,我知道你是客户端。”客户端将其输出到控制台。
package test.inetaddress;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
public class TCPTest {
@Test
public void customer() {
InetAddress ip=null;
Socket socket=null;
OutputStream ops=null;
InputStream ips=null;
ByteArrayOutputStream baos=null;
try {
//创建Socket对象指明IP和端口号
ip=InetAddress.getByName("localhost");
socket=new Socket(ip,35550);
//获取输出流,用来输出数据
ops=socket.getOutputStream();
//写出数据
ops.write("你好,我是客户端".getBytes());
//告诉服务端已经发完了
socket.shutdownOutput();
//获取输入流,用来输入数据
ips=socket.getInputStream();
//将数据暂时存储在ByteArrayOutputStream中
baos=new ByteArrayOutputStream();
//读取数据
byte [] buffer=new byte[5];
int lenth;
while((lenth=ips.read(buffer))!=-1) {
baos.write(buffer, 0, lenth);
}
System.out.println(baos.toString());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ops.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ips.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void server() {
/*服务端*/
ServerSocket ss=null;
Socket s=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
OutputStream os=null;
try {
ss=new ServerSocket(35550);
s=ss.accept();
is=s.getInputStream();
baos=new ByteArrayOutputStream();
byte [] buffer=new byte[5];
int lenth;
while((lenth=is.read(buffer))!=-1) {
baos.write(buffer, 0, lenth);
}
System.out.println(baos.toString());
os=s.getOutputStream();
os.write("我已收到,我知道你是客户端。".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
ss.close();
} catch (IOException e) {
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.3.1 代码说明
- 客户端通过OutputStream对象将数据传输发送给服务器端,同时将Socket对象发送过去,与服务器端建立连接;服务器端通过SeverSocket对象调用accept()方法来接收Socket对象,在获取InputStream对象来读取从客户端发送过来的数据。
- 在使用InputStream对象读取数据时,使用了read()方法,该方法时阻塞式方法,需要客户端在发送完数据后使用shutdownOutput()来告诉服务器端已经发完数据了。否则服务器端会一直读取与客户端建立的inputstream流。
- 在读取一次数据后,我们将数据保存在一个byte数组中,但是对于除英文单词外的其他文字,可能会出现一个字符被拆分成多个字节分别出现在两次不同的读取中,这样就会出现乱码。为了解决这个问题,我们将读取到的数据再从byte数组转移到ByteArraysOutputStream的对象中,当数据读取完以后在从该对象中一次性读取出来。
- 在服务完成后Socket对象也是需要我们主动关闭的资源。
3. URL介绍
URL全称统一资源定位符,可以创建URL的一个对象,同时用 以下方法获取相关信息
- URL(String url):用来获取一个URL对象。
- public String getProtocol():获取该URL的协议名。
- public String getHost():获取该URL的主机名。
- public String getPort():获取该URL的端口号。
- public String getPath():获取该URL的文件路径。
- public String getFile():获取该URL的文件名。