JAVA网络专题1

本文深入解析网络基础,涵盖TCP/IP协议栈、模型组成、UDP与TCP的区别、服务器与客户端交互原理。同时,详细介绍了基于Socket的Java网络编程,包括客户端与服务端通信、Socket通信三次握手、阻塞IO及多线程处理,最后对比了基于URL的网络编程方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网络基础必备

1.TCP/IP协议栈概念

  • TCP/IP 是供已连接因特网的计算机进行通信的通信协议。
  • TCP/IP 指传输控制协议/网络地址协议
  • TCP/IP是一个两层的程序。高层为传输控制协议,它负责聚集信息或把文件拆分成更小的包。这些包通过网络传送到接收端的TCP层,接收端的TCP层把包还原为原始文件。低层是网际协议,它处理每个包的地址部分,使这些包正确的到达目的地。

2.TCP/IP模型组成

TCP/IP模型从高到低依次为:应用层,传输层,互联网层,网络访问层

  • 应用层:操作系统或网络应用程序提供访问网络服务的接口。
    例如:发送邮件(简单邮件传输协议SMTP)
    访问互联网上网页(超文本传输协议http协议)
    文件上传,下载(文件传输协议FTP协议)
    远程登录:无加密,不安全(远程网络访问协议TELNET协议)
    远程登录:安全加密(SSH协议)
  • 传输层:提供端到端的通信,用来把应用层的数据打包发送出去,知道程序发送的目标程序。 传输层协议包括TCP(传输控制协议),UDP(用户数据报协议) ,传输层数据的单位为报文或数据段。
  • 互联网层:提供点到点通信,协议为(IP协议),确定数据要发到哪,用于寻址,互联网层单位为分组或数据包。对应设备路由器
  • 网络访问层:接收IP数据报并通过网络发送,偏向于硬件的层,对应设备网卡,交换机

3.IP地址和端口

ip地址用于唯一标示网络中的一个通信实体,这个通信实体可以是一台主机,可以是一台打印机,或者是路由器的某一个端口。而在基于IP协议网络中传输的数据包,必须使用IP 地址来进行标示。ip地址就像写一封信,必须指定收件人的地址一样。每个被传输的数据包中都包括了一个源IP和目标IP。定义网络上设备。
一个通信实体可以有多个通信程序同时提供网络服务。这个时候就要通过端口来区分开具体的通信程序。用端口号来定义网络上设备的服务程序,一个通信实体上不能有两个通信程序使用同一个端口号。
IP地址和端口号,就像一个出差去外地入住酒店一样,IP地址表示了酒店在具体位置,而端口号则表示了这个人在酒店的房间号。

  • http占用80端口
  • FTP( 文件传输FTP服务)占用21端口
  • TELNET占用23端口
  • E-mail SMTP占用25端口
  • SQL Server占用1433端口
  • POP2占用109端口

4.UDP,TCP区别

(1)TCP传输控制协议是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。它能够提供两台计算机之间的可靠的数据流,HTTP、FTP、Telnet等应用都需要这种可靠的通信通道。
(2)UDP用户数据报协议是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传送目的地,至于能够达到目的地,达到目的地的时间以及内容的正确性都是不能保证的无法保证数据有序性和可靠性,但传输速度快。
既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?原因有两个:
1、可靠的传输是要付出代价的,对数据内容的正确性的检验必然会占用计算机处理时间和网络带宽。因此TCP的传输效率不如UDP高。
2、许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求视频音频数据绝对正确,只要能够连贯就可以了。所以在这些场景下,使用UDP更合适些。

5.服务器,客户端

首先先在控制面板打开Telnet客户端
通过Windows DOS命令客户端给服务器发送请求,获取相应响应
连接: telnet 对方ip 端口号
发送请求: http协议
请求:
GET /time HTTP/1.1 获取当前服务器上时间(GET /路径 http版本)
或者GET /time?locale=zh HTTP/1.1获取服务器上时间的中文格式
或者GET /index.html HTTP/1.1 返回html代码
Host: localhost(虚拟主句)
回车
回车

响应:
HTTP/1.1 200 (200表示响应成功,404表示请求地址不正确,520表示请求程序错误)
Content-Type: text/plain;charset=UTF-8
Content-Length: 20
Date: Sat, 24 Nov 2018 01:37:25 GMT
Nov 24 2018 09:37:25

6. 浏览器

使用Trident内核的浏览器:IE等;
使用Gecko内核的浏览器:FireFox(火狐);
使用Presto内核的浏览器:Opera7及以上版本;
使用Webkit内核的浏览器:Safari、Chrome(谷歌)
http请求我们可以直接通过浏览器打开
http://对方IP/路径(任务):端口号(端口号可省略)

7. 基于Socket的客户端java网络编程

用java Socket编程实现客户端和服务端通信
Socket是客户端和服务器端建立连接的连接点
工作在传输层,底层是TCP协议
Socket通信遵循三次握手

三次握手

在这里插入图片描述所谓三次握手指建立TCP连接过程,需要客户端和服务器端总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换TCP窗口大小,在socket编程中,客户端执行connect()时,将出发三次握手。
(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=K),即SYN+ACK包,此时服务器B进入SYN_RCVD状态。
(3)第三次握手:客户端A收到服务器端B的SYN+ACK包,向服务器B发送确认包ACK(ACK=K+1),此包发送完毕,客户端A和服务器端B进入ESTABLISHED状态,完成三次握手。开始传送数据。
客户端和服务器端发送数据用到的流
在这里插入图片描述
客户端
1.新建Socket对象
2.客户端先发,用输出流,发送数据输出流
3. // 客户端 --》 服务器
// 服务器 --》 客户端
// 客户端 --》 服务器
三次握手建议过程,第三步接收响应输入流

 /*客户端程序*/
 public static void main(String[] args) throws IOException {
        //新建SOCKET对象,参数1为要连接主机IP,参数2为端口号
        Socket socket = new Socket("192.168.3.123", 80);
        //发送数据输出流
        OutputStream out = socket.getOutputStream();
        //按照http协议请求格式发送请求
        out.write("GET /index.html HTTP/1.1\n".getBytes());
        out.write("Host: localhost\n".getBytes());
        out.write("\n".getBytes());
        out.write("\n".getBytes());
        //客户端接收响应输入流
        InputStream in = socket.getInputStream();
        //包装成字符流
        BufferedReader reader = new BufferedReader(new InputStreamReader(in, "utf-8"));
        //每次读一行
        while(true){
            String line = reader.readLine();
            if(line==null){
                break;
            }
            System.out.println(line);
        }
        socket.close();
    }   

下载图片到本地
1.新建socket对象
2.发送请求,发送数据输出流
3.按照http请求格式发送请求(GET /图片路径 HTTP/1.1)
4.接收响应,图片选择用字节流接收
5.图片写入本地,用FileoutputStream流进行写,将读到的字节数写入本地磁盘

public static void main(String[] args) throws IOException {
        Socket socket = new Socket("192.168.3.123", 80);
        OutputStream out = socket.getOutputStream();
        out.write("GET /img/chrome.png HTTP/1.1\n".getBytes());
        out.write("Host: localhost\n".getBytes());
        out.write("\n".getBytes());
        out.write("\n".getBytes());
        InputStream in = socket.getInputStream();
        FileOutputStream stream = new FileOutputStream("e:/1.png");
        byte[] bytes = new byte[1024 * 8];
        while(true){
            int len = in.read(bytes);
            if(len==-1){
                break;
            }
            stream.write(bytes,0,len);
            stream.flush();
        }
        stream.close();
        socket.close();
    }

测试这段代码发现会获取响应的头信息,实际获取的字节数大于原文件的字节数,我们需要对其进行修改。因此引入以下内容

8.基于URL的JAVA网络编程

URL:统一资源定位服务
可以处理http请求响应,把头信息进行处理
URL( http://ip地址:端口/资源地址)
完成了创建对象和发送请求
// GET /index.html HTTP/1.1
// Host: localhost操作

public static void main(String[] args) throws IOException {
        HttpURLConnection connection= (HttpURLConnection) 
                new URL("http://192.168.3.123:80/img/chrome.png").openConnection();
        InputStream in = connection.getInputStream();
        FileOutputStream out = new FileOutputStream("e:\1.png");
        byte[] bytes = new byte[1024 * 8];
        while (true){
            int len = in.read(bytes);
            if(len==-1){
                break;
            }
            out.write(bytes,0,len);
            out.flush();
        }
        in.close();
        out.close();
        connection.disconnect();//断开url的连接
    }

该方法测试得到的获取的字节数与原文件的字节数完全相等

9.基于Socket的服务端JAVA网络编程

A.阻塞IO
ServerSocket:服务器的端点
(1)创建ServerSocket
new ServerSocket(端口号)
程序与程序之间不能重复使用端口号,端口号最好4位以上
(2)调用accept方法等待客户端连接
(3)输出流接收客户端输入
(4)给客户端返回信息,拿到输出流向客户写入

public static void main(String[] args) throws IOException {
        //创建serverSocket
        ServerSocket server = new ServerSocket(6000);
        System.out.println("服务已启动,等待连接");
        //调用accept方法等待客户端连接
        Socket socket = server.accept();
        System.out.println("客户端已连接...");
        //接收客户端读入
        InputStream in = socket.getInputStream();
        //给客户返回信息,输出流向客户端写入
        OutputStream out = socket.getOutputStream();
        while (true){
            byte[] bytes = new byte[1024 * 8];
            int len = in.read(bytes);
            if(len==-1){
                break;
            }
            String s = new String(bytes, 0, len, "utf-8");
            System.out.println(s);
            //给客户返回信息,输出流向客户端写入
            out.write(("服务器回答:" + s).getBytes());
        }     
    }

可以写一个客户端和一个服务器端完成一次socket通信,先启动服务器,再启动客户端,双方必须同时是socket连接且端口号一致。
服务器端accept()执行后不会再次执行,无法接受另一个客户端,一般情况下服务器端启动后一直执行,且可以处理多个客户端连接。下面我们要对程序进行改进…
首先既然服务器端代码要一直执行,可以将代码写在死循环里,依旧无法运行第二个客户端,因为第一个客户端还没有执行完,无法回到accept()那一步,对于这种阻塞IO,我们在服务器端可以用多线程来实现。

new thread(new Runnable(){
	public void run(){
		//把IO相关操作放在线程内执行,让每个线程处理一个IO操作,避免IO阻塞
	}
}).start();

服务器端程序

public static void main(String[] args) throws IOException {
        //创建serverSocket
        ServerSocket server = new ServerSocket(5234);
        System.out.println("服务已启动,等待连接");
        //调用accept方法等待客户端连接
        while(true){
            Socket socket = server.accept();
            System.out.println("客户端已连接...");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //接收客户端读入
                    try {
                        handle(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    private static void handle(Socket socket) throws IOException {
        InputStream in = socket.getInputStream();
        //给客户返回信息,输出流向客户端写入
        OutputStream out = socket.getOutputStream();
        while (true){
            byte[] bytes = new byte[1024 * 8];
            int len = in.read(bytes);
            if(len==-1){
                break;
            }
            String s = new String(bytes, 0, len, "utf-8");
            System.out.println(s);
            //给客户返回信息,输出流向客户端写入
            out.write(("服务器回答:" + s).getBytes());

        }
    }

此时一个服务器可以连接多个客户端。
客户端程序发送的消息是给定的,显然与实际应用不符,我们可以再客户端程序中进行修改,修改成控制台输入

Scanner sc=new Scanner(System.in);
while(sc.hasNextLine()){
	sc.nextLine();
}
//while循环中, 当没有下一条语句时会出现阻塞,影响socket,可以在客户端加入多线程来解决

改进版客户端程序

 //客户端程序
 public static void main(String[] args) throws IOException {
        Socket socket = new Socket("192.168.56.1", 5234);
        OutputStream out = socket.getOutputStream();
        //out.write("好好学习java".getBytes());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Scanner sc = new Scanner(System.in);
                    while(sc.hasNextLine()){
                        String s = sc.nextLine();
                        out.write(s.getBytes());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        InputStream in = socket.getInputStream();
        while(true){
            byte[] bytes = new byte[1024 * 8];
            int len = in.read(bytes);
            if(len==-1){
                break;
            }
            String s = new String(bytes, 0, len);
            System.out.println(s);
        }
    }

一个程序只能占一个端口,因此不能启动多个服务器
B:阻塞IO特点(BIO):
一个socket在执行IO读写操作时会阻塞其他IO的读写,一个线程内IO的读写是串行的,可以用多线程的方法来解决,来一个连接创建新的线程,但这种方法占用资源大,因此建议使用线程池来对服务器端程序进行改进。线程池内容可参考线程池专题。

ServerSocket server = new ServerSocket(5234);
        System.out.println("服务已启动,等待连接");
        //调用accept方法等待客户端连接
        while(true){
            Socket socket = server.accept();
            System.out.println("客户端已连接...");
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        handle(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

发量再高怎么办
非阻塞IO NIO, nonBlocking IO, new IO
线程(多路)复用
一个线程可以同时处理多个io操作, 减少了线程的数量,能够应付更高的并发,减少线程的数量来应对更高的并发
多路复用:1.声明两个选择器
2.创建处理ACCEPT事件的线程
3.创建处理Read事件的线程
WEB服务器 tomcat 雄猫(BIO,NIO)
Netty 服务器, 封装了NIO技术,并发能力很高
spark 分布式计算框架
Redis 缓存服务器 (C) 单线程 QPS 10万

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值