一. 网络编程概述
计算机网络
- 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
什么是网络编程?
- 在网络通信协议下,不同计算机上运行的程序,进行的数据传输。
网络编程的应用场景:
- 即时通信、网游对战、金融证券、国际贸易、邮件等等...
- 不管是什么场景,都是计算机跟计算机之间通过网络进行数据传输。
- Java中可以使用java.net包下的技术轻松开发出常见的网络应用程序。
常见的软件架构![](https://i-blog.csdnimg.cn/blog_migrate/eed667f40315cfff55d8a42e5a1a2882.png)
- 客户端或者浏览器,它们两个仅仅是负责把数据展示出来,展示给用户去看,在项目当中,真正的核心业务逻辑,其实都是在后面的服务器当中。
1. B/S架构:
- B/S:Browser/Server 浏览器/服务器:通过浏览器访问服务器
- B/S架构适合移动互联网应用,可以再任何地方随时访问的系统。
- B/S架构只需要一个浏览器,用户通过不同的网址,客户访问不同的服务器。
- 所有通过浏览器去访问的,其实都是BS架构
- B/S架构不需要开发客户端。
- BS架构更新功能是不需要用户操作的,直接在服务器修改就可以了,用户唯一要做的就是刷新一下浏览器,仅此而已。所以BS架构的特点主要突出一个,方便。
- 缺点:如果应用过大,用户体验将收到影响,因为服务器需要把图片、背景音乐等资源通过网络再传输给浏览器,比如说网页游戏的画质就会非常的差劲,背景音乐也没有那么精美。
2. C/S架构:
- C/S:Client/Server 客户端/服务器:通过客户端访问服务器
- C/S架构需要开发客户端
- C/S架构在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。
- 所有需要我们下载安装包并进行安装的,都是C/S架构,比如LOL,王者荣耀等...
- 画面越精美,它的安装包越大,因为C/S架构当中的安装包里面包含的就是游戏所用到的图片、音乐等资源,那么这些资源在安装的时候就已经在本地了,服务器就不需要把图片、音乐等资源通过网络再传输给客户端了,服务器只需要客户端,现在该显示哪张图片了。
- C/S架构的软件因为已经事先下载好了所有的资源,所以可以把画面,音乐做的非常的精美,用户的体验非常的好。
- C/S架构既要开发客户端又要开发服务端,所以对于公司来讲C/S架构的开发、安装、部署、维护都会非常的麻烦,特别是服务器一更新,客户端也要跟着一起更新。
- C/S架构适合定制专业化的办公类软件,如:IDEA、网游
二. 网络编程三要素
- 所谓三要素,是指两台电脑需要传输数据,需要知道哪些东西才能传输,特别是想要给一堆电脑中的一台去发送数据。
- IP:确定对方设备(电脑等)在互联网上的地址,而这个地址是唯一的,叫做IP,IP地址是唯一的!IP地址可以唯一标识网络中的设备。要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来只指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号,也就是设备的标识号。
- 端口号:网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,端口号是应用程序在设备中唯一的标识,确定对方设备上接收数据的软件,一个端口号只能被一个软件绑定使用。端口号可以唯一标识设备中的应用程序,也就是应用程序的标识。
- 协议:确定网络传输的协议!协议就是数据在网络中传输的规则,常见的协议有UDP协议、TCP协议、http、https、ftp。通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
1. IP
IPv4
- 目前的主流方案,是给每个连接在网络上的恶主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。为了方便使用,IP地址经常被写成十进制的形式。
- 点分十进制表示法:一个字节作为一段数据,用点隔开。一个字节是8位,8位也就是一个字节的取值范围是0-255,总共256种信息。它会把8个bit分成一组,那么总共是4组,每一组再转成十进制,中间用点进行区分,每一组它的取值范围是0-255。
- 点表示用点分隔,十进制表示最终转成十进制的表示方式。
- IPv4最多只能有2^32次方个IP,目前已经用完了
IPv4差不多能表示43亿个IP,为了解决IP不够用的问题,所以才出现了IPv6,这样就解决了网络地址资源数量不够的问题
IPv6
- IPv6它可以给地球上的每一粒沙子都定一个IP
- IPv6最多有2^128次方个IP
- IPv6还正在渐渐普及。
- 冒分十六进制表示法:把上面的每一组转换成16进制,再用冒号去分隔。
IPv4的一些小细节
IPv4的地址分类形式
- 公网地址(万维网使用)和私有地址(局域网使用),即公网IP和私有IP,私有IP也叫做局域网IP。
- 192.168.开头的就是私有地址 / 局域网IP,范围为192.168.0.0--192.168.255.255,专门为组织机构内部使用,以此节省IP。现在就是用局域网IP去节省IP的使用。
- 我们知道IPv4是不够的,那么如何解决IPv4不够的问题呢?
- 利用局域网IP解决IP不够的问题。
- 例如下图,网吧里面是有很多很多电脑的,但不是每一台电脑在连接外网的时候就有一个公网的IP,它们往往是共享同一个公网IP,再由路由器给每一台电脑分配局域网IP,这样就可以实现节约IP的效果,这就是利用局域网节省IP的原理。
- 127.0.0.1永远表示本机的IP,本机IP,也就是自己电脑的IP。
- 假设192.168.1.100是我电脑的IP,那么这个IP跟127.0.0.1是不一样的!
- 如下图,假设局域网当中有6台电脑,那么这些IP都是由路由器所分配的,如果要往192.168.1.100去发送数据,此时这个数据是先发到路由器,路由器再找到你当前的IP,这样子才能实现数据的发送。
- 但是,每一个路由器给自己的电脑分配的IP是不一样的,所以会有这样一个情况,当你换了一个地方上网,局域网IP有可能不一样。
- 如下图,如果要往127.0.0.1去发送数据,那么此时它是不经过路由器的,你的数据在经过网卡的时候,网卡发现你要往127.0.0.1去发送数据,那此时,它就直接把这个数据给你自己发过来了,不管你是在哪个地方上网,永远都是这样的,这就是两者的区别。
- 建议:以后自己写练习的时候,如果是自己给自己发数据,那么就写127.0.0.1,这就可以了。
常见的CMD命令
- ipconfig:查看本机IP地址
- ping + IP / 网址:检查网络是否联通
- ping这个命令除了能检查局域网里面的网络是否畅通,它还可以检查你的电脑跟外网是否畅通。
- 网址的底层逻辑其实也是IP,如下图,我们可以看到百度服务器的IP:220.181.38.149
- 在ping的后面可以跟随IP,也可以跟随网址
InetAddress类的使用:该类表示Internet协议 / IP地址。
- InetAddress:在Java当中用来表示IP的类,InetAddress的对象就表示的IP的对象。
- 获取InetAddress的对象,表示IP的对象,IP又表示电脑,所以说这个类的对象可以看作是一台电脑的对象,它表示的就是网络中的某一台电脑。
- 但是会有一个问题,IP有两种,一个是IPv4,一个是IPv6,到底创建的是哪个对象呢?它有两个子类,一个是Inet4Address,一个是Inet6Address,所以我们在获取这个类的对象的时候,它在底层会有一个操作,它会先判断你当前系统用的是四版本的还是六版本的,然后根据你当前系统的版本返回对应的对象。
- InetAddress这个类对外没有提供构造方法,所以不能直接new,我们需要通过它的静态方法getByName去获取对象,这个方法的底层它就会做一个判断,判断你当前电脑的系统用的是IPv4还是IPv6,判断完了之后它会创建对应子类的对象给你进行一个返回。
InetAddress的一个实例由一个IP地址和可能的相应主机名组成(取决于它是用主机名构造还是已经完成了反向主机名解析)。
主机名就是你给自己电脑起的名字,如果你没起,它会有默认的。如果要修改主机名,修改完记得重启,建议:主机名不要用中文。
注意:如果说你的电脑因为网络原因或者是局域网当中就没有这台电脑,那么此时是获取不到它的主机名的,如果获取不到它的主机名那么是以IP的形式进行返回的。
package com.gch.d1_InetAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressDemo1 {
public static void main(String[] args) throws UnknownHostException {
/*
static InetAddress getByName(String host) 确定主机名称的IP地址。
主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
*/
// 1.获取InetAddress的对象,表示IP的对象
// IP又表示电脑,所以说这个类的对象可以看作是一台电脑的对象,它表示的就是网络中的某一台电脑
InetAddress address = InetAddress.getByName("192.168.1.100");
System.out.println(address); // /192.168.1.100
InetAddress address1 = InetAddress.getByName("ROG");
System.out.println(address1); // ROG/192.168.56.1
System.out.println(address == address1); // false
// 2.获取电脑的主机名
// 注意:如果说你的电脑因为网络原因或者是局域网当中就没有这台电脑,那么此时是获取不到它的主机名的
// 如果获取不到它的主机名那么是以IP的形式进行返回的
String name = address.getHostName();
System.out.println(name); // 192.168.1.100
String name1 = address1.getHostName();
System.out.println(name1); // ROG
// 3.获取电脑的IP
String ip = address1.getHostAddress();
System.out.println(ip); // 192.168.56.1
}
}
package com.gch.d1_InetAddress;
import java.net.InetAddress;
/**
目标:InetAddress类概述(了解)
一个该类的对象就代表一个IP地址对象。
InetAddress类成员方法:
static InetAddress getLocalHost()
* 获得本地主机IP地址对象。
static InetAddress getByName(String host)
* 根据IP地址字符串或主机名获得对应的IP地址对象。
String getHostName()
* 获得主机名。
String getHostAddress()
* 获得IP地址字符串。
*/
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象。
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName()); // ROG
System.out.println(ip1.getHostAddress()); // 192.168.56.1
// 2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName()); // www.baidu.com
System.out.println(ip2.getHostAddress()); // www.baidu.com
// 3.获取公网IP对象。
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName()); // 112.80.248.76
System.out.println(ip3.getHostAddress()); // 112.80.248.76
// 4.判断是否能通: ping 5s之前测试是否可通
System.out.println(ip3.isReachable(5000)); // true
}
}
端口号:应用程序在设备中唯一的标识。
- 端口号:用两个字节表示的整数,它的取值范围是0-65535,其中,0-1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
- 如果端口号被另外一个服务或者应用所占用,会导致当前程序启动失败。
- 在我们的设备中,最多只有六万多个端口。
- 一个端口号只能被一个应用程序使用,不能说是两个应用程序用同一个端口。
- 如下图,假设现在有两台电脑,一个是电脑A,一个电脑B,那么左边的电脑A要跟右边的电脑B去发送消息,在这个过程中,就有端口的存在。
- 端口,可以把它理解成是电脑往外发送数据或者是接收数据的出口或者是入口,说的直白一点,其实就是下图中的各种各样的小眼儿。
- 那么这些小眼儿是有编号的。从0-65535,而我们的软件在启动之后,它一定要跟一个端口进行绑定,如果说你不绑定,那么你当前的程序是单机的,是无法对外发送数据或者是接收数据的。
协议:数据传输的规则
- 在计算机网络当中,连接和通信的规则被称为网络通信的协议,说白了协议就是数据传输的规则。
- 那么在传输数据的时候,国际标准组织定义了一个OSI的参考模型,那么这个模型它是把传输数据分成了七层,但由于这个分法它过于理想化,也太复杂,所以未在因特网上进行广泛的推广,那么后来呢,把这个模型进行了简化,简化之后叫做TCP/IP模型,那么这个模型现在正在被广泛的进行使用。
OSI参考模型
- OSI模型把网络通信的工作分为7层。
- OSI七层模型是网络通信协议的基础,它使得不同的计算机和设备可以进行数据交换和通信。
- 在最初的OSI参考模型当中,它是把整个数据的传输分成了七层,那么在发送数据的时候,对方的电脑其实也有这七层,我们的代码时运行在最上面的应用层的
- 如果说我想要发送一条数据过去,在左边自己的电脑当中,它会一层一层的往下,在最下面的物理层会把数据最终转换成二进制,再去传给对方的电脑,那么对方的电脑接收到二进制之后会进行解析,再一层一层的传递给最上面的应用层,那么这样呢,我们的程序就可以接收到数据了。
- OSI模型是一个理论模型,它提供了一种标准化的方法来描述计算机网络的功能和协议,但在实际应用中,TCP/IP模型才是一个实际应用的模型,它是互联网的基础。
OSI分层的好处?
OSI分层的好处可以从五个方面讲:
- 人们可以很容易的讨论和学习协议的规范细节;
- 层间的标准接口方便了工程模块化;
- 创建了一个更好的互连环境;
- 降低网络设计和实现复杂度,使程序更容易修改,产品开发的速度更快;
- 每层利用紧邻的下层服务,更容易记住每个层的功能;
- 由于每个层级都有特定的功能,故障可有更容易地被隔离到特定地层级,因此简化了故障排除操作,并提高了网络地可靠性、可扩展性和可维护性。
TCP/IP参考模型
- TCP/IP四层网络模型是对OSI七层网络模型的一个简化。
- TCP/IP模型本身是一个四层的网络协议模型。
- TCP/IP协议时一种网络通信协议,TCP/IP协议是由两个协议组成的,分别是传输控制协议(TCP)和互联网协议(IP)。
- TCP协议负责数据的可靠传输,保证数据的完整性和顺序性;IP协议负责数据的路由和转发,将数据包从源地址传输到目的地址。
- TCP/IP协议是一种分层协议,分为应用层、传输层、网络层和物理链路层(网络接口层)。
- 物理链路层包括以太网、无线网络等。
- TCP/IP协议是互联网通信的基础,它使得不同的计算机和设备可以进行数据交换和通信
- 在TCP/IP模型中,它是把应用层,表示层,会话层进行了合并,三者合一变成应用层,而下面的传输层和网络层没有发生变化,最后的两层数据链路层和物理层进行了合并,变成了物理链路层,简化之后流程变得简单了,也大大减少了资源的消耗,所以说这个模型一直沿用至今。
- 那么这里的每一层其实都有自己的协议,像应用层的HTTP、FTP、DNS协议这些协议的底层其实就是TCP协议和UDP协议,而TCP协议和UDP协议的底层又用到了下面的IP、ICMP等等,但是最终它们还是会转成0101这样的二进制形式。
UDP协议(用户数据报协议)
- UDP是无连接的协议,它不保证数据的可靠性和顺序,它不需要建立连接,直接进行数据传输,数据传输可能出现丢失、重复、乱序等问题,但是传输速度更快~!
- UDP协议通过数据报进行数据传输,每个数据报都是独立的,可以单独处理。
- UDP适用于需要快速传输的应用程序,如视频和音频流
- UDP协议也叫做用户数据报协议
- UDP协议,它的特点是面向无连接的通信协议,它的传输速度非常的快,但是它有大小限制,一次最多只能发送64KB,数据不安全,数据容易丢失。
- 如下图,假设现在是左边的电脑要给右边的电脑去发送数据,在发送数据之前,按道理来讲,要先检查两台电脑之间的网络是否畅通,但是UDP协议,它是不会检查的,数据就直接发送的,你能收到就收到,收不到就拉倒,这就是面向无连接。
UDP协议的应用场景:
- UDP协议本身有没有优点,有啊,它的优点就是传输速度会非常的快,因为它在发送数据的时候是不需要检查网络的,所以说速度会很快,但是呢它也有缺点,就是因为不检查网络,所以说数据不安全,数据容易丢失。
- 所以说UDP协议适用于那种丢失一点数据无所谓,不会产生任何影响的情况。
- 比如说网络会议,丢一点儿数据无所谓,不产生任何的影响
- 再比如说语音通话,丢一点儿数据也不会有任何的影响,关系不大,大不了啊就是卡了一下而已
- 再比如说看在线视频的时候,丢一点儿数据也无所谓,对于我们看电影来讲没有任何的影响
- 所以说在这些情况下,我们一般会用UDP协议进行传输。
TCP协议(传输控制协议)
- TCP协议是面向连接的协议,它提供可靠的数据传输,确保数据的完整性和顺序~!
- TCP协议通过三次握手建立连接,通过四次挥手关闭连接。
- TCP协议可以进行流量控制和拥塞控制,从而保证网络的稳定性和可靠性。
- TCP适用于需要可靠传输的应用程序,如文件传输和电子邮件
- TCP协议,它是面向连接的通信协议,它的速度会比较慢,但是没有大小限制,数据相对来讲会比较安全。
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据
- 面向连接:它在发送数据的时候,会先检查两台电脑之间的网络是否畅通,简答来说就是确保连接成功才会发送数据,那么这个就是面向连接。
- 在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端发送一个带有SYN(synchronize)标志的数据包给服务端,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务端接收成功后,服务器端向客户端回传一个响应,回传一个带有SYN/ACK标志的数据包传递确认消息,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认请求信息,再回传一个带有ACK标志的数据包,确认连接
- 其中:SYN标志位数设置为1,表示建立TCP连接;ACK标志表示验证字段。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
TCP协议的应用场景:
- TCP协议适用于那种对数据有非常高的要求,一点儿都不能丢的情况
- 比如说下载软件,你丢一点儿数据,安装包有可能就用不了了
- 再比如说文字聊天,你发消息,少一个字,意思有可能就变了
- 再比如说发送邮件,同样的,数据也不能丢失
- 那么这些情况就适合用TCP协议进行传输
三. UDP通信程序
3.1 UDP协议发送数据
在创建发送端的DatagramSocket对象的时候,我们可以利用空参构造什么都不传,利用随机的端口进行发送,也可以给它指定一个端口。
- 在创建DatagramSocket对象的时候,它还会去绑定对应的端口,在以后我们就是通过这个端口往外发送数据。
- 空参:如果说你是用空参的,那么此时它会在所有可用的端口中,随机一个进行使用。
- 有参:指定端口号进行帮绑定
- 用DatagramPacket打包数据的时候,数据是利用字节数组往外发,那么在发的时候你可以指定是从哪个索引开始,一共要发几个,起始索引offset,要全部发送,length就写数组的长度即可,IneAddress表示我要给哪台电脑发,所以说可以在InetAddress这个地方去指定IP,port表示你要发给这台电脑的哪个端口上。
package com.gch.d2_udp_demo;
import java.io.IOException;
import java.net.*;
/**
UDP协议发送数据
*/
public class SendMessageDemo {
public static void main(String[] args) throws IOException {
// 1.创建发送端的DatagramSocket对象(快递公司)
// 在创建DatagramSocket对象的时候,我们可以利用空参构造什么都不传,利用随机的端口进行发送
// 也可以给它指定一个端口
// 细节:
// 在创建DatagramSocket对象的时候,它还会去绑定对应的端口,
// 那么在以后我们就是通过这个端口往外发送数据
// 空参:如果说你是用空参的,那么此时它会在所有可用的端口中,随机一个进行使用
// 有参:指定端口号进行绑定
DatagramSocket ds = new DatagramSocket();
// 2.打包数据
// 要发送的数据
String str = "你好厉害呀";
// 把字符串转为字节数组
byte[] bytes = str.getBytes();
// 获取InetAddress的对象,表示IP的对象
InetAddress address = InetAddress.getByName("127.0.0.1");
// 指定端口
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3.发送数据
ds.send (dp);
// 4.释放资源
ds.close();
}
}
问题:为什么控制台什么都不显示?
- 原因:因为我们是利用UDP协议去发送数据,UDP协议是无连接的,在发送数据的时候它不会检查两台电脑的网络是否畅通,它是直接发送的,你能收到就收到,收不到就拉到。
3.2 UDP协议接收数据
package com.gch.d2_udp_demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
UDP协议接收数据
*/
public class ReceiveMessageDemo {
public static void main(String[] args) throws Exception {
// 1.创建接收端的DatagramSocket对象(快递公司)
// 细节:
// 在接收的时候,一定要手动绑定端口
// 而且绑定的端口一定要跟发送的端口保持一致
DatagramSocket ds = new DatagramSocket(10086);
// 创建字节数组用来保存接收过来的信息
byte[] bytes = new byte[1024];
// 2.数据包,存储接收到的数据 接收数据只需要给它传递一个数组就可以了
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
// 3.接收数据包
// 该方法是阻塞的
// 程序执行到这一步的时候,会在这里死等
// 等发送端发送消息
ds.receive(dp);
// 4.解析数据包
byte[] data = dp.getData();
// System.out.println(data.length); // 1024
int len = dp.getLength();
// 获取发送端的IP
// System.out.println(dp.getSocketAddress()); // /127.0.0.1:50167
InetAddress address = dp.getAddress();
// 获取发送端的端口号
int port = dp.getPort();
System.out.println("接收到数据:" + new String(data,0,len));
System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的");
// 5.释放资源
ds.close();
}
}
- 在运行的时候,要先来运行接收端,再来运行发送端,这样子才是可以的!
问题1:为啥这里的端口不是10086而是58694呢?
- 因为我们在发送端是利用空参构造创建的DatagramSocket对象,它会随机一个端口进行使用
3.3 UDP练习-聊天室
发送端代码实现:
package com.gch.d3_udp_demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Objects;
import java.util.Scanner;
public class SendMessageDemo {
public static void main(String[] args) throws Exception {
/*
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
// 1.创建发送端的DatagramSocket对象
// 细节:在创建DatagramSocket对象的时候,它会去绑定对应的端口,在以后我们就是通过这个端口往外发送数据
DatagramSocket ds = new DatagramSocket();
// 2.打包数据
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要说的话:");
String str = sc.nextLine();
if(Objects.equals("886",str)){
System.out.println("离线成功!");
ds.close();
break;
}
byte[] bytes = str.getBytes();
// 要发给哪台电脑的哪个端口
// 获取InetAddress的对象,表示IP的对象
InetAddress address = InetAddress.getByName("127.0.0.1");
// 指定端口号
int port = 2002;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3.发送数据
ds.send(dp);
}
// 4.释放资源
ds.close();
}
}
接收端代码实现:
package com.gch.d3_udp_demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ReceiveMessageDemo {
public static void main(String[] args) throws Exception {
/*
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
// 1.创建接收端的DatagramSocket对象
// 细节:
// 在接收的时候,一定要手动绑定端口
// 而且绑定的端口一定要跟发送的端口保持一致
// 这个端口就表示你要从哪个端口上去接收数据
DatagramSocket ds = new DatagramSocket(2002);
// 2.数据包,存储接收到的数据
// 创建字节数组,保存接收到的信息
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
while (true) {
// 3.接收数据包
ds.receive(dp);
// 4.解析数据包
// 调用getData()可以获取发送过来的数据
byte[] data = dp.getData();
// 调用getLength()获取当前接收到了多少字节
int len = dp.getLength();
// 获取发送端的电脑对象
InetAddress address = dp.getAddress();
// 获取发送端的IP,这个才是真正的IP
String ip = dp.getAddress().getHostAddress();
// 获取发送端的主机名
String name = dp.getAddress().getHostName();
// 获取发送端的端口号
int port = dp.getPort();
System.out.println("接收到数据:" + new String(data,0,len));
System.out.println("该数据是从" + address + "这台电脑, " + "IP为:" + ip + ", 主机名为:" + name + "中的" + port + "这个端口发出的!");
}
}
}
允许这个类可以重复的运行多次:
3.4 UDP的三种通信方式:单播、组播、广播
- 单播:其实就是一对一,左边的发送端只能给右边接收端的一台电脑去发送数据。
- 组播:就是现在可以给一组电脑去发送信息,比如说左边是发送端,但是右边的接收端可以 是一组。
- 广播:左边的发送端可以给局域网当中所有的电脑发送数据,这就叫广播。
- 之前所有的IP只能表示一台电脑,而这里随便的一个组播地址,它就可以表示多台电 脑。
- 如果发送数据的IP地址是这个组播地址,那此时这一组所有的电脑都可以接收到这个数据了。
- 广播:在发送消息的时候,发送端的IP地址发到的是255.255.255.255,那么此时在局 域网里面所有的电脑都可以接收到你的这个信息,这就是广播。广播地址是UDP 独有的!
四. TCP通信程序
- 发送端:即客户端(Client),负责发送数据,用的是输出流,Socket
- 接收端:即服务端(Server),负责接收数据,用的是输入流ServerSocket
- 连接一旦建立之后,会通过IO流进行网络通信。
- 客户端和服务器在发送数据之间,一定要保证连接已经建立,如果是连接不建立的话,TCP是无法发送数据的。
- 先运行服务端,再来运行客户端!
- 在创建客户端的Socket对象的时候,需要传递服务器的IP还有端口号。
- 服务器绑定的端口必须跟客户端连接的端口保持一致,服务器端调用accept方法去等待客户端来连接!
- IO流是在连接通道里面的,所以说在释放资源的时候只需要关闭通道即可。
客户端代码:利用TCP协议,去发送数据
package com.gch.d5_tcp_demo;
import java.io.OutputStream;
import java.net.Socket;
/**
客户端:利用TCP协议,去发送数据
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建Socket对象:需要传递服务器的IP还有端口号
// 细节:在创建对象的同时会连接服务器
// 如果连接不上,运行后代码会报错
Socket socket = new Socket("127.0.0.1",10086);
// 2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
// 写出数据 字节流往外写出的时候只能写字节信息
// 中文会乱码
os.write("你好你好".getBytes());
// 3.释放资源
// os.close();
socket.close();
}
}
服务器端代码:利用TCP协议,接收数据
package com.gch.d5_tcp_demo;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
服务端:利用TCP协议,接收数据
*/
public class Server {
public static void main(String[] args) throws Exception{
// 1.创建服务端的ServerSocket对象:服务器端绑定的端口一定要跟客户端连接的端口保持一致
ServerSocket ss = new ServerSocket(10086);
// 2.监听客户端连接:去死等客户端来连
// 一旦有客户端来连,它就会返回客户端的连接对象
Socket socket = ss.accept();
// 3.从连接通道中获取输入流读取数据
/*InputStream is = socket.getInputStream();
// 字符输入转换流:把is变成了字符流
InputStreamReader isr = new InputStreamReader(is);
// 字符缓冲输入流
BufferedReader br = new BufferedReader(isr);*/
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
// int b;
// while((b = br.read()) != -1){
// System.out.print((char)b);
// }
// 4.释放资源
// 这行代码是相当于断开了跟客户端之间的连接
socket.close();
// 这行代码相当于就是关闭了服务器
ss.close();
}
}
TCP代码细节 - 三次握手、四次挥手:
- TCP协议的三次握手协议保证连接建立。
- 利用四次挥手协议保证断开连接,而且需要保证服务端已经把连接通道里面的数据已经处理完毕了。
- TCP协议的四次挥手是为了关闭可靠的连接,通过四次挥手,客户端和服务器端可以关闭可靠的连接,从而释放资源。
TCP通信程序的综合练习:
### 练习一:多发多收
需求:
客户端:多次发送数据
服务器:接收多次接收数据,并打印
代码示例:
package com.gch.d7_test1;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
客户端:利用TCP协议发送数据
*/
public class Client {
public static void main(String[] args) throws Exception {
/*
多发多收
客户端:多次发送数据
服务端:接收多次接收数据,并打印
*/
// 1.创建客户端的Socket对象并连接服务端
Socket socket = new Socket("127.0.0.1",528);
// 2.从连接管道中获取输出流,写出数据
OutputStream os = socket.getOutputStream();
// 写出数据:字节流往外写数据的时候只能写字节信息
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要发送的信息:");
String str = sc.nextLine();
if("886".equals(str)){
break;
}
os.write(str.getBytes());
}
// 3.释放资源
socket.close();
}
}
服务器端代码:
package com.gch.d7_test1;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
服务端:利用TCP协议接收数据
*/
public class Server {
public static void main(String[] args) throws Exception {
/*
多发多收
客户端:多次发送数据
服务端:接收多次接收数据,并打印
*/
// 1.创建服务器端的ServerSocket对象绑定客户端连接的端口
ServerSocket ss = new ServerSocket(528);
// 2.监听客户端连接:等待客户端连接,一旦有客户端来连,会返回客户端的连接对象
Socket socket = ss.accept();
// 3.从连接管道中获取输入流读取数据
// InputStream is = socket.getInputStream();
// 字符缓冲输入流 字符输入转换流 字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
/* int b;
while((b = br.read()) != -1){
System.out.print((char)b);
}*/
// 4.释放资源
// 这行代码相当于断开了跟客户端之间的连接
socket.close();
// 关闭了服务器
ss.close();
}
}
### 练习二:接收并反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息
package com.gch.d8_test2;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
客户端:利用TCP协议发送数据
*/
public class Client {
public static void main(String[] args) throws Exception{
/*
接收并反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息
*/
// 1.创建客户端的Socket对象并连接服务器
Socket socket = new Socket("127.0.0.1", 8080);
// 2.从连接管道中获取输出流,写出数据
OutputStream os = socket.getOutputStream();
// 写出数据:字节流往外写数据的时候只能写字节信息
String str = "见到你很高兴";
os.write(str.getBytes());
// 写出一个结束标记
socket.shutdownOutput();
// 3.接收服务端回写的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
// 4.释放资源
socket.close();
}
}
package com.gch.d8_test2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
服务端:利用TCP协议接收数据
*/
public class Server {
public static void main(String[] args) throws IOException {
/*
接收并反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息
*/
// 1.创建服务器端的ServerSocket对象绑定客户端连接的端口
ServerSocket ss = new ServerSocket(8080);
// 2.监听客户端连接:等待客户端连接,一旦有客户端来连接,会返回客户端的连接对象
Socket socket = ss.accept();
// 3.从连接管道中获取输入流读取数据
// 字符缓冲输入流 字符输入转换流 字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
// 细节:
// read方法会从连接通道中读取数据
// 但是,需要有一个结束标记,此时的循环才会停止
// 否则,程序就会一致停在read方法这里,等待读取下面的数据
while((line = br.readLine()) != null){
System.out.print(line);
}
// 4.回写数据
OutputStream os = socket.getOutputStream();
String str = "到底有多高兴呢?";
os.write(str.getBytes());
// 5.释放资源
// 表示断开与客户端之间的连接
socket.close();
// 表示关闭服务器端
ss.close();
}
}
练习三:上传练习(TCP协议)
-
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
package com.gch.d9_test3;
import java.io.*;
import java.net.Socket;
/**
客户端:利用TCP协议负责发送数据
*/
public class Client {
public static void main(String[] args) throws IOException {
/*
上传文件
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
*/
// 1.创建客户端的Socket对象并连接服务器
Socket socket = new Socket("127.0.0.1",10087);
// 2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day12-net-demo/client.dir/1.png"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
// 3.往服务器写出结束标记
socket.shutdownOutput();
// 4.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
// 5.释放资源
socket.close();
}
}
package com.gch.d9_test3;
import sun.net.www.content.image.png;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
/**
利用TCP协议,负责接收数据
*/
public class Server {
public static void main(String[] args) throws IOException {
/*
上传文件
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
*/
// 1.创建服务器端的ServerSocket对象并绑定客户端连接的端口
ServerSocket ss = new ServerSocket(10087);
// 2.监听客户端连接:等待客户端来连接
Socket socket = ss.accept();
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-","");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day12-net-demo/server-dir/server/" + name + ".png"));
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
// 4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine(); // 换行
bw.flush(); // 刷新流
// 5.释放资源
// 关闭连接通道,断开跟客户端之间的连接
socket.close();
// 关闭服务器
ss.close();
}
}
package com.gch.d9_test3;
import java.util.UUID;
/**
UUID一个表示不可变的通用唯一标识符的类
UUID可以生成一个随机的字符串,而且字符串的内容是唯一的
*/
public class UUIDTest {
public static void main(String[] args) {
// System.out.println(UUID.randomUUID());
String str = UUID.randomUUID().toString().replace("-","");
System.out.println(str);
}
}
多线程 ,线程池版的服务端:
package com.gch.d10_test4;
import java.io.*;
import java.net.Socket;
/**
客户端:利用TCP协议负责发送数据
*/
public class Client {
public static void main(String[] args) throws IOException {
/*
上传文件
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
*/
// 1.创建客户端的Socket对象并连接服务器
Socket socket = new Socket("127.0.0.1",10087);
// 2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day12-net-demo/client.dir/1.png"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
// 3.往服务器写出结束标记
socket.shutdownOutput();
// 4.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
// 5.释放资源
socket.close();
}
}
package com.gch.d10_test4;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.*;
/**
利用TCP协议,负责接收数据
*/
public class Server {
public static void main(String[] args) throws IOException {
/*
上传文件
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
*/
// 创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(
3,// 核心线程数量
6, // 最大线程数量
60, // 空闲线程的存活时间
TimeUnit.SECONDS, // 存活时间单位
new ArrayBlockingQueue<>(3), // 任务的阻塞队列
Executors.defaultThreadFactory(), // 线程的创建工厂
new ThreadPoolExecutor.AbortPolicy()); // 任务的拒绝策略
// 1.创建服务器端的ServerSocket对象并绑定客户端连接的端口
ServerSocket ss = new ServerSocket(10087);
while (true) {
// 2.监听客户端连接:等待客户端来连接
Socket socket = ss.accept();
// 开启一条线程
// 一个用户就对应服务端的一条线程
// new Thread(new MyRunnable(socket)).start();
pool.execute(new MyRunnable(socket));
}
// 关闭服务器
// ss.close();
}
}
package com.gch.d10_test4;
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class MyRunnable implements Runnable {
private Socket socket;
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-","");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day12-net-demo/server-dir/server/" + name + ".png"));
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
// 4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine(); // 换行
bw.flush(); // 刷新流
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 5.释放资源
// 关闭连接通道,断开跟客户端之间的连接
if(socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
BS架构模型:BS架构不需要不需要开发客户端,只需要开发服务器即可!
package com.gch.d7_test1;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
服务端:利用TCP协议接收数据
*/
public class Server {
public static void main(String[] args) throws Exception {
/*
多发多收
客户端:多次发送数据
服务端:接收多次接收数据,并打印
*/
// 1.创建服务器端的ServerSocket对象绑定客户端连接的端口
ServerSocket ss = new ServerSocket(528);
// 2.监听客户端连接:等待客户端连接,一旦有客户端来连,会返回客户端的连接对象
Socket socket = ss.accept();
// 3.从连接管道中获取输入流读取数据
// InputStream is = socket.getInputStream();
// 字符缓冲输入流 字符输入转换流 字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
/* int b;
while((b = br.read()) != -1){
System.out.print((char)b);
}*/
// 4.释放资源
// 这行代码相当于断开了跟客户端之间的连接
socket.close();
// 关闭了服务器
ss.close();
}
}
先运行服务器端,再打开浏览器,输入本机IP以及端口号:
总结:
- 在BS架构中客户端就是浏览器,而我们的服务端就是接收浏览器传递过来的数据。
- 当我们要回写的时候,也是把数据回写给浏览器!