Java网络编程原理及应用

网络编程概述

网络编程是指在网络通信协议下,不同计算机上运行的程序,可以进行数据的传输与交互。

网络编程三要素:

要素 说明
IP地址 网络设备的唯一标识
端口号 设备上应用程序的唯一标识(普通应用程序使用1024以上端口)
协议 对数据传输格式、速率、步骤做统一规定,双方通讯必须遵守规定才能完成数据交互,常见TCP、UDP协议

IP的地址类

Java当中,java.net包下的InetAddress表示IP地址类

相关方法:

方法 说明
static InetAddress getByName(String host) 确定主机名称的 IP 地址。主机名称可以是机器名称,也可以是 IP 地址
String getHostName() 获取此 IP 地址的主机名
String getHostAddress() 返回 IP 地址字符串

Socket简介

socket通常也称套接字,用来描述ip地址和端口(包含了源ip、地址,目标ip、地址)
socket通信在Java当中是网络编程的工具
应用程序通常通过socket套接字向网络发出请求或者应答请求。

socket是建立网络时使用的,在连接成功时,应用程序两端(服务器端,客户端)都会产生一个socket实例,操作socket实例,完成对话

底层数据结构

套接字是基于TCP / IP实现的,它是用来提供一个访问TCP的服务接口,即Socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。

socket底层实现就是实现TCP的数据结构
socket套接字包含
1,该套接字所关联的本地和远程互联网地址和端口

2,一个队列用于存放等待处理的数据,以及一个用于存放等待传输数据的队列
3,打开和关闭TCP握手相关的额外协议状态信息

socket采用的设计模式中的门面模式,把复杂的TCP / IP协议信息隐藏在Socket接口后面

TCP

TCP是Transport Control Protocol的首字母缩写,称为传输控制协议,位于计算机体系结构中的传输层。

特点

要点 说明
面向连接 TCP三次握手过程
可靠 可靠传输,使用流量控制和拥塞控制
面向字节流 数据之间是以字节流的方式处理接受和应答请求的数据

应用场景:

打电话,视频聊天等

可靠性传输

可靠指的是接收方进程从缓存区读出的字节流与发送方发出的字节流是完全一样的

TCP 实现可靠传输的机制

校验: 又称校验和:在计算时要加上12byte的伪首部,检验和总共计算3部分:TCP首部、TCP数据、TCP伪首部。计算方法为:在发送方将整个报文段分为多个16位的段,然后将所有段进行反码相加,将结果存放在检验和字段中,接收方用相同的方法进行计算,如最终结果为检验字段所有位是全1则正确,否则存在错误。

序号:TCP将每个数据包都进行了编号,这就是序列号,TCP首部的序号字段用来保证数据能有序提交给应用层

确认:TCP首部的确认号是期望收到对方的下一个报文段的数据的第一个字节的序号

重传:有两种事件会导致TCP对报文段进行重传:超时和冗余ACK

超时:TCP 每发送一个报文段,就对这个报文段设置一次计时器。计时器设置的重传时间到期但还未收到确认时,就要重传这一报文段。
冗余 ACK:超时触发重传存在的一个问题是超时周期往往太长

每当比期望序号大的失序报文段到达时,发送一个冗余ACK,指明下一个期待字节的序号。

发送方已发送12345报文段
接收方收到1,返回给1的确认(确认号为2的第一个字节)
接收方收到3,仍返回给1的确认(确认号为2的第一个字节)
接收方收到4,仍返回给1的确认(确认号为2的第一个字节)
接收方收到5,仍返回给1的确认(确认号为2的第一个字节)
发送方收到3个对于报文段1的冗余ACK → \rightarrow→认为2报文段丢失,重传2号报文段 快速重传

可靠性传输的方式:流量控制,拥塞控制

流量控制:TCP 提供流量控制服务来消除发送方(发送速率太快)使接收方缓存区溢出的可能性
TCP 提供一种基于滑动窗口协议的流量控制机制,在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,
发送方的发送窗口取接收窗口rwnd和拥塞窗口cwnd的最小值。

.
拥塞控制 :一般来说发生阻塞的原因带宽资源不足,中间路由缓存满,物理链路过载等,拥塞控制是让网络能够承受现有的网络负荷,防止过多的数据一下子涌入到网络中。和流量控制有点不同,上面的接收窗口方式主要是保护两端的主机,是端对端的保护,防止数据量太大处理不过来而瘫痪,阻塞控制能保护到主机,路由和物理链路各个部分,更加“全面”

三次握手

tcp三次握手过程就是建立连接的过程

三次握手过程如下:

第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
.
第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
.
第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。
.
连接成功,开始数据传输

下图所示,理解握手过程(客户端和服务端都要知道对方的发送和接受数据能力的一个过程)
在这里插入图片描述

问题一:二次握手行不行?明显不行!!

如果是二次握手的话:

情况1:
第一次握手
客户端说:你是男的吗?
第二次握手
服务器端说:我是男的,你是吗?

导致结果就是:客户端知道服务器端有接收能力和发送能力,但是服务器端不知道客户端是否有接收能力。导致TCP不可靠。

情况2:
在这里插入图片描述

如果第一次握手阻塞之后又畅通了,服务器也返回二次握手了,但是客户端以及认为此次连接建立失败,而不理服务器了,但是服务器不知道,一直保持着等待状态。但是如果有第三次握手的话,服务器就知道客户端已经不理他了,他就不会等待客户端了(不会给客户端预留资源)

问题二:三次以上握手行不行?一定只能是三次?

TCP三次以上的握手也是可以的,但是没必要,同时也是浪费资源,因为三次握手已经足够可以建立可靠的连接了

四次挥手

tcp四次挥手过程就是关闭连接的过程

四次挥手过程如下:

第1次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送
.
第2次挥手:服务端收到FIN后,发送一个ACK给客户端,服务端还没关闭,确保传输的数据全部接受到之后,在接下来第三步
.
第3次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送
.
第4次挥手:客户端收到FIN后,接着发送一个ACK给服务端,客户端关闭、服务端关闭
.
其中:FIN标志位数置1,表示断开TCP连接。

下图所示,理解挥手过程
在这里插入图片描述
问题一:3次挥手行不行?一定要4次吗?

第二次、三次合并成一次挥手?

不行、这个因为第一次挥手表示客户端发送了一个fin的包,表示客户端已发送数据完毕,但是服务端这个时候可能还有数据没有发送完成,先发送给客户端一个ask的包,等待自己的数据发送完成才能向客户端发送一个 fin的包,表示自己的数据也已发送完成。这样中间就必须为两次来发送ask和fin

第四次挥手取消?

不行、服务器在发送消息完毕后,不能确定客户端是否收到了最后的这些消息,TCP是可靠传输,所以为了可靠一定需要一次客户端确认一

问题二:4次以上挥手行不行?

4次挥手已经能够足够TCP的断开连接,后续多余操作没必要,浪费资源而已

TCP编程

Java 为客户端提供了 Socket 类,为服务器端提供了 ServerSocket 类。

Java中TCP通信流程:思路
客户端

创建Socket对象,指明需要连接的服务器的地址和端口号。
连接建立后,通过输出流向服务器发送请求信息。
通过输入流获取服务器响应的信息。
关闭相应资源。

服务端

创建ServerSocket对象,绑定监听端口。
通过accept()方法监听客户端请求。
连接建立后,通过输入流读取客户端发送的请求信息。
通过输出流向客户端发送响应信息。
关闭响应的资源。

代码如下;
客户端

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;

public class TCP_Client {
    
    
    public static void main(String[] args) throws IOException {
    
    
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        OutputStream os = null;
        OutputStreamWriter osw = null;
        PrintWriter pw = null;
        try{
    
    
            //建立连接
            Socket socket = new Socket("localhost", 10092);
            System.out.println("开启Socket线程");
            //获取字节输出流
            os = socket.getOutputStream();
            //将输出流包装为打印流
            pw = new PrintWriter(os);
            pw.write("Hi server! This is message from client!");
            pw.flush();
            //关闭输出流
            socket.shutdownOutput();
            //获取字节输入流
            is=socket.getInputStream();
            br=new BufferedReader(new InputStreamReader(is));
            String message =null;
            while ((message = br.readLine())!=null){
    
    
                System.out.println("我是客户端,服务器返回消息:"+message);
            }

            socket.close();
        } catch (UnknownHostException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭资源
            br.close();
            is.close();
            pw.close();
            os.close();
        }
    }
}

服务端

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCP_Server {
    
    
    public static void main(String[] args)throws IOException
    {
    
    
        //监听端口
        int port = 10092;
        //创建一个ServerSocket, 用于监听客户端Socket的连接请求
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("server启动成功,等待客户端连接...");

        //server等待连接的到来
        Socket socket = serverSocket.accept();
        //将Socket对应的输出流包装成printStream
        PrintStream ps = new PrintStream(socket.getOutputStream());
        ps.println("You have received message form server!");

        InputStream inputStream = socket.getInputStream();
        byte[] bytesFromClient = new byte[1024];
        int len = 0;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytesFromClient))!=-1){
    
    
            sb.append(new String(bytesFromClient, 0 , len));
        }
        System.out.println("get message from client: "+ sb.toString());

        //关闭输出流,关闭socket
        inputStream.close();
        ps.close();
        socket.close();
    }
}

因为TCP连接是一个长连接,而默认情况下这个连接的持续时间会长达2个小时,此时,后续中,可以调用socket中的关于检测连接时间的API来进一步优化程序

UDP

UDP是User Datagram Protocol的缩写,译为用户数据报协议。UDP协议是一种传输速度较快的网络传输层协议,但提供了更快的传输速度,也让他失去了一定的可靠性,他也是一种无连接的网络协议。

特点:

要点 说明
无连接 UDP协议是不需要建立连接,知道目的IP、端口号之后,就会直接进行数据传输,这期间不会进行请求连接、建立连接等操作
不可靠 在UDP协议中,没有确认机制或者重传机制,如果因为网络原因导致对方收不到数据,UDP的协议层并不会向应用层响应错误信息
面向数据报 应用层将数据报文交给UDP,无论这个报文多长,UDP都会原样发送,不会进行拆分或者合并
只有接收缓冲区 UDP是没有自己的发送缓冲区的,只有一个接收缓冲区,但是这个接收缓冲区也无法保证发送的数据报和接收的数据报顺序是一致的。如果接收缓冲区满了的话,再新发送到的数据报就会被丢弃

应用场景:

发短息、发语音、音频、视频等

UDP编程

`客户端:

package JUC.UDP;
 
import java.io.IOException;
import java.net.*;
 
public class DatagramSocketTest {
    
    
    public static void main(String[] args) throws IOException {
    
    
        DatagramSocket datagramSocket = new DatagramSocket();
        String msg = "你好";
        //要发送的内容
        DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length, 
                                                               InetAddress.getByName("localhost"),9999);
        datagramSocket.send(datagramPacket);//发送
        datagramSocket.close();
    }
}

服务端:

package JUC.UDP;
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class DatagramServerTest {
    
    
    public static void main(String[] args) throws IOException {
    
    
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
        datagramSocket.receive(datagramPacket);//接收
        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
        datagramSocket.close();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_45399396/article/details/128896825
今日推荐