JAVA网络编程
前言
本文讲的是一些基础的java网络编程用法,如有不足请指出。
1.网络通信
网络通信需要解决的问题
1.如何定位到网络上的一台主机或多台主机
2.定位到目标主机之后如何与其通信
网络编程中的要素
1.IP
2.端口号
网络通信协议
常见的网络协议
IP(Internetworking Protocol)网间网协议
IP协议是是网络层的主要协议,支持网间互联的数据报通信,它是无连接、不可靠的协议。IP 网络上的主机只能直接向本地网上的其他主
机发送数据包
TCP(Transport Control Protocol)传输控制协议
TCP协议是面向连接(需要在客户端和服务器之间建立连接)、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达,安全性可以得到保障)传输层协议。
*TCP全称传输控制
协议,必须对数据的传输进行控制。
1.TCP连接过程:
第一次握手
客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
*为什么要进行三次握手:防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。
2.TCP断开连接过程:
第一次挥手
若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
第二次挥手
B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。
第三次挥手
B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。
第四次挥手
A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。
3.TCP协议的特点
面向连接
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
仅支持单播传输
每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。
面向字节流
TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。
可靠传输
对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
提供拥塞控制
当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞
TCP提供全双工通信
TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)
UDP(User Datagram Protocol)用户数据报协议
1.无连接:
只知道对端的IP和端口号就可以发送,不需要实现建立连接。
2.不可靠:
没有确认机制, 没有重传机制。如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
3.面向数据报:
应用层交给UDP多长的报文, UDP原样发送既不会拆分,也不会合并。
4.UDP的缓冲区:
UDP存在接收缓冲区,但不存在发送缓冲区。
5.UDP是一种全双工
通信协议。
UDP协议首部中有一个16位的大长度. 也就是说一个UDP能传输的报文长度是64K
(包含UDP首部)。如果我们需要传输的数据超过64K, 就需要在应用层手动的分包
, 多次发送, 并在接收端手动拼装。
注:还有很多协议类型,目前写的网络编程只在传输层上做文章所以只列出这几种
网络模型
OSI七层网络模型 | 对应部分网络协议 |
---|---|
应用层 | HTTP,TFTP,FTP,NFS等 |
表示层 | Telnet,Rlogin等 |
会话层 | SMTP,DNS等 |
传输层 | TCP,UDP等 |
网络层 | IP,ICPM,ARP,RARP等 |
数据链路层 | FDDI,Ethernet等 |
物理层 | IEEE 802.1A,IEEE 802.2到IEEE 802.11 |
TCP/IP四层概念模型 | 对应部分网络协议 |
---|---|
应用层 | 对应上图的应用层到会话层 |
传输层 | 对应上图的传输层 |
网络层 | 对应上图的网络层 |
数据链路层 | 对应上图的数据链路层和物理层 |
IP(InetAddress)
可用windows系统可在cmd中用ipconfig查看
组成
IP地址由四段组成,每个字段是一个字节,8位,最大值是255,
IP地址由两部分组成,即网络地址和主机地址。网络地址表示其属于互联网的哪一个网络,主机地址表示其属于该网络中的哪一台主机。
二者是主从关系。
作用
IP地址的四大类型标识的是网络中的某台主机。IPv4的地址长度为32位,共4个字节,但实际中我们用点分十进制记法。
本地主机:127.0.0.1(localhost)
ip地址的分类
1.网络号和主机号
分为A、B、C三类及特殊地址D、E。 全0和全1的都保留不用。
A类:(1.0.0.0-126.0.0.0)(默认子网掩码:255.0.0.0或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类IP地址的最前面
为“0”,所以地址的网络号取值于1~126之间。一般用于大型网络。
B类:(128.0.0.0-191.255.0.0)(默认子网掩码:255.255.0.0或0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类IP地址的
最前面为“10”,所以地址的网络号取值于128~191之间。一般用于中等规模网络。
C类:(192.0.0.0-223.255.255.0)(子网掩码:255.255.255.0或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类IP地
址的最前面为“110”,所以地址的网络号取值于192~223之间。一般用于小型网络。
D类:是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户[1] 。
E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的网络号取值于240~255之间。
在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:
A类地址:10.0.0.0~10.255.255.255
B类地址:172.16.0.0~172.31.255.255
C类地址:192.168.0.0~192.168.255.255
2.ipv4/ipv6
ipv4:IPv4协议具有32位(4字节)地址长度,Pv4地址是以小数表示的二进制数
ipv6:IPv6协议具有128位(16字节)地址长度,IPv6地址是以十六进制表示的二进制数。
3.公网-私网
公网(互联网):公有地址(Public address):由Inter NIC(因特网信息中心)负责。这些IP 地址分配给注册并向Inter NIc提出申请的组织机构,公有IP 全球唯一,通过它直接访问因特网(直接能上网)。
私网(局域网):私有地址(Private address):属于非注册地址,专门为组织机构内部使用。
域名
方便记忆ip
java中获取ip地址
package net;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class study {
public static void main(String[] args) throws UnknownHostException {
//查询目标网页ip
InetAddress byName = InetAddress.getByName("www.baidu.com");//www.baidu.com/36.152.44.95
System.out.println(byName);
//查询本机ip
System.out.println(InetAddress.getLocalHost());//DESKTOP-T0E95V5/192.168.153.1
System.out.println(InetAddress.getByName("localhost"));//localhost/127.0.0.1
System.out.println(InetAddress.getByName("127.0.0.1"));///127.0.0.1
//常用方法
System.out.println(byName.getAddress());//[B@1b6d3586
System.out.println(byName.getHostAddress());//36.152.44.96
System.out.println(byName.getCanonicalHostName());//36.152.44.96
System.out.println(byName.getHostName());//www.baidu.com
}
}
端口
端口表示计算机上一个程序的进程
在网络技术中,端口(Port)大致有两种意思:一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。
我们这里将要介绍的就是逻辑意义上的端口。我们这里所说的端口,不是计算机硬件的I/O端口,而是软件形式上的概念.工具提供服务类型的不同,端口分为两种,一种是TCP端口,一种是UDP端口。计算机之间相互通信的时候,分为两种方式:一种是发送信息以后,可以确认信息是否到达,也就是有应答的方式,这种方式大多采用TCP协议;一种是发送以后就不管了,不去确认信息是否到达,这种方式大多采用UDP协议。对应这两种协议的服务提供的端口,也就分为TCP端口和UDP端口。
windows环境下可以在cmd中输入
netstat -an
netstat -ano|findstr "特定端口" #查看指定端口
tasklist|findstr "特定端口" #查看指定端口进程
Ctrl + Shift + Esc #任务管理器
查看端口状态
如果输入:netstat -an,提示:不是内部或外部命令,也不是可运行的程序或批处理文件。 提示不是内部或外部命令的原因是:cmd当前操作不在系统文件夹system32下,那么只需输入:cd c:/WINDOWS/system32 ;即可将当前操作路径切换到Windows 操作系统的系统文件夹下。 然后再输入netstat -an,即可解决。
网络端口的分类
(1)公认端口(Well Known Ports)
0~1023,它们紧密绑定于一些服务,通常这些端口的通讯明确表明了某种服务的协议,如:80端口对应与HTTP通信,21端口绑定
与FTP服务,25端口绑定于SMTP服务,135端口绑定与RPC(远程过程调用)服务。
(2)注册端口(Registered Ports)
1024~49151,它们松散的绑定于一些服务,也就是说有许多服务绑定于这些端口,这些端口同样用于其他许多目的,如:许多系统
处理端口从1024开始
(3)动态和/或私有端口(Dyanmic and /or Private Ports)
49152~65535,理论上,不应为服务分配这些端口,通常机器从1024开始分配动态端口。例外:SUN的RPC端口从32768开始。
java代码
package net;
import java.net.InetSocketAddress;
public class port {
public static void main(String[] args) {
InetSocketAddress inetSocketAddress2 = new InetSocketAddress("localhost", 8080);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8080);
System.out.println(inetSocketAddress);///127.0.0.1:8080
System.out.println(inetSocketAddress2);//localhost/127.0.0.1:8080
System.out.println(inetSocketAddress.getAddress());///127.0.0.1
System.out.println(inetSocketAddress.getPort());//8080
System.out.println(inetSocketAddress.getHostName());//127.0.0.1
}
}
TCP编程
基础过程
1.客户端
1.通过socket连接服务器
2.发送消息
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
public class client {
public static void main(String[] args) throws IOException {
OutputStream os =null;
ByteArrayOutputStream byteArrayOutputStream =null;
try {
InetAddress serverip = InetAddress.getByName("127.0.0.1");
int port = 9999;
//创建一个socket连接
Socket socket = new Socket(serverip,port);
//发送消息,用I/O流
os = socket.getOutputStream();
os.write("hello 你好".getBytes(StandardCharsets.UTF_8));
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (os!=null)os.close();
}
}
}
2.服务端
1.建立服务端口 ServerSocket
2.等待用户连接,accpet
3.接收用户的消息
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//提升作用域
ServerSocket serverSocket=null;
Socket socket =null;
InputStream is =null;
ByteArrayOutputStream byteArrayOutputStream =null;
try {
serverSocket = new ServerSocket(9999);
//等待客户端连接
socket = serverSocket.accept();
//读取消息
is = socket.getInputStream();
/*容易乱码
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
String msg = new String(buffer,0,len);
System.out.println(msg);
}
*/
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
byteArrayOutputStream.write(buffer,0,len);
}
System.out.println(byteArrayOutputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭资源
if(byteArrayOutputStream!=null) byteArrayOutputStream.close();
if(is!=null) is.close();
if(socket!=null) socket.close();
if(serverSocket!=null) serverSocket.close();
}
}
}
实现文件上传
1.客户端
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Fclient {
public static void main(String[] args) throws IOException {
//1.创建socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);
//2.创建输出流
OutputStream os = socket.getOutputStream();
//3.读取文件
FileInputStream fie = new FileInputStream(new File("E:\\javasestudy\\src\\example.png"));
//4.写入文件
byte[] buffer = new byte[1024];
int len;
while((len=fie.read(buffer))!=-1){
os.write(buffer,0,len);
}
//5.通知服务器我已经传输完了
socket.shutdownOutput();
//6.确定服务端接收完毕了,才断开连接
InputStream is = socket.getInputStream();
//String
ByteArrayOutputStream to = new ByteArrayOutputStream();
byte[] buffer2 = new byte[2048];
int len2;
while((len2= is.read(buffer2))!=-1){
to.write(buffer2,0,len2);
}
System.out.println(to);
//关闭资源
to.close();
is.close();;
fie.close();
socket.close();
}
}
2.服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Fserver {
public static void main(String[] args) throws IOException {
//1.创建服务
ServerSocket serverSocket = new ServerSocket(9999);
//2.监听客户端连接
Socket socket = serverSocket.accept();
//3.获取输入流
InputStream is = socket.getInputStream();
//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("recive.png"));
byte[] buffer=new byte[1024];
int len;
while((len= is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//5.通知服务端我接收完毕了
OutputStream res = socket.getOutputStream();
res.write("接收完毕,可以断开".getBytes(StandardCharsets.UTF_8));
//6.结束关闭
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
Tomcat
概念:
Tomcat 服务器是一个开源的轻量级Web应用服务器,在中小型系统和并发量小的场合下被普遍使用,是开发和调试Servlet、JSP 程序的首选。
网站参考
转换
原来的基础上,我们的客户端和服务端都是自己手动书写的
客户端C,服务端S
现在我们可以将客户端与服务端替代
客户端(浏览器)B,服务端(Tomcat)S
环境部署
环境布置完后后续可以使用进行测试开发。
UDP编程
单向发送接收
客户端
import java.net.*;
import java.nio.charset.StandardCharsets;
public class Uclient {
public static void main(String[] args) throws IOException {
//建立一个socket
DatagramSocket socket = new DatagramSocket();
//建立包
String msg = "你好";
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9999;
DatagramPacket packet = new DatagramPacket(msg.getBytes(StandardCharsets.UTF_8), 0, msg.getBytes(StandardCharsets.UTF_8).length, localhost, port);
//发送包
socket.send(packet);
//关闭流
socket.close();
}
}
服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
//等待客户端发送
public class Uservice {
public static void main(String[] args) throws IOException {
//开放端口
DatagramSocket socket = new DatagramSocket(9999);
//接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);//接收
socket.receive(packet);//阻塞接收
System.out.println(new String(packet.getData(),0, packet.getLength()));
//关闭连接
socket.close();
}
}
连续发送接收
发送方
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Sender {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8888);
//准备数据
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while(true){
String data = reader.readLine();
byte[] datas = data.getBytes();
DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress("localhost",9999));
socket.send(packet);
if (data.equals("bye".trim())){
break;
}
}
socket.close();
}
}
接收方
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Recive {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999);
while(true){
//准备接收包
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container,0,container.length);
socket.receive(packet);
//断开连接
byte[] data = packet.getData();
String reciveData = new String(data,0,packet.getLength());
System.out.println(reciveData);
if(reciveData.equals("bye".trim())){
break;
}
}
socket.close();
}
}
模拟聊天
线程一(控制接收)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Talkrecive implements Runnable{
DatagramSocket socket=null;
private int port;
private String fromstr;
public Talkrecive(int port,String fromstr) throws SocketException {
this.fromstr=fromstr;
this.port=port;
socket = new DatagramSocket(port);
}
@Override
public void run() {
while(true){
//准备接收包
try {
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container,0,container.length);
socket.receive(packet);
//断开连接
byte[] data = packet.getData();
String reciveData = new String(data,0,packet.getLength());
System.out.println(fromstr+":"+reciveData);
if(reciveData.equals("bye".trim())){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
socket.close();
}
}
线程二(控制发送)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class Talksend implements Runnable{
DatagramSocket socket=null;
BufferedReader reader=null;
private int fromport;
private String toip;
private int toport;
public Talksend(int fromport,String toip,int toport) throws SocketException {
this.fromport=fromport;
this.toip=toip;
this.toport=toport;
socket = new DatagramSocket(fromport);
reader = new BufferedReader(new InputStreamReader(System.in));
}
@Override
public void run() {
while(true){
try {
String data = null;
data = reader.readLine();
byte[] datas = data.getBytes();
DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toip,this.toport));
socket.send(packet);
if (data.equals("bye".trim())){
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象1
import java.net.SocketException;
public class Talk1 {
public static void main(String[] args) throws SocketException {
//开启线程
new Thread(new Talksend(7777,"Localhost",9999)).start();
new Thread(new Talkrecive(8888,"one")).start();
}
}
对象2
import java.net.SocketException;
public class Talk2 {
public static void main(String[] args) throws SocketException {
//开启线程
new Thread(new Talksend(5555,"localhost",8888)).start();
new Thread(new Talkrecive(9999,"two")).start();
}
}
最后目标图式如下
URL
概念
统一资源定位符:定位资源所用(Uniform Resource Locator)
组成
URL由三部分组成:资源类型、存放资源的主机域名、资源文件名。
URL的一般语法格式为:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
protocol:协议
hostname:IP地址
port:端口
path:资源路径
parameters:参数
query:查询
可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP。NET等技术制作的网页)传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。
fragment:信息片断
信息片断,字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。
Windows主机不区分域名大小写,Linux和Unix区分。
import java.net.MalformedURLException;
import java.net.URL;
public class url {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=bluestone&passworld=123");
System.out.println(url.getProtocol());//http
System.out.println(url.getAuthority());//localhost:8080
System.out.println(url.getPort());//8080
System.out.println(url.getPath());///helloworld/index.jsp
System.out.println(url.getFile());///helloworld/index.jsp?username=bluestone&passworld=123
System.out.println(url.getQuery());//username=bluestone&passworld=123
}
}
下载实现(本地)
1.首先创建一个待下载文件。hello.txt
2.将待下载的文件拖如tomcat的网页资源中。
3.启动tomcat
4.编写代码
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class url {
public static void main(String[] args) throws IOException {
//下载地址
URL url = new URL("http://localhost:8080/hello.txt");
//连接这个资源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("finish.txt");
byte[] buffer = new byte[1024];
int len;
while ((len=inputStream.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
inputStream.close();
urlConnection.disconnect();//断开连接
}
}
5.成功下载网页资源
下载实现(联网)
edge中键入F12打开开发者工具平台,筛选出你想要下载的页面资源,复制他的url地址,再代码中替换,即可下载。
总结
讲述了一些位于网络层和传输层的基本协议在java中如何编程使用,涉及一些计算机网络的基础知识,希望对您有帮助。