27-网络编程

网络编程

网络编程的常识------------------------------
目前主流的网络通讯软件有:微信、QQ、飞信、阿里旺旺、陌陌、探探、…
七层网络模型------------------------------
OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985年研究的网络互连模型
OSI七层模型和TCP/IP五层模型的划分如下

在这里插入图片描述

当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去
当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来
如买手机时要包装手机,而我们接受时要拆包装而得到手机,即可以将手机看成一种数据,来形容这七层模型
相关的协议------------------------------
计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议
这样的协议就是:当你没有与别人进行通话时,即自言自语,那么就不需要一些规范
而当你要和别人通话时,需要将你的话让别人听的懂
就如英语一样,有规则,这样就需要在网络上也要有一种规则,使得都可以通话
例如:网络中一个微机用户和一个大型主机的操作员进行通信
由于这两个数据终端所用字符集不同,因此操作员所输入的命令彼此不认识
为了能进行通信,规定每个终端都要将各自字符集中的字符先变换为标准字符集的字符后,才进入网络传送
到达目的终端之后,再变换为该终端字符集的字符。对于不相容终端,除了需变换字符集字符外还需转换其他特性
通信协议可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准
对于下面的协议了解即可,如果你不需要观看,可以直接的全局搜索"IP地址"即可,从那里开始
TCP协议------------------------------
传输控制协议(Transmission Control Protocol),是一种面向连接的协议,类似于打电话
建立连接 => 进行通信 => 断开连接在传输前采用"三次握手"方式

在这里插入图片描述

三次握手理解:客户端A,服务端B
主要是必须确认自己的接收能力和发送能力的可行
第一次:A先向B发送报文,确认自己和B的接收和发送能力是否可以(我的发送是否被B得到)
第二次:B收到A的报文,那么B就知道自己的接收能力和A的发送能力是可以的(B得到了A的发送)
并向A发送报文,确认自己的发送能力和A的接收能力是否可以(我的发送是否被A得到)
第三次:A收到B的报文
那么就知道自己的发送能力和接收能力都可以,且知道B的发送能力和接收能力都可以(我的发送已经被B得到,并回应了我)
并向B发送报文,自己的接收能力和你的发送能力都可以(给回应)
然后B收到后,就确认了自己和A的接收和发送能力都可以(确认A得到了B的发送)
那么就可以进行通信了
也可以用实例说明
例如:你首先向妹子招手,妹子看到你向自己招手后,向你点了点头挤出了一个微笑(你的发送被妹子得到)
你看到妹子微笑后确认了妹子成功辨认出了自己
但是妹子有点不好意思,向楼下和楼上看了一看,有没有可能你是在看别人呢,她也需要确认一下
妹子也向你招了招手(妹子的发送是否被你得到)
你看到妹子向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑
你知道了你和妹子的发送和接收都可以
妹子看到对方的微笑后确认了你就是在向自己打招呼(确认你得到了妹子的发送)
妹子知道了妹子和你的发送和接收都可以
即第一次和第二次握手确认了A的发送和接收都没错
第二次和第三次握手确认了B的发送和接收都没错
第二次握手可以看成两个握手,即A->B,B->A,A确认是B(确认A的接收和发送能力可以)
再B->A,A->B,B确认是A发送消息给他(确认B的接收和发送能力可以)
即这三次握手可以看成四个握手,只不过中间两次合并为一起了
客户端:在web中是以request对象存在的,发送请求给服务器端处理
服务端:顾名思义是服务的,客户端发送的请求交给服务器端处理,是以response对象存在,服务器端处理完毕后反馈给客户端
一般我们访问网站,都是客户端(浏览器、app)发出请求,然后对方服务器端(sina,sohu)响应
结果就是返回了页面路径给我们,我们再根据路径看到了网页
在通信的整个过程中全程保持连接,形成数据传输通道
保证了数据传输的可靠性和有序性
是一种全双工的字节流通信方式,可以进行大数据量的传输
传输完毕后需要释放已建立的连接,发送数据的效率比较低
UDP协议------------------------------
用户数据报协议(User Datagram Protocol),是一种非面向连接的协议,类似于写信
在通信的整个过程中不需要保持连接,其实是不需要建立连接
不保证数据传输的可靠性和有序性
是一种全双工的数据报通信方式,每个数据报的大小限制在64K内
发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快
三次握手,保证连接的可靠性
四次挥手,断开连接

在这里插入图片描述

四次挥手理解:客户端A,服务端B
第一次挥手:A向B发送消息,我要断开了
第二次挥手:B收到A的消息后,准备断开
第三次挥手:B向A发送消息,我断开了
第四次挥手:A向B发送消息,知道了
//三次握手和四次挥手的底层操作
//三次握手
第一次握手:客户端发送SYN包(seq=x)到服务器,并进入SYN_SEND)(请求连接)状态,等待服务器确认
第二次握手:服务器收到SYN包,必须确认客户的ack=x+1,同时自己也发送一个SYN包(seq=y),即SYN+ACK包
此时服务器进入SYN_RECV(半连接)状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ack=y+1,此包发送完毕,客户端和服务器进入
ESTABLISHED(连接已建立)状态,完成三次握手
SYN报文,seq初始序列号,ack确认号,ACK确认报文段
seq只要是发送报文,基本都会初始化一次,而多次发送,则会在基础上加1
若客户端发送一次,加入seq为x,那么下一次seq就为x+1(也可以用其他字母表示,但只可以表示,实际上还是加1)
而ack则是将接收的seq+1,来向对方确认收到,提示已经成功接收上一次所有数据
ACK报文段可以携带数据,不携带数据则不消耗序号,为1便是确认连接
SYN=1,ACK=1的解释
最原始的计算机通过01代表有效无效,仅仅代表这个包确认有效
SYN=1,SYN包有效
ACK=0,ACK包无效,ACK包不存在的意思罢了
即知道SYN只是包括了很多个东西,如seq等,而ACK也包括了很多东西而已,如ack
所以通常都会发现SYN和ACK都为1
//四次挥手
第一次挥手:Client将FIN置为1,序号seq=M,发送给Server
进入FIN_WAIT_1(套接字已关闭,正在关闭连接[发送FIN,没有收到ACK也没有收到FIN])状态
第二次挥手:Server收到后,将ACK置为1,ack=M+1,响应给Client
进入CLOSE_WAIT(远程套接字已经关闭:正在等待关闭这个套接字[被动关闭的一方收到FIN])状态
Client收到响应后
进入FIN_WAIT_2(套接字已关闭,正在等待远程套接字关闭[在FIN_WAIT_1状态下收到发过去FIN对应的ACK])状态
第三次挥手:Server在结束所有数据传输后,将Fin置为1,seq=N,发送给Client进入
LAST_ACK(远程套接字已关闭,正在等待本地套接字的关闭确认[被动方在CLOSE_WAIT状态下发送FIN])状态
第四次挥手:Client收到后,将ACK置为1,ack=N+1,响应给Server
进入TIME_WAIT(这个套接字已经关闭,正在等待远程套接字的关闭传送[FIN、ACK、FIN、ACK都完毕
这是主动方的最后一个状态,在过了2MSL时间后变为CLOSED状态])状态,等待2MSL后
进入CLOSED(没有使用这个套接字[netstat 无法显示closed状态])状态
Server收到后,进入CLOSED状态
//对于三次握手
客户端和服务端通信前要进行连接,"3次握手"的作用就是双方都能明确自己和对方的收、发能力是正常的
第一次握手:客户端发送网络包,服务端收到了
这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的
第二次握手:服务端发包,客户端收到了
这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的
从客户端的视角来看,我接到了服务端发送过来的响应数据包,说明服务端接收到了我在第一次握手时发送的网络包
并且成功发送了响应数据包,这就说明,服务端的接收、发送能力正常
而另一方面,我收到了服务端的响应数据包
说明我第一次发送的网络包成功到达服务端,这样,我自己的发送和接收能力也是正常的
第三次握手:客户端发包,服务端收到了
这样服务端就能得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的
第一、二次握手后,服务端并不知道客户端的接收能力以及自己的发送能力是否正常
而在第三次握手时,服务端收到了客户端对第二次握手作的回应
从服务端的角度,我在第二次握手时的响应数据发送出去了,客户端接收到了
所以,我的发送能力是正常的。而客户端的接收能力也是正常的
//经历了上面的三次握手过程,客户端和服务端都确认了自己的接收、发送能力是正常的。之后就可以正常通信了
//而从上面的过程可以看到,最少是需要三次握手过程的
//两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论
//对于四次挥手
其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力
其中FIN也是一个包也有很多东西,如seq等,为1表示有效,0表示无效,与SYN和ACK包一样
之所以有四次挥手而不是三次,主要是数据的完全读取,需要等待数据的操作完毕,如第二次挥手就是等待数据的操作完毕
注意:第三次握手,是可以存放某些(一般并不包括发送,如果有这样的说明,那么应该是连接成功后的,如果以后有这样的包括说明,那么就是错误的,忽略即可)信息的,即当对应确认你的接收能力可以时,将你的第三次的报文中的存放信息进行操作
否则也只有你也只能去发送信息,然后给出信息给你,当第三次没有成功
你发送信息,那么对方(服务端)就会发送失败信息,并让你操作重新连接
三次握手后,服务端一般不会在考虑响应给客户端数据了,即回到客户端请求操作,服务端响应的时间了,即客户端的主场
上面的大致说明即可,实际上对应的包只有在握手时是特殊的,否则一般需要对应(最好自己看《计算机网络》这本书)
IP地址------------------------------
192.168.1.1 - 是绝大多数路由器的登录地址,主要配置用户名和密码以及Mac过滤
IP地址是互联网中的唯一地址标识,本质上是由32位二进制组成的整数,叫做IPv4
当然也有128位二进制组成的整数,叫做IPv6,目前主流的还是IPv4
日常生活中采用点分十进制表示法来进行IP地址的描述,将每个字节的二进制转化为一个十进制整数
不同的整数之间采用小数点隔开
如:0x01020304 => 1.2.3.4
查看IP地址的方式------------------------------
Windows系统:在dos窗口中使用ipconfig或ipconfig/all命令即可
Unix/linux系统:在终端窗口中使用ifconfig或/sbin/ifconfig命令即可
特殊的地址本地回环地址(hostAddress):127.0.0.1
主机名(hostName):localhost
端口号------------------------------
IP地址 - 可以定位到具体某一台设备
端口号 - 可以定位到该设备中具体某一个进程
端口号本质上是16位二进制组成的整数,表示范围是:0 ~ 65535,其中0 ~ 1024之间的端口号通常被系统占用
建议编程从1025开始使用
特殊的端口------------------------------
HTTP:80
FTP:21
Oracle:1521
MySQL:3306
Tomcat:8080
网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket
基于tcp协议的编程模型------------------------------
C/S架构的简介:
在C/S模式下客户向服务器发出服务请求,服务器接收请求后提供服务
例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户
这就是一种C/S工作方式
如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器
这种系统分工和协同工作的方式就是C/S的工作方式
客户端部分:为每个用户所专有的,负责执行前台功能
服务器部分:由多个用户共享的信息与功能,招待后台服务
编程模型------------------------------

在这里插入图片描述

服务器:
创建ServerSocket类型的对象并提供端口号
等待客户端的连接请求,调用accept()方法
使用输入输出流进行通信
关闭Socket
客户端:
创建Socket类型的对象并提供服务器的IP地址和端口号
使用输入输出流进行通信
关闭Socket
ServerSocket类------------------------------
public class ServerSocket
extends Object
implements Closeable
java.net.ServerSocket类主要用于描述服务器套接字信息(大插排)
常用的方法------------------------------
ServerSocket(int port),根据参数指定的端口号来构造对象
Socket accept(),侦听并接收到此套接字的连接请求
void close(),用于关闭套接字
Socket类------------------------------
public class Socket
extends Object
implements Closeable
//Closeable 是可以关闭的数据源或目标,调用 close 方法可释放对象保存的资源(如打开文件)
java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点(小插排)
常用的方法------------------------------
Socket(String host, int port),根据指定主机名和端口来构造对象
InputStream getInputStream(),用于获取当前套接字的输入流
OutputStream getOutputStream(),用于获取当前套接字的输出流
void close(),用于关闭套接字
注意事项------------------------------
客户端 Socket 与服务器端 Socket 对应, 都包含输入和输出流
客户端的socket.getInputStream() 连接于服务器socket.getOutputStream()
客户端的socket.getOutputStream()连接于服务器socket.getInputStream()
package com.lagou.task19;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            //ServerSocket(int port),根据参数指定的端口号来构造对象
            ss = new ServerSocket(8888);

            // 2.等待客户端的连接请求,调用accept方法
            while(true) {
                System.out.println("等待客户端的连接请求...");
                // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
                //Socket accept(),侦听并接收到此套接字的连接请求
                //可以接收Socket客户端的连接,若没有循环,那么就没有接收连接的东西了
                //所以当其他客户端来连接时,需要循环,即多个accept方法来接收
                s = ss.accept();  //注意:他只是判断阻塞有没有,如果你前面加上sleep来阻塞,并且客户端连接了,到这里他直接的通过,而不阻塞,也就是说,他并不是握手的操作只是来接收判断是否连接的或者说握手成功的,握手与他没有关系,当然,对应的客户端连接需要上面的执行,也就是new ServerSocket(8888);,即他是握手的主要作用,当他没有时,自然客户端会报错,而accept就是第三次握手中客户端发送的,这个时候就只看服务器了,客户的操作以及完成了,即accept就是最终的接收,而上面的8888就是确定客户端的前面三次的握手
                //s.getInetAddress()获取客户端的地址
                System.out.println("客户端" + s.getInetAddress() + "连接成功!");
                // 每当有一个客户端连接成功,则需要启动一个新的线程为之服务
                //之所以要这样,是因为,其中的accept的保证循环执行,若没有的话
                //由于是main一个线程,那么在accept的其他地方无限循环时,就运行不到accept方法了
                new ServerThread(s).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if (null != ss) {
                try {
                    //void close(),用于关闭套接字
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

package com.lagou.task19;



import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientStringTest {

    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;

        try {
            // 1.创建Socket类型的对象并提供服务器的主机名和端口号
            //Socket(String host, int port),根据指定主机名和端口来构造对象
            s = new Socket("127.0.0.1", 8888);
            System.out.println("连接服务器成功!");

            // 2.使用输入输出流进行通信
            sc = new Scanner(System.in);
            //InputStream getInputStream(),用于获取当前套接字的输入流
            //OutputStream getOutputStream(),用于获取当前套接字的输出流
            ps = new PrintStream(s.getOutputStream());
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));

            while(true) {
                //Thread.sleep(10000);
                // 实现客户端发送的内容由用户从键盘输入
                System.out.println("请输入要发送的数据内容:");
                String str1 = sc.next();
                // 实现客户端向服务器发送字符串内容"hello"
                //ps.println("hello");
                ps.println(str1);
                System.out.println("客户端发送数据内容成功!");
                // 当发送的数据内容为"bye"时,则聊天结束
                if ("bye".equalsIgnoreCase(str1)) {
                        System.out.println("聊天结束!");
                        break;
                }
                // 实现接收服务器发来的字符串内容并打印
                //必须读到\n或者\r才停止(IO流中获得的输入流,因为对象不同,即普通的一般不会有阻塞操作,但IO流(网络编程的io,即阻塞io)有)
                //所以会产生阻塞,主要用于Socket网络编程,使得阻塞,而等待输入
                String str2 = br.readLine();
                System.out.println("服务器回发的消息是:" + str2);
            }

        } catch (IOException /*| InterruptedException*/ e) {
            //两个||当你判断第一个为true时,后面的不判断,而一个|则都会判断
            //两个&&当你判断第一个为false时,后面的不判断,而一个&则都会判断
            //但他们在数字之间就是移位运算符,在判断之间则不是
            e.printStackTrace();
        } finally {
            // 3.关闭Socket并释放有关的资源
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != ps) {
                ps.close();
            }
            if (null != sc) {
                sc.close();
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

package com.lagou.task19;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;

public class ServerThread extends Thread {
    private Socket s;

    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            // 3.使用输入输出流进行通信
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());

            while(true) {
                // 实现对客户端发来字符串内容的接收并打印
                // 当没有数据发来时,下面的方法会形成阻塞
                String s1 = br.readLine();
                InetAddress inetAddress = s.getInetAddress();
                System.out.println("客户端" + inetAddress + "发来的字符串内容是:" + s1);
                // 当客户端发来的内容为"bye"时,则聊天结束
                if ("bye".equalsIgnoreCase(s1)) {
                    System.out.println("客户端" + inetAddress + "已下线!");
                    break;
                }
                // 实现服务器向客户端回发字符串内容"I received!"
                ps.println("I received!");
                System.out.println("服务器发送数据成功!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != ps) {
                ps.close();
            }
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

基于udp协议的编程模型------------------------------
编程模型
接收方:
创建DatagramSocket类型的对象并提供端口号
创建DatagramPacket类型的对象并提供缓冲区
通过Socket接收数据内容存放到Packet中,调用receive方法
关闭Socket
发送方:
创建DatagramSocket类型的对象,之所以没有写端口号,是因为发送时会指定端口
创建DatagramPacket类型的对象并提供接收方的通信地址
通过Socket将Packet中的数据内容发送出去,调用send方法
关闭Socket
DatagramSocket类------------------------------
public class DatagramSocket
extends Object
implements Closeable
//Closeable 是可以关闭的数据源或目标,调用 close 方法可释放对象保存的资源(如打开文件)
java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。换句话说,该类就是包裹投递服务的发送或接收点
常用的方法------------------------------
DatagramSocket(),使用无参的方式构造对象
DatagramSocket(int port),根据参数指定的端口号来构造对象
void receive(DatagramPacket p),用于接收数据报存放到参数指定的位置,没有接收则会阻塞,与accept方法类似
void send(DatagramPacket p),用于将参数指定的数据报发送出去
void close(),关闭Socket并释放相关资源
DatagramPacket类------------------------------
public final class DatagramPacket
extends Object
java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务
常用的方法------------------------------
DatagramPacket(byte[] buf, int length),根据参数指定的数组来构造对象,用于接收长度为length的数据报 
如发送过来"hs",接收一个,则只有h,从前往后,若接收三个,则大于发送的长度,即会全部接收,并不会有其他数据
即实际上是接收两个
DatagramPacket(byte[] buf, int length, InetAddress address, int port),根据参数指定数组来构造对象
将数据报发送到指定地址和端口,如"hse",发送两个,则只有hs,从前往后
上面两个都是对数组的操作,即发送的数据和接收的数组都是数组,即发送数组的数据后,将接收的数据放入数组里面
InetAddress getAddress(),用于获取发送方或接收方的通信地址
int getPort(),用于获取发送方或接收方的端口号
int getLength(),用于获取发送数据或接收数据的长度
上面所表示的地址,必须是可以找到的,如本机地址
InetAddress类------------------------------
public class InetAddress
extends Object
implements Serializable
//Serializable,序列化接口
java.net.InetAddress类主要用于描述互联网通信地址信息
常用的方法------------------------------
static InetAddress getLocalHost(),用于获取当前主机的通信地址
static InetAddress getByName(String host),根据参数指定的主机名获取通信地址
package com.lagou.task19;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveTest {

    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象并提供端口号
            //DatagramSocket(int port),根据参数指定的端口号来构造对象
            ds = new DatagramSocket(8888);

            // 2.创建DatagramPacket类型的对象并提供缓冲区
            byte[] bArr = new byte[20];
            //DatagramPacket(byte[] buf, int length),根据参数指定的数组来构造对象
            //用于接收长度为length的数据报
            DatagramPacket dp = new DatagramPacket(bArr, bArr.length);
            // 3.通过Socket接收数据内容存放到Packet里面,调用receive方法
            System.out.println("等待数据的到来...");
            //void receive(DatagramPacket p),用于接收数据报存放到参数指定的位置
            ds.receive(dp);
            System.out.println("接收到的数据内容是:" + new String(bArr, 0, dp.getLength()) + "!");

            // 实现将字符串内容"I received!"回发过去
            byte[] bArr2 = "I received!".getBytes();
            //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            //根据参数指定数组来构造对象
            //将数据报发送到指定地址和端口
            //int getPort(),用于获取发送方或接收方的端口号
            //int getLength(),用于获取发送数据或接收数据的长度
            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length, dp.getAddress(), 
            dp.getPort());
            //void send(DatagramPacket p),用于将参数指定的数据报发送出去
            ds.send(dp2);
            System.out.println("回发数据成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}

package com.lagou.task19;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class SendTest {

    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象
            ds = new DatagramSocket();
            // 2.创建DatagramPacket类型的对象并提供接收方的通信地址和端口号
            byte[] bArr = "hello".getBytes();
            //static InetAddress getLocalHost(),用于获取当前主机的通信地址
            DatagramPacket dp = new DatagramPacket(bArr, bArr.length, InetAddress.getLocalHost(),
            8888);
            // 3.通过Socket发送Packet,调用send方法
            ds.send(dp);
            System.out.println("发送数据成功!");

            // 接收回发的数据内容
            byte[] bArr2 = new byte[20];
            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length);
            ds.receive(dp2);
            System.out.println("接收到的回发消息是:" + new String(bArr2, 0, dp2.getLength()));

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}

对于有些类没有close方法,是因为该类并不占有资源,如File类,只是代表路径,并不会对该文件进行操作
即会占有某些资源操作的,让其他的操作无法操作的基本上都有close方法
对于TCP和UDP,都需要端口和地址
但TCP只要accpet方法,来得到客户端,数据使用IO流传输,连接后可以直接互相传输,但必须要连接,即accpet方法要不阻塞
就如电话一样
UDP需要互相发送数据,且是具体的数据,是用数据存储,简称为缓冲区,单方面传输,若要互相传输,则又要缓冲区
只要找的到地址,就可以发送数据,而不用像TCP一样,必须要accpet不阻塞,即必须连接(中间的操作是主要的,虽然不是我们要明白的)才可,就如写信一样
虽然TCP和UDP中,地址和端口基本都知道,但是TCP有连接失败的可能性,但通过地址和端口连接成功后可以产生大量数据
只不过需要断开连接,即释放资源,而UDP虽然数据量不大,但准确,且不需要连接发送,而是直接发送
知道对方地址和端口,如电话连接(在通话时,进行连接中的过程),写信不连接(直接发送信息)
其中TCP若没有accpet方法,那么客户端就会连接失败,一般也是发送出去(前提是对应的在运行),即会确认服务端连接情况,所以TCP是通过连接的
而UDP就算没有接收的receive方法来接收,无论是否对应的运行,也会发送出去(中间有没有被拒绝那是对应端口的事了,🤭),即不会确认对方的情况,所以UDP是不同过连接的
URL类------------------------------
public final class URL
extends Object
implements Serializable
//Serializable,序列化接口
java.net.URL(Uniform Resource Identifier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针
这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询等
通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的URL可以在网络上查找相应的资源
URL的基本结构如下:<传输协议>://<主机名>:<端口号>/<资源地址>
常用的方法------------------------------
URL(String spec),根据参数指定的字符串信息构造对象
String getProtocol(),获取协议名称
String getHost(),获取主机名称
int getPort(),获取端口号
String getPath(),获取路径信息
String getFile(),获取文件名
URLConnection openConnection(),获取URLConnection类的实例
URLConnection类------------------------------
public abstract class URLConnection
extends Object
java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类
主要实现类有支持HTTP特有功能的HttpURLConnection类
HttpURLConnection类的常用方法------------------------------
InputStream getInputStream(),获取输入流
void disconnect(),断开连接
package com.lagou.task19;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class URLTest {

    public static void main(String[] args) {

        try {
            // 1.使用参数指定的字符串来构造对象
            //URL(String spec),根据参数指定的字符串信息构造对象
            URL url = new URL("https://www.lagou.com/"); //其中https的s代表是否安全
            //即http不安全一点,而https安全一点
            // 2.获取相关信息并打印出来
            //String getProtocol(),获取协议名称
            System.out.println("获取到的协议名称是:" + url.getProtocol());
            //String getHost(),获取主机名称
            System.out.println("获取到的主机名称是:" + url.getHost());
            //int getPort(),获取端口号
            System.out.println("获取到的端口号是:" + url.getPort());
            //打印-1说明没有获取到

            // 3.建立连接并读取相关信息打印出来,java爬虫的基本代码,下面是得到对应html信息的,自然可以进行爬取信息
            //URLConnection openConnection(),获取URLConnection类的实例
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream inputStream = urlConnection.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            //正是因为缓冲流可以一次读取多个数据,所以有读取多个数据的方法
            String str = null;
            while ((str = br.readLine()) != null) {
                System.out.println(str);
            }
            br.close();
            // 断开连接
            urlConnection.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

号是:" + url.getPort());
            //打印-1说明没有获取到

            // 3.建立连接并读取相关信息打印出来
            //URLConnection openConnection(),获取URLConnection类的实例
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream inputStream = urlConnection.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            //正是因为缓冲流可以一次读取多个数据,所以有读取多个数据的方法
            String str = null;
            while ((str = br.readLine()) != null) {
                System.out.println(str);
            }
            br.close();
            // 断开连接
            urlConnection.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

现在我们来编写一个小型的tomcat:
因为实际上tomcat一般也是利用java来编写的,看如下代码:
package com.lagou;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 */
public class d {

    public static void main(String[] args) {
        try {
            ServerSocket socket = new ServerSocket(8888);
            Socket accept = socket.accept();
            System.out.println("有人连接我了");
            InputStream inputStream = accept.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String s = reader.readLine();
            System.out.println(s); //如果是浏览器访问http://localhost:8888/index.html
            //那么结果就是:GET /index.html HTTP/1.1,第一行
            //虽然是第一行,但是这样一般我们可以根据该信息,知道他是什么协议,并且可以知道其资源,那么我们可以通过该资源来得到信息
            //具体如何得到,看后面的代码,我们给出响应的信息(一般我们会根据路径来执行对应路径的响应代码,这里就不操作了):

            OutputStream outputStream = accept.getOutputStream();
            PrintWriter out = new PrintWriter(outputStream);

            //需要响应信息
            out.write("HTTP1.1 200 OK\r\n");
            out.write("Content-Type:text/html;charset=UTF-8\r\n");
            out.write("\r\n");
            out.write("<html>");
            out.write("<body>");
            out.write("1");
            out.write("</body>");
            out.write("</html>");
            //注意:对应的标签需要加上,因为这里是底层的,一般情况下,单纯的文件浏览器或者服务端会自动加上的(来满足协议,浏览器手动打开文件一般会补全,但是在真的协议上需要加上的),可是这里中间可没有哦,所以要加上对应的标签,否则可能没有对应的值(当然,这里只考虑数据的出现,其他的信息并没有给出,所以在浏览器中可能存在某些错误)
            //我们启动,然后使用浏览器访问,可以发现,返回了1
            //那么可以知道,实际上我们访问他时,如果是放入html,那么我们会获取该文件信息,并输出里面的所有内容,如果就是某些其他的文件(如txt文件),那么一般tomcat会有特别的显示的,但一般都会默认加上标签的
            //如果是类,那么自然会使用类里面的输出的内容,如jsp(转换),最终使得加上输出的内容,如果是路径,那么通过路径找上面说的文件了,然后输出,总而言之,这里是最重要的地方,也就是如何将他们的内容进行输出到浏览器
            //至此,一个小的tomcat就完成了
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

至此一个小型的tomcat编写完成,一般来说,tomcat不能访问目录,因为会当成路径,可能有特殊的情况(具体可以百度,但一般没有),当然,按照这样的说明,tomcat提供的servlet,自然最终也是操作上面的操作的,即他是网络到http的一个基础,java要与网页交互,底层基本都是这样的操作,只是大多数软件或者框架都是对他进行了很多处理而已
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值