Java网络编程

计算机网络就是通过传输介质、通信设施和网络协议,把分散再不同四点的计算设备互连起来,实现资源共享和数据传输的系统

TCP/IP协议簇

TCP/IP栈是一系列网络协议的总和,是构成网络通信的核心骨架

分层模型

TCP/IP协议栈的分层模型常见的有两个,分别是TCP/IP参考模型和ISO组织提出的OSI参考模型。再TCP/IP参考模型中将网络分为网络访问层【数据链路层】、互联网层【网络层】、传输层、应用层共4层,OSI参考模型分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层共7层

OSI参考模型是一个开放的通信系统互联参考模型

TCP/IP参考模型

TCP/IP协议采用4层架构。从上向下分为应用层、传输层、网络层和链路层,每一层都可以使用其下一层的协议完成自己的需求,不允许下层访问上层

当通过http协议发起一个请求时,从上往下一次通过应用层、传输层、网络层和链路层,每一层相关协议都依次对数据报进行处理,并携带响应的首部,最终再链路层生产以太网数据报,通过物理介质进行传输,传送到对方主机后,对方主句再依次从下向上使用响应协议进行拆包,最终经应用层数据交给应用程序进行处理

配送车就是物理介质,配送站就是网关、快递员就是路由器、收货地址就是IP地址、练习电话就是MAC地址

三次握手

TCP是面向连接的协议,连接需要有三个阶段:连接建立、数据传送和连接释放。其中连接建立需要经历三个步骤,通常称为三次握手

三次握手发生在客户端和服务器需要建立TCP连接时主要作用就是让客户端和服务器都知道自己和对方的发送、接收能力均正常建立可靠连接

  • 刚开始客户端处于Colsed状态,服务器处于Linsten(收听)状态

  • 第一次握手:客户端向服务端发送一个SYN报文初始化序列ISN,同时选择一个序列号seq = x。SYN = 1的报文段不能携带数据,但是需要消耗一个序号,客户端处于SYN-SENT(同步已发送)状态,此时报文头部报文中SYN = 1,seq = x

  • 第二次握手:服务端接收到客户端的SYN报文之后,也会向客户端发送一个报文作为应答,如同意建立连接,则向客户端发送确认,在确认报文段中应把SYN和ACK都置1,确认号时ack = x+1,同时也为自己选择一个初始序号seq = y,注意这个报文段也不能携带数据,同样消耗一个序列号,之后服务器段处于SYN-RCND(同步收到)状态,此时应答报文中SYN = 1,ACK = 1,ack = x = 1,seq = y

  • 客户端收到服务端的确认后,还要向服务端给出确认,确认报文段的ACK置1,确认号ack = y+1,而自己的序号seq = x + 1,TCP的标准规定,ACK报文段可以携带数据,但是不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq = x+1,这时TCP连接已经建立,客户端进入 ESTABLISHED(已建立连接)状态

  • 服务端都到客户端的确认后进入ESTABLISHED(已建立连接)状态,至此。TCP三次握手完成

四次挥手

由于TCP连接是双工的,所以每个方向都必须单独进行关闭

TCP四次挥手用于客户端想要与服务端断开连接的时候

  • 第一次挥手:客户端的应用进程先向其TCP发送连接释放报文段,报文中会指定一个序列号,即发出连接释放的报文段(FIN = 1,seq = u),并停止发送数据,主动关闭TCP连接进入FIN-WAIT-1(终止等待1)状态等待服务端的确认

  • 第二次挥手:服务端收到FIN之后,会发送ACK报文,且把客户端的序列号值+1作为ACK报文的序列号值,此时服务端处于COLSE-WAIT(关闭等待)状态,TCP连接处于半关闭状态,客户端到服务端的连接已经释放。服务端发送的报文段(ACK = 1,ack = u + 1,seq = v),当客户端收到服务端的确认后,进入FIN-WAIT-2(终止等待2)状态,等待服务端发出的连接释放报文段

  • 第三次挥手:当服务端也想断开连接时,给客户端发送FIN报文,且指定一个序号,此时服务端处于LAST-ACK(最后确认)状态,连接释放的报文段(FIN = 1,ACK = 1,ack = u + 1,seq = w)

  • 第四次挥手:客户端收到FIN之后,一样发送一个ACK报文作为应答且服务端的序号值+1作为自己ACK报文的序号值,确认报文段(ACK = 1,seq = u + 1,ack = w + 1),此时客户端处于TIME-WAIT(时间等待)状态,等待2MSL后客户端进入CLOSE状态,等待服务端接收确认报文段后,关闭TCP连接,服务端进去CLOSED状态

为什么连接的时候是三次握手,而断开的时候是4次挥手

TCP连接是双工通信的,而断开时双方都需要确定两个问题:自己是否还有数据发送,对端是否还有数据要发送,而四次挥手正好在双方同步了这两个问题

  • 第一次挥手:客户端告诉服务端自己的数据已全部发送,客户端可以回收发送缓冲区,服务端可以回收接收缓冲区

  • 第二次挥手:服务端告诉客户端收到了关闭信息

  • 第三次挥手:服务端告诉客户端自己的数据已全部发送,服务端可以回收发送缓冲区,客户端可以回收接收缓冲区

  • 第四次挥手:客户端告诉服务端收到了关闭信息

可以发现,四次挥手一次都不能少,如果少了其中任何一次,总有一方不能可靠的通知对方自己的数据已发送完毕,因此,连接不能可靠的断开,TCP也就成了不可靠的协议

TCP之2msl问题

2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在

发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,

必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么

对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。

在TIME_WAIT状态 时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL

等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置 SO_REUSEADDR选项达

到不必等待2MSL时间结束再使用此端口。

IP地址

在网络中定位一个机器需要通过IP地址,IP协议可以分为IPv4和IPv6两种,IPv4采用的是点分治十进制的记法,例如192.168.1.3

Java中提供了一个InetAderss实现对IP地址的封装,子类Inet4Address和Inet6Address,这个类一般会和Socket一起使用

InetAddress没有共有的构造方法,必须使用静态方法获取对应的实例

InetAddress ia = InetAddress.getByName("www.baidu.com");//依赖DNS
System.out.println(ia);  //输出格式为www/baidu.com/14.215.177.39
​
System.out.println(ia.getHostName());//获取主机名称www.baidu.com
System.out.println(ia.getHostAddress());//获取主机对应的IP地址
​
InetAddress ia2 = InetAddresss.getLocalHost();//获取当前主机的IP地址

特殊方法isReachable用于检测是否可以到达指定的地址,防火墙或者服务器配置可能会阻塞请求,使得访问指定地址时处于不用达状态

InetAddress ia = InetAddress.getByName("14.215.177.254");//参数可以是主机名,也可以是Ip地址
//参数int类型,表示超时时间,单位ms
boolean res = ia.isReachable(2000);
System.out,ptintln(res);

URL编程

java.net.URL对象用于代表一个网络环境的资源,资源可以是简单的文件或者目录,也可以是复杂对象的引用,例如数据库或者搜索引擎的查询。URL使用协议名、主机名、端口号和资源组成,基本格式为protocol://host:port/resource,例如http://www.yan.com:80/index.php,由于不同协议有对应的标准端口号,如果使用标准端口,这个端口号可以省略,http协议的标准端口号为80

  • URL统一资源定位器,实际上就是一个资源的指针

  • URL统一资源标识符,实际上就是一个URL的名称

  • 目前考虑到http协议缺少安全机制,很容易被监听;所以引入https协议。https = http + SSL安全套接层,可以实现传输数据的加密,默认端口号443

try {
    URL url = new URL("http://campus.51job.com/ssjkq/images/banner.jpg");
    InputStream is = url.openStream();
    OutputStream os = new FileOutputStream("d:/cc.jpg");
    byte[] buffer = new byte[8192];
    int len = 0;
    while ((len = is.read(buffer)) > 0)
        os.write(buffer, 0, len);
        is.close();
        os.close();
} catch (Exception e) {
    e.printStackTrace();
}

可以通过URL对象获取访问相关的属性

  • String getFile() 获取资源名

  • String geHost() 获取主机名称

  • String getPath() 获取路径部分的名称

  • int getPort() 获取端口号,如果不能获取则返回-1

URL url = new URL("http://campus.51job.com:80/ssjkq/images/banner.jpg");
System.out.println(url.getFile());
System.out.println(url.getHost());
System.out.println(url.getPath());
System.out.println(url.getPort());

可以使用字符串解析获取相应部分的内容

String ss = "http://campus.51job.com:80/ssjkq/images/banner.jpg";
String Filename = ss.substring(ss.lastIndexOf("/") + 1);
System.out.println(Filename);
int pos1 = ss.indexOf("http://") + "http://".length();
int poe2 = ss.indexOf("/", pos1);
String hostName = ss.substring(pos1, poe2);
System.out.println(hostName);
String PathName = ss.substring(poe2);
System.out.println(PathName);
int pos3 = ss.lastIndexOf(":");
if (pos3 != -1) {
    int pos4 = ss.indexOf("/", pos3);
    String port = ss.substring(pos3 + 1, pos4);
    System.out.println(port);
}

重要方法

  • openConnection():URL Connection可以获取输入、输出流

http协议靠的是全双工的TCP协议

  • openStream():InputStream直接获取服务器的响应输出流

URL url = new URL("https://news.cctv.com/2022/04/10/ARTIWLj3f0W2lIKwP8lp7HTb220410.shtml");
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
PrintWriter pw = new PrintWriter(new BufferedReader(new FileWriter("d:/bb.html")));
String tmp = "";
while((tmp = br.readLine()) != null){
    System.out.println(tmp);
    pw.println(tmp);
}
br.close();
pw.close();

URL vs URLConnection

从语义的角度上来说:URL代表一个资源的位置,URLConnection代表的是连接

java中提供了两种读取数据的方法:1.通过URL对象直接获取相关的网络信息。2.先获取一个URLConnection实例,然后再得到响应的InputStream和OutputStream,实现数据的读写

URL是一种简单直接的方法,但是缺乏灵活性,并且只能读取只读性质的信息;URLConnection提供了非常灵活有效的方法来读取网络资源

URL url = new URL("https://news.cctv.com/2022/04/10/ARTIWLj3f0W2lIKwP8lp7HTb220410.shtml");
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("d:/bbb.html")));
String tmp = "";
while ((tmp = br.readLine()) != null) {
    System.out.println(tmp);
    pw.println(tmp);
}
br.close();
pw.close();

TCP编程

TCP编程是一种面向虚电路连接的端对端的保证可靠传输的协议,使用TCP协议可以得到一个顺序的无差错的数据流

UDP是一种不可保证数据可靠性,但是协议简单、传输速度快。一般用于视频或者音频的传输,不需要很高的可靠性,可以容忍偶尔的丢帧

在具体编程中发送方和接收方必须成对的使用socket建立连接,在TCP协议的基础上进行通信

Socket

Socket套接字就是两个进行通信的主机之间逻辑连接的端点。socket编程实现主要涉及到客户端和服务端两方面。首先在服务器端创建一个服务器套接字ServerSocket,并将其附加到一个端口上,服务器可以通过这个端口监听客户端的连接请求。端口号就是int类型,取值范围为0到65535,但是一般0-1024属于特殊保留的端口

服务器和客户端建立连接时,需要服务器的域名或者ip地址,加上端口号,可以打开一个套接字。当服务器收到客户端的连接请求后,服务器和客户端之间的通信实际上就是一种输入输出流的操作

典型的网络编程模型

ServerSocket

java.net.SeverSocket用于表示一个服务器套接字,主要功能就是监视客户端的连接请求,并将客户端的连接请求存入到请求队列中,默认请求队列大小为50

  • ServerSocket() 创建非绑定服务器指定端口的套接字

  • ServerSocket(int) 创建绑定到服务器指定端口的套接字,其中int类型参数就是对应的监听端口号,取值范围为0-65535,一般不建议使用1024以下的端口号,其中0表示使用任意的没有被占用的端口,如果当前指定端口已经被占用,则异常

for (int i = 0; i < 65535; i++) {
    ServerSocket ss = null;
    try {
        ss = new ServerSocket(i);
    } catch (Exception e) {
        System.out.println(i + "端口已经被占用");
    } finally {
        if (ss != null)
            try {
                ss.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

Socket

客户端通过构建Socket对象实现连接请求

  • Socket(InetAddress,int) InetAddress就是需要连接的服务器,int就是服务器的监听端口号

  • Sokcet(String,int) String就是服务器的名称或者IP地址。如果构建Socket对象成功,则连接创建,否则ConnectException

简单的C/S编程

客户端发送hello信息,服务器段接收到信息后,添加一个日期信息,然乎回传给客户端,客户端再控制台上打印显示

服务器端程序

ServerSocket ss = new ServerSocket(9000);// 服务器打开端口监听
Socket socket = ss.accept(); // 阻塞当前线程,并等待客户端请求,如果客户端发起了连接请求,同时连接 成功,则返回一个socket对象
​
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
​
// 具体的数据接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println("服务器接收到客户端的数据:" + str);
​
PrintStream ps = new PrintStream(os);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = df.format(new Date());
str += ("[server:]" + date);
ps.println(str);
ps.flush();
ss.close();
socket.close();
is.close();
os.close();
br.close();
ps.close();

具体编码流程:

1.创建ServerSocket对象,绑定监听端口

2.通过accept方法阻塞程序执行,并等待监听客户端的连接请求

3.连接建立后,通过Socket对象获取输入输出流

4.通过输入输出流读取客户端发送的数据或者向客户端发送数据

5.关闭响应的资源

客户端程序

Socket socket = new Socket("localhost", 9000);// 参数一就是扶服务器的地址
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("hello miaomiao");
// 客户端接收服务器的响应信息
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println("客户端接收到服务器的响应信息:" + str);
socket.close();
is.close();
os.close();
br.close();
ps.close();

具体的编码流程:

1.创建Socket对象,指明需要连接的服务器地址和对应的监听端口号

2.连接建立后,通过Socket对象获取输入输出流

3.通过输入输出流读取服务器端发送的数据或者向服务器发送数据

5.关闭相应的资源

UDP编程

UDP是用数据报协议的简称,是一种无连接的协议,每个数据报都是一个独立信息,包括完成的源地址和目的地址,它能够在网络上以任何可能的路径传送到目的地,因此是否能够到达目的地、到达目的地的时间以及内容的正确性都是不能保证的

UDP vs TCP

UDP

  • 每个数据报中都有完整的地址信息,因此无需再发送发和接收方之间建立连接

  • UDP传输数据时是由大小限制的,每个传输的数据应该再64KB之内

  • UDP是一个不可靠的协议,所以发送方发送的数据报不一定以相同的顺序到达接收方

  • UDP操作简单,一般只需要少量的监控,,常用于高可靠的分散系统的网络环境中

TCP

  • 面向虚电路连接的协议,再socket之间进行数据传输之前必须建立连接,多以再TCP中需要有连接时间

  • 理论上来说TCP传输数据没有大小限制

  • TCP是一个可靠的协议。能够保证正确的传送和接收

  • TCP为了保证数据传输的可靠传送是需要付出代价的,对数据内容的校验必然占据计算机的处理时间和网络带宽,所以TCP的传送效率不如UDP

UDP通信过程

发送数据报过程

1.使用DatagramSocket创建一个数据报套接字

2.使用DatagramPacket(byte[] data 数据,int offset 偏移量指定下标,int length长度, InetAddress 接收方地址,port 接收方端口号)创建一个要发送的数据报

3.使用DatagramSocket的send方法就可以发送数据报

//构建用于发送数据报的套接字,一般都没有参数,表示使用任意空闲接口发送数据
DatagramSocket socket=new DatagramSocket();
//需要发送的信息
String str="小胖回家吃饭!";
byte[] data=str.getBytes();  //将字符串转换为字节数组
//将需要发送的信息打包成用户数据报,其中需要包括目标地址和端口号
DatagramPacket dp=new DatagramPacket(data,data.length,InetAddress.getLocalHost(),9999);
//调用datagramSocket中的send方法发送数据报发送
socket.send(dp);
//关闭释放资源
socket.close();

接收数据报过程

1.使用DatagramSocket创建一个数据包套接字,绑定到指定端口上

2.使用DatagramPacket(byte[] 数据 ,int 长度) 创建一个字节数组用于接收数数据报

3.使用DatagramSocket的方法receive方法接收UDP数据报

//构建用于接收数据报的套接字,一般应该有参数,用于绑定再指定的端口上,这个端口号应该提前约定
DatagramSocket socket = new DatagramSocket(9999);
//创建用于接收客户端传送数据的空的用户数据报
byte[] buffer = new byte[100];
DatagramPacket dp = new DatagramPacket(buffer,buffer.length);
//调用用户数据报套接字中的方法接收客户端提交的UDP数据
socket.receive(dp);  //会自动将接收到的数据写入到字节数组中,如果没有到达会阻塞等待
String res = new String(dp.getData(),0,dp.getLength());
//关闭释放资源
socket.close();
//输出接收到的数据
System.out.println("接收到的数据"+res)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值