网络编程
网络编程
网络基础
计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
网络通信要素
想要实现网络中的主机互相通信,需要知道通信双方的地址(IP、端口号)和一定的规则(网络通信协议)
IP和端口号
IP
IP的认识
-
唯一的标识Internet上的计算机(通信实体)
-
在Java中,使用InetAddress类代表IP
-
本地回环地址(hostAddress)
主机名(hostName):localhost
-
IP地址分类
- 方式一:IPV4 和 IPV6
- IPV4:4个字节组成,4个范围是0-255。2011年初已经用尽。以点分十进制表示,如192.168.0.1
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用4个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- 方式二:公网地址(万维网使用)和私有地址(局域网使用)。
- 192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
- 方式一:IPV4 和 IPV6
-
特点:不易记忆
InetAddress类的使用
-
两种方式表示地址:
-
InetAddress类没有提供公共的构造器,而是提供了几个静态方法来获取InetAddress 实例
- 获取本地的IP地址:
public static InetAddress getLocalHost()
- 通用的方法(host可以是域名或是IP地址):
public static InetAddress getByName(String host)
- 获取本地的IP地址:
-
几个常用的方法(前两个常用)
- public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)。
- public String getHostName() :获取此 IP 地址的主机名
- public boolean isReachable(int timeout):测试是否可以达到该地址
端口号
端口号的认识
端口号标识正在计算机上运行的进程(程序),可以用端口号区别不同的进程
- 不同的进程有不同的端口号
- 端口号被规定为一个 16 位的整数 0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)
- 动态/ 私有端口:49152~65535。
端口号和IP地址组合在一起得出一个网络套接字:Socket
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
因为网络协议过于复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。
最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
TCP/IP协议簇
传输层协议中两个重要的协议:
- 传输控制协议TCP(Transmission Control Protocol)
- 用户数据报协议UDP(User Datagram Protocol)
TCP协议
类似于生活中打电话
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议
类似于发短信
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
Socket
概念
- 网络上具有唯一标识的IP地址和端口号组合在一起构成唯一能识别的标识符套接字Socket
- 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
Socket常用的方法
Socket类的常用构造器
-
public Socket(InetAddress address,int port)
创建一个流套接字并将其连接到指定IP地址的指定端口号。
-
public Socket(String host,int port)
创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket类的常用方法:
-
public InputStream getInputStream()
返回此套接字的输入流。可以用于接收网络消息
-
public OutputStream getOutputStream()
返回此套接字的输出流。可以用于发送网络消息
-
public InetAddress getInetAddress()
此套接字连接到的远程IP地址;如果套接字是未连接的,则返回null。
-
public InetAddress getLocalAddress()
获取套接字绑定的本地地址,即本端的IP地址
-
public int getPort()
此套接字连接到的远程端口号;如果尚未连接套接字,则返回0。
-
public int getLocalPort()
返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回-1,即本端的端口号。
-
public void close()
关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。
-
public void shutdownInput()
如果在套接字上调用shutdownInput()后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。即不能在从此套接字的输入流中接收任何数据。
-
public void shutdownOutput()
禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列。如果在套接字上调用shutdownOutput()后写入套接字输出流,则该流将抛出IOException。即不能通过此套接字的输出流发送任何数据。
基于Socket的TCP编程
Java语言的基于套接字编程分为服务端编程和客户端编程
客户端Socket的基本的步骤
- 创建Socket对象,指明服务器端的ip和端口号
- 创建的同时会自动向服务器方发起连接
- 获取一个输出流,用于输出数据
- 写出数据
- 资源关闭
public void client(){
//客户端
Socket socket = null;
OutputStream os = null;
try {
//1. 创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,8899);
//2. 获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3. 写出数据
os.write("您好,我是客户端".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 资源的关闭
try {
if(os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端Socket的基本步骤
- 创建服务器端的ServerSocket,指明自己的端口号
- 服务器必须事先建立一个等待客户请求建立套接字
的 连接的ServerSocket对象
- 服务器必须事先建立一个等待客户请求建立套接字
- 调用accept()方法,表示接收来自客户端的Socket
- 获取一个输入流
- 读取输入流中的数据
- 资源的关闭
public void server(){
//服务端
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
ByteArrayOutputStream baos = null;
try {
//1. 创建服务器端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8899);
//2. 调用accept()方法,表示接收来自客户端的Socket
socket = serverSocket.accept();
//3. 获取一个输入流
inputStream = socket.getInputStream();
//4. 读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = inputStream.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//5. 资源的关闭
try {
if(baos != null)
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(inputStream != null)
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(serverSocket != null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意
-
客户端发送文件给服务端,服务端将文件保存在本地
- 客户端发送文件时需要先利用节点流将文件读取,在利用Socket写出
FileInputStream fis = new FileInputStream(new File("殷志源.png")); byte[] buffer = new byte[1024]; int len; while((len = fis.read(buffer)) != -1){ ops.write(buffer,0,len); }
- 服务端接受文件时,因为要保存在本地,要利用节点流
FileOutputStream fos = new FileOutputStream(new File("殷志源(1).png")); byte[] buffer = new byte[1024]; int len; while((len = ips.read(buffer)) != -1){ fos.write(buffer,0,len); }
- 客户端发送文件时需要先利用节点流将文件读取,在利用Socket写出
-
从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
-
服务端
//服务器端给客户端反馈 OutputStream os = socket.getOutputStream(); os.write("发送成功".getBytes());
-
客户端
//关闭数据的输出 socket.shutdownOutput(); //接收来自服务器端的数据并显示在控制台上 InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer1 = new byte[1024]; int len1; while((len = is.read(buffer1)) != -1){ baos.write(buffer1,0, len); } System.out.println(baos.toString());
read()方法是阻塞式方法,程序一直停在read()方法这里,等待数据。没有数据就不继续往下执行,直到得到数据。所以需要调用socket的shutdownOutput()方法,关闭数据的输出
阻塞式方法:在程序调用改方法时,必须等待输入数据可用或者检测到输入结束或者抛出异常,否则程序会一直停留在该语句上,不会执行下面的语句。
-
UDP网络编程
- 类DatagramSocket和DatagramPacket实现了基于 UDP协议网络程序。其中可以理解为DatagramSocket为快递员,DatagramPacket为包裹,即为需要传输的数据
- UDP数据报通过数据报套接字DatagramSocket发送和接收
- DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接
代码举例
其中没有用try-catch来处理异常,选择抛出异常,但是实际使用时要用try-catch来处理异常,此处举例为简写
@Test
public void send() throws IOException {
//发送端
DatagramSocket socket = new DatagramSocket();
String str = "UDP方式发送文件";
byte[] data = str.getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddress,9090);
socket.send(packet);
socket.close();
}
@Test
public void receiver() throws IOException {
//接收端
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0 ,packet.getLength()));
socket.close();
}
URL编程
URL
- URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址。
- URL的基本结构由5部分组成
< 传输协议>://< 主机名>:< 端口号>/< 文件名># 片段名? 参数列表
常用方法
- public String getProtocol( ) 获取该URL的协议名
- public String getHost( ) 获取该URL的主机名
- public String getPort( ) 获取该URL的端口号
- public String getFile( ) 获取该URL的文件名
- public String getQuery( ) 获取该URL的查询名