- C/S:Client/Server 客户端/服务器:通过客户端访问服务器
- C/S架构需要开发客户端
- C/S架构在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。
- 所有需要我们下载安装包并进行安装的,都是C/S架构,比如LOL,王者荣耀等...
- 画面越精美,它的安装包越大,因为C/S架构当中的安装包里面包含的就是游戏所用到的图片、音乐等资源,那么这些资源在安装的时候就已经在本地了,服务器就不需要把图片、音乐等资源通过网络再传输给客户端了,服务器只需要客户端,现在该显示哪张图片了。
- C/S架构的软件因为已经事先下载好了所有的资源,所以可以把画面,音乐做的非常的精美,用户的体验非常的好。
- C/S架构既要开发客户端又要开发服务端,所以对于公司来讲C/S架构的开发、安装、部署、维护都会非常的麻烦,特别是服务器一更新,客户端也要跟着一起更新。
- C/S架构适合定制专业化的办公类软件,如:IDEA、网游
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,这就可以了。
- 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参考模型当中,它是把整个数据的传输分成了七层,那么在发送数据的时候,对方的电脑其实也有这七层,我们的代码时运行在最上面的应用层的
- 如果说我想要发送一条数据过去,在左边自己的电脑当中,它会一层一层的往下,在最下面的物理层会把数据最终转换成二进制,再去传给对方的电脑,那么对方的电脑接收到二进制之后会进行解析,再一层一层的传递给最上面的应用层,那么这样呢,我们的程序就可以接收到数据了。
- 在TCP/IP模型中,它是把应用层,表示层,会话层进行了合并,三者合一变成应用层,而下面的传输层和网络层没有发生变化,最后的两层数据链路层和物理层进行了合并,变成了物理链路层,简化之后流程变得简单了,也大大减少了资源的消耗,所以说这个模型一直沿用至今。
- 那么这里的每一层其实都有自己的协议,像应用层的HTTP、FTP、DNS协议这些协议的底层其实就是TCP协议和UDP协议,而TCP协议和UDP协议的底层又用到了下面的IP、ICMP等等,但是最终它们还是会转成0101这样的二进制形式。
- UDP是无连接的协议,它不保证数据的可靠性和顺序,但是传输速度更快
- UDP适用于需要快速传输的应用程序,如视频和音频流
- UDP协议也叫做用户数据报协议
- UDP协议,它的特点是面向无连接的通信协议,它的传输速度非常的快,但是它有大小限制,一次最多只能发送64KB,数据不安全,数据容易丢失。
- 如下图,假设现在是左边的电脑要给右边的电脑去发送数据,在发送数据之前,按道理来讲,要先检查两台电脑之间的网络是否畅通,但是UDP协议,它是不会检查的,数据就直接发送的,你能收到就收到,收不到就拉倒,这就是面向无连接。
UDP协议的应用场景:
- UDP协议本身有没有优点,有啊,它的优点就是传输速度会非常的快,因为它在发送数据的时候是不需要检查网络的,所以说速度会很快,但是呢它也有缺点,就是因为不检查网络,所以说数据不安全,数据容易丢失。
- 所以说UDP协议适用于那种丢失一点数据无所谓,不会产生任何影响的情况。
- 比如说网络会议,丢一点儿数据无所谓,不产生任何的影响
- 再比如说语音通话,丢一点儿数据也不会有任何的影响,关系不大,大不了啊就是卡了一下而已
- 再比如说看在线视频的时候,丢一点儿数据也无所谓,对于我们看电影来讲没有任何的影响
- 所以说在这些情况下,我们一般会用UDP协议进行传输。
- TCP协议是面向连接的协议,它提供可靠的数据传输,确保数据的完整性和顺序
- TCP适用于需要可靠传输的应用程序,如文件传输和电子邮件
- TCP协议,它是面向连接的通信协议,它的速度会比较慢,但是没有大小限制,数据相对来讲会比较安全。
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据
- 面向连接:它在发送数据的时候,会先检查两台电脑之间的网络是否畅通,简答来说就是确保连接成功才会发送数据,那么这个就是面向连接。
- 在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
TCP协议的应用场景:
- TCP协议适用于那种对数据有非常高的要求,一点儿都不能丢的情况
- 比如说下载软件,你丢一点儿数据,安装包有可能就用不了了
- 再比如说文字聊天,你发消息,少一个字,意思有可能就变了
- 再比如说发送邮件,同样的,数据也不能丢失
- 那么这些情况就适合用TCP协议进行传输
- 在创建发送端的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协议是无连接的,在发送数据的时候它不会检查两台电脑的网络是否畅通,它是直接发送的,你能收到就收到,收不到就拉到。
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呢?
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 + "这个端口发出的!");
}
}
}
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();
}
}
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协议的三次握手协议保证连接建立
- 利用四次挥手协议保证断开连接,而且需要保证服务端已经把连接通道里面的数据已经处理完毕了。
### 练习一:多发多收
需求:
客户端:多次发送数据
服务器:接收多次接收数据,并打印
代码示例:
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();
}
}
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
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);
}
}
}
}
}
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架构中客户端就是浏览器,而我们的服务端就是接收浏览器传递过来的数据。
- 当我们要回写的时候,也是把数据回写给浏览器!