计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
IP地址:是网络中设备的唯一标识
IP地址分为两大类
IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
DOS常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
特殊IP地址:
127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
InetAddress:此类表示Internet协议(IP)地址
相关方法
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
代码演示
public class InetAddressDemo {
? ?public static void main(String[] args) throws UnknownHostException {
//InetAddress address = InetAddress.getByName("itheima");
? ? ? ?InetAddress address = InetAddress.getByName("192.168.1.66");
?
? ? ? ?//public String getHostName():获取此IP地址的主机名
? ? ? ?String name = address.getHostName();
? ? ? ?//public String getHostAddress():返回文本显示中的IP地址字符串
? ? ? ?String ip = address.getHostAddress();
?
? ? ? ?System.out.println("主机名:" + name);
? ? ? ?System.out.println("IP地址:" + ip);
? }
}
端口
设备上应用程序的唯一标识
端口号
用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
协议
计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议
用户数据报协议(User Datagram Protocol)
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议
传输控制协议 (Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
Java中的UDP通信
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为基于UDP协议的Socket
构造方法
方法名 | 说明 |
---|---|
DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
相关方法
方法名 | 说明 |
---|---|
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭数据报套接字 |
void receive(DatagramPacket p) | 从此套接字接受数据报包 |
发送数据的步骤
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包
调用DatagramSocket对象的方法发送数据
关闭发送端
代码演示
public class SendDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?//创建发送端的Socket对象(DatagramSocket)
? ? ? ?// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
? ? ? ?DatagramSocket ds = new DatagramSocket();
?
? ? ? ?//创建数据,并把数据打包
? ? ? ?//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
? ? ? ?//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
? ? ? ?byte[] bys = "hello,udp,我来了".getBytes();
?
? ? ? ?DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);
?
? ? ? ?//调用DatagramSocket对象的方法发送数据
? ? ? ?//void send(DatagramPacket p) 从此套接字发送数据报包
? ? ? ?ds.send(dp);
?
? ? ? ?//关闭发送端
? ? ? ?//void close() 关闭此数据报套接字
? ? ? ?ds.close();
? }
}
接收数据的步骤
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
构造方法
方法名 | 说明 |
---|---|
DatagramPacket(byte[] buf, int len) | 创建一个DatagramPacket用于接收长度为len的数据包 |
相关方法
方法名 | 说明 |
---|---|
byte[] getData() | 返回数据缓冲区 |
int getLength() | 返回要发送的数据的长度或接收的数据的长度 |
示例代码
public class ReceiveDemo {
? ?public static void main(String[] args) throws IOException {
? ? //创建接收端的Socket对象(DatagramSocket)
? ? DatagramSocket ds = new DatagramSocket(12345);
?
? ? //创建一个数据包,用于接收数据
? ? byte[] bys = new byte[1024];
? ? DatagramPacket dp = new DatagramPacket(bys, bys.length);
?
? ? //调用DatagramSocket对象的方法接收数据
? ? ds.receive(dp);
?
? ? //解析数据包,并把数据在控制台显示
? ? System.out.println("数据是:" + new String(dp.getData(), 0, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dp.getLength()));
? ? ? }
? }
}
案例需求
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
代码实现
/*
? ?UDP发送数据:
? ? ? ?数据来自于键盘录入,直到输入的数据是886,发送数据结束
*/
public class SendDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?//创建发送端的Socket对象(DatagramSocket)
? ? ? ?DatagramSocket ds = new DatagramSocket();
? ? ? ?//键盘录入数据
? ? ? ?Scanner sc = new Scanner(System.in);
? ? ? ?while (true) {
? ? ? ? String s = sc.nextLine();
? ? ? ? ? ?//输入的数据是886,发送数据结束
? ? ? ? ? ?if ("886".equals(s)) {
? ? ? ? ? ? ? ?break;
? ? ? ? ? }
? ? ? ? ? ?//创建数据,并把数据打包
? ? ? ? ? ?byte[] bys = s.getBytes();
? ? ? ? ? ?DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.66"), 12345);
?
? ? ? ? ? ?//调用DatagramSocket对象的方法发送数据
? ? ? ? ? ?ds.send(dp);
? ? ? }
? ? ? ?//关闭发送端
? ? ? ?ds.close();
? }
}
?
/*
? ?UDP接收数据:
? ? ? ?因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
public class ReceiveDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?//创建接收端的Socket对象(DatagramSocket)
? ? ? ?DatagramSocket ds = new DatagramSocket(12345);
? ? ? ?while (true) {
? ? ? ? ? ?//创建一个数据包,用于接收数据
? ? ? ? ? ?byte[] bys = new byte[1024];
? ? ? ? ? ?DatagramPacket dp = new DatagramPacket(bys, bys.length);
? ? ? ? ? ?//调用DatagramSocket对象的方法接收数据
? ? ? ? ? ?ds.receive(dp);
? ? ? ? ? ?//解析数据包,并把数据在控制台显示
? ? ? ? ? ?System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
? ? ? }
? ? ? ?//关闭接收端
// ? ? ? ds.close();
? }
}
单播
单播用于两个主机之间的端对端通信
组播
组播用于对一组特定的主机进行通信
广播
广播用于一个主机对整个局域网上所有主机上的数据通信
实现步骤
发送端
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包(DatagramPacket)
调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
释放资源
接收端
创建接收端Socket对象(MulticastSocket)
创建一个箱子,用于接收数据
把当前计算机绑定一个组播地址
将数据接收到箱子中
解析数据包,并打印数据
释放资源
代码实现
// 发送端
public class ClinetDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?// 1. 创建发送端的Socket对象(DatagramSocket)
? ? ? ?DatagramSocket ds = new DatagramSocket();
? ? ? ?String s = "hello 组播";
? ? ? ?byte[] bytes = s.getBytes();
? ? ? ?InetAddress address = InetAddress.getByName("224.0.1.0");
? ? ? ?int port = 10000;
? ? ? ?// 2. 创建数据,并把数据打包(DatagramPacket)
? ? ? ?DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
? ? ? ?// 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
? ? ? ?ds.send(dp);
? ? ? ?// 4. 释放资源
? ? ? ?ds.close();
? }
}
// 接收端
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?// 1. 创建接收端Socket对象(MulticastSocket)
? ? ? ?MulticastSocket ms = new MulticastSocket(10000);
? ? ? ?// 2. 创建一个箱子,用于接收数据
? ? ? ?DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
? ? ? ?// 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
? ? ? ?ms.joinGroup(InetAddress.getByName("224.0.1.0"));
? ? ? ?// 4. 将数据接收到箱子中
? ? ? ?ms.receive(dp);
? ? ? ?// 5. 解析数据包,并打印数据
? ? ? ?byte[] data = dp.getData();
? ? ? ?int length = dp.getLength();
? ? ? ?System.out.println(new String(data,0,length));
? ? ? ?// 6. 释放资源
? ? ? ?ms.close();
? }
}
实现步骤
发送端
创建发送端Socket对象(DatagramSocket)
创建存储数据的箱子,将广播地址封装进去
发送数据
释放资源
接收端
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
代码实现
// 发送端
public class ClientDemo {
? ?public static void main(String[] args) throws IOException {
? ? // 1. 创建发送端Socket对象(DatagramSocket)
? ? ? ?DatagramSocket ds = new DatagramSocket();
// 2. 创建存储数据的箱子,将广播地址封装进去
? ? ? ?String s = "广播 hello";
? ? ? ?byte[] bytes = s.getBytes();
? ? ? ?InetAddress address = InetAddress.getByName("255.255.255.255");
? ? ? ?int port = 10000;
? ? ? ?DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 发送数据
? ? ? ?ds.send(dp);
// 4. 释放资源
? ? ? ?ds.close();
? }
}
// 接收端
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?// 1. 创建接收端的Socket对象(DatagramSocket)
? ? ? ?DatagramSocket ds = new DatagramSocket(10000);
? ? ? ?// 2. 创建一个数据包,用于接收数据
? ? ? ?DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
? ? ? ?// 3. 调用DatagramSocket对象的方法接收数据
? ? ? ?ds.receive(dp);
? ? ? ?// 4. 解析数据包,并把数据在控制台显示
? ? ? ?byte[] data = dp.getData();
? ? ? ?int length = dp.getLength();
? ? ? ?System.out.println(new String(data,0,length));
? ? ? ?// 5. 关闭接收端
? ? ? ?ds.close();
? }
}
Java中的TCP通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
构造方法
方法名 | 说明 |
---|---|
Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 |
Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
相关方法
方法名 | 说明 |
---|---|
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
示例代码
public class ClientDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?//创建客户端的Socket对象(Socket)
? ? ? ?//Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
? ? ? ?Socket s = new Socket("127.0.0.1",10000);
?
? ? ? ?//获取输出流,写数据
? ? ? ?//OutputStream getOutputStream() 返回此套接字的输出流
? ? ? ?OutputStream os = s.getOutputStream();
? ? ? ?os.write("hello,tcp,我来了".getBytes());
?
? ? ? ?//释放资源
? ? ? ?s.close();
? }
}
构造方法
方法名 | 说明 |
---|---|
ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |
相关方法
方法名 | 说明 |
---|---|
Socket accept() | 监听要连接到此的套接字并接受它 |
注意事项
accept方法是阻塞的,作用就是等待客户端连接
客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
针对客户端来讲,是往外写的,所以是输出流 针对服务器来讲,是往里读的,所以是输入流
read方法也是阻塞的
客户端在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,通过四次挥手协议保证连接终止
三次握手和四次挥手
三次握手
四次挥手
示例代码
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?//创建服务器端的Socket对象(ServerSocket)
? ? ? ?//ServerSocket(int port) 创建绑定到指定端口的服务器套接字
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?//Socket accept() 侦听要连接到此套接字并接受它
? ? ? ?Socket s = ss.accept();
?
? ? ? ?//获取输入流,读数据,并把数据显示在控制台
? ? ? ?InputStream is = s.getInputStream();
? ? ? ?byte[] bys = new byte[1024];
? ? ? ?int len = is.read(bys);
? ? ? ?String data = new String(bys,0,len);
? ? ? ?System.out.println("数据是:" + data);
?
? ? ? ?//释放资源
? ? ? ?s.close();
? ? ? ?ss.close();
? }
}
案例需求
客户端:发送数据,接受服务器反馈
服务器:收到消息后给出反馈
案例分析
客户端创建对象,使用输出流输出数据
服务端创建对象,使用输入流接受数据
服务端使用输出流给出反馈数据
客户端使用输入流接受反馈数据
代码实现
// 客户端
public class ClientDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?Socket socket = new Socket("127.0.0.1",10000);
?
? ? ? ?OutputStream os = socket.getOutputStream();
? ? ? ?os.write("hello".getBytes());
? ? ? // os.close();如果在这里关流,会导致整个socket都无法使用
? ? ? ?socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
? ? ? ?
? ? ? ?BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
? ? ? ?String line;
? ? ? ?while((line = br.readLine())!=null){
? ? ? ? ? ?System.out.println(line);
? ? ? }
? ? ? ?br.close();
? ? ? ?os.close();
? ? ? ?socket.close();
? }
}
// 服务器
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?Socket accept = ss.accept();
?
? ? ? ?InputStream is = accept.getInputStream();
? ? ? ?int b;
? ? ? ?while((b = is.read())!=-1){
? ? ? ? ? ?System.out.println((char) b);
? ? ? }
?
? ? ? ?System.out.println("看看我执行了吗?");
?
? ? ? ?BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
? ? ? ?bw.write("你谁啊?");
? ? ? ?bw.newLine();
? ? ? ?bw.flush();
?
? ? ? ?bw.close();
? ? ? ?is.close();
? ? ? ?accept.close();
? ? ? ?ss.close();
? }
}
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
案例分析
创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
客户端接受服务端的回馈信息
相关方法
方法名 | 说明 |
---|---|
void shutdownInput() | 将此套接字的输入流放置在“流的末尾” |
void shutdownOutput() | 禁止用此套接字的输出流 |
代码实现
// 客户端
public class ClientDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?Socket socket = new Socket("127.0.0.1",10000);
?
? ? ? ?//是本地的流,用来读取本地文件的.
? ? ? ?BufferedInputStream bis = new BufferedInputStream(new FileInputStream("socketmodule\\ClientDir\\1.jpg"));
?
? ? ? ?//写到服务器 --- 网络中的流
? ? ? ?OutputStream os = socket.getOutputStream();
? ? ? ?BufferedOutputStream bos = new BufferedOutputStream(os);
?
? ? ? ?int b;
? ? ? ?while((b = bis.read())!=-1){
? ? ? ? ? ?bos.write(b);//通过网络写到服务器中
? ? ? }
? ? ? ?bos.flush();
? ? ? ?//给服务器一个结束标记,告诉服务器文件已经传输完毕
? ? ? ?socket.shutdownOutput();
?
? ? ? ?BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
? ? ? ?String line;
? ? ? ?while((line = br.readLine()) !=null){
? ? ? ? ? ?System.out.println(line);
? ? ? }
? ? ? ?bis.close();
? ? ? ?socket.close();
? }
}
// 服务器
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?Socket accept = ss.accept();
?
? ? ? ?//网络中的流,从客户端读取数据的
? ? ? ?BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
? ? ? ?//本地的IO流,把数据写到本地中,实现永久化存储
? ? ? ?BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("socketmodule\\ServerDir\\copy.jpg"));
?
? ? ? ?int b;
? ? ? ?while((b = bis.read()) !=-1){
? ? ? ? ? ?bos.write(b);
? ? ? }
?
? ? ? ?BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
? ? ? ?bw.write("上传成功");
? ? ? ?bw.newLine();
? ? ? ?bw.flush();
?
? ? ? ?bos.close();
? ? ? ?accept.close();
? ? ? ?ss.close();
? }
}
优化方案一
需求
服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
解决方案
使用循环
代码实现
// 服务器代码如下,客户端代码同上个案例,此处不再给出
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?while (true) {
? ? ? ? ? ?Socket accept = ss.accept();
?
? ? ? ? ? ?//网络中的流,从客户端读取数据的
? ? ? ? ? ?BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
? ? ? ? ? ?//本地的IO流,把数据写到本地中,实现永久化存储
? ? ? ? ? ?BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\copy.jpg"));
?
? ? ? ? ? ?int b;
? ? ? ? ? ?while((b = bis.read()) !=-1){
? ? ? ? ? ? ? ?bos.write(b);
? ? ? ? ? }
?
? ? ? ? ? ?BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
? ? ? ? ? ?bw.write("上传成功");
? ? ? ? ? ?bw.newLine();
? ? ? ? ? ?bw.flush();
?
? ? ? ? ? ?bos.close();
? ? ? ? ? ?accept.close();
? ? ? }
? ? ? ?//ss.close();
? ? ? ?
? }
}
优化方案二
需求
第二次上传文件的时候,会把第一次的文件给覆盖。
解决方案
UUID. randomUUID()方法生成随机的文件名
代码实现
// 服务器代码如下,客户端代码同上个案例,此处不再给出
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?while (true) {
? ? ? ? ? ?Socket accept = ss.accept();
?
? ? ? ? ? ?//网络中的流,从客户端读取数据的
? ? ? ? ? ?BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
? ? ? ? ? ?//本地的IO流,把数据写到本地中,实现永久化存储
? ? ? ? ? ?BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));
?
? ? ? ? ? ?int b;
? ? ? ? ? ?while((b = bis.read()) !=-1){
? ? ? ? ? ? ? ?bos.write(b);
? ? ? ? ? }
?
? ? ? ? ? ?BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
? ? ? ? ? ?bw.write("上传成功");
? ? ? ? ? ?bw.newLine();
? ? ? ? ? ?bw.flush();
?
? ? ? ? ? ?bos.close();
? ? ? ? ? ?accept.close();
? ? ? }
? ? ? ?//ss.close();
?
? }
}
优化方案三
需求
使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
解决方案
开启多线程处理
代码实现
// 线程任务类
public class ThreadSocket implements Runnable {
? ?private Socket acceptSocket;
?
? ?public ThreadSocket(Socket accept) {
? ? ? ?this.acceptSocket = accept;
? }
?
? ?@Override
? ?public void run() {
? ? ? ?BufferedOutputStream bos = null;
? ? ? ?try {
? ? ? ? ? ?//网络中的流,从客户端读取数据的
? ? ? ? ? ?BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
? ? ? ? ? ?//本地的IO流,把数据写到本地中,实现永久化存储
? ? ? ? ? ?bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));
?
? ? ? ? ? ?int b;
? ? ? ? ? ?while((b = bis.read()) !=-1){
? ? ? ? ? ? ? ?bos.write(b);
? ? ? ? ? }
? ? ? ? ?
? ? ? ? ? ?BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
? ? ? ? ? ?bw.write("上传成功");
? ? ? ? ? ?bw.newLine();
? ? ? ? ? ?bw.flush();
? ? ? } catch (IOException e) {
? ? ? ? ? ?e.printStackTrace();
? ? ? } finally {
? ? ? ? ? ?if(bos != null){
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?bos.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
?
? ? ? ? ? ?if (acceptSocket != null){
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?acceptSocket.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? }
}
// 服务器代码
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
?
? ? ? ?while (true) {
? ? ? ? ? ?Socket accept = ss.accept();
? ? ? ? ? ?ThreadSocket ts = new ThreadSocket(accept);
? ? ? ? ? ?new Thread(ts).start();
? ? ? }
? ? ? ?//ss.close();
? }
}
优化方案四
需求
使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
解决方案
加入线程池
代码实现
// 服务器代码如下,线程任务类代码同上,此处不再给出
public class ServerDemo {
? ?public static void main(String[] args) throws IOException {
? ? ? ?ServerSocket ss = new ServerSocket(10000);
? ? ? ?ThreadPoolExecutor pool = new ThreadPoolExecutor(
? ? ? ? ? ? ? ?3,//核心线程数量
? ? ? ? ? ? ? ?10, ? //线程池的总数量
? ? ? ? ? ? ? ?60, ? //临时线程空闲时间
? ? ? ? ? ? ? ?TimeUnit.SECONDS, //临时线程空闲时间的单位
? ? ? ? ? ? ? ?new ArrayBlockingQueue<>(5),//阻塞队列
? ? ? ? ? ? ? ?Executors.defaultThreadFactory(),//创建线程的方式
? ? ? ? ? ? ? ?new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
? ? ? );
?
? ? ? ?while (true) {
? ? ? ? ? ?Socket accept = ss.accept();
? ? ? ? ? ?ThreadSocket ts = new ThreadSocket(accept);
? ? ? ? ? ?//new Thread(ts).start();
? ? ? ? ? ?pool.submit(ts);
? ? ? }
? ? ? ?//ss.close();
? }
}
概述
程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储。
日志与输出语句的区别
输出语句 | 日志技术 | |
---|---|---|
取消日志 | 需要修改代码,灵活性比较差 | 不需要修改代码,灵活性比较好 |
输出位置 | 只能是控制台 | 可以将日志信息写入到文件或者数据库中 |
多线程 | 和业务代码处于一个线程中 | 多线程方式记录日志,不影响业务代码的性能 |
体系结构
logback
通过使用logback,我们可以控制日志信息输送的目的地是控制台、文件等位置。
我们也可以控制每一条日志的输出格式。
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
使用步骤
导入logback的相关jar包
编写logback配置文件
在代码中获取日志的对象
按照级别设置记录日志信息
代码示例
// 测试类
public class Test01 {
?
? ?//获取日志的对象
? ?private static ?final Logger LOGGER = LoggerFactory.getLogger(Test01.class);
?
? ?public static void main(String[] args) {
? ? ? ?//1.导入jar包
? ? ? ?//2.编写配置文件
? ? ? ?//3.在代码中获取日志的对象
? ? ? ?//4.按照日志级别设置日志信息
? ? ? ?LOGGER.debug("debug级别的日志");
? ? ? ?LOGGER.info("info级别的日志");
? ? ? ?LOGGER.warn("warn级别的日志");
? ? ? ?LOGGER.error("error级别的日志");
? }
}
为了间接的表示一些固定的值,Java就给我们提供了枚举 是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内
格式
public enum s { ? 枚举项1,枚举项2,枚举项3; } 注意: 定义枚举类要用关键字enum
示例代码
// 定义一个枚举类,用来表示春,夏,秋,冬这四个固定值 public enum Season { ? ?SPRING,SUMMER,AUTUMN,WINTER; }
特点
所有枚举类都是Enum的子类
我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
每一个枚举项其实就是该枚举的一个对象
枚举也是一个类,也可以去定义成员变量
枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
枚举类可以有构造器,但必须是private的,它默认的也是private的。
枚举项的用法比较特殊:枚举("");
枚举类也可以有抽象方法,但是枚举项必须重写该方法
示例代码
public enum Season {
?
? ?SPRING("春"){
?
? ? ? ?//如果枚举类中有抽象方法
? ? ? ?//那么在枚举项中必须要全部重写
? ? ? ?@Override
? ? ? ?public void show() {
? ? ? ? ? ?System.out.println(this.name);
? ? ? }
?
? },
?
? ?SUMMER("夏"){
? ? ? ?@Override
? ? ? ?public void show() {
? ? ? ? ? ?System.out.println(this.name);
? ? ? }
? },
?
? ?AUTUMN("秋"){
? ? ? ?@Override
? ? ? ?public void show() {
? ? ? ? ? ?System.out.println(this.name);
? ? ? }
? },
?
? ?WINTER("冬"){
? ? ? ?@Override
? ? ? ?public void show() {
? ? ? ? ? ?System.out.println(this.name);
? ? ? }
? };
?
? ?public String name;
?
? ?//空参构造
? ?//private Season(){}
?
? ?//有参构造
? ?private Season(String name){
? ? ? ?this.name = name;
? }
?
? ?//抽象方法
? ?public abstract void show();
}
?
public class EnumDemo {
? ?public static void main(String[] args) {
? ? ? ?/*
? ? ? ?1.所有枚举类都是Enum的子类
? ? ? ?2.我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
? ? ? ?3.每一个枚举项其实就是该枚举的一个对象
? ? ? ?4.枚举也是一个类,也可以去定义成员变量
? ? ? ?5.枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,
? ? ? ? ?但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
? ? ? ?6.枚举类可以有构造器,但必须是private的,它默认的也是private的。
? ? ? ? ?枚举项的用法比较特殊:枚举("");
? ? ? ?7.枚举类也可以有抽象方法,但是枚举项必须重写该方法
? ?*/
?
? ? ? ?//第二个特点的演示
? ? ? ?//我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
? ? ? ?System.out.println(Season.SPRING);
? ? ? ?System.out.println(Season.SUMMER);
? ? ? ?System.out.println(Season.AUTUMN);
? ? ? ?System.out.println(Season.WINTER);
?
? ? ? ?//第三个特点的演示
? ? ? ?//每一个枚举项其实就是该枚举的一个对象
? ? ? ?Season spring = Season.SPRING;
? }
}
方法介绍
方法名 | 说明 |
---|---|
String name() | 获取枚举项的名称 |
int ordinal() | 返回枚举项在枚举类中的索引值 |
int compareTo(E o) | 比较两个枚举项,返回的是索引值的差值 |
String toString() | 返回枚举常量的名称 |
static <T> T valueOf(Class<T> type,String name) | 获取指定枚举类中的指定名称的枚举值 |
values() | 获得所有的枚举项 |
示例代码
public enum Season {
? ?SPRING,SUMMER,AUTUMN,WINTER;
}
?
public class EnumDemo {
? ?public static void main(String[] args) {
// ? ? ? String name() 获取枚举项的名称
? ? ? ?String name = Season.SPRING.name();
? ? ? ?System.out.println(name);
? ? ? ?System.out.println("-----------------------------");
?
// ? ? ? int ordinal() 返回枚举项在枚举类中的索引值
? ? ? ?int index1 = Season.SPRING.ordinal();
? ? ? ?int index2 = Season.SUMMER.ordinal();
? ? ? ?int index3 = Season.AUTUMN.ordinal();
? ? ? ?int index4 = Season.WINTER.ordinal();
? ? ? ?System.out.println(index1);
? ? ? ?System.out.println(index2);
? ? ? ?System.out.println(index3);
? ? ? ?System.out.println(index4);
? ? ? ?System.out.println("-----------------------------");
?
// ? ? ? int compareTo(E o) 比较两个枚举项,返回的是索引值的差值
? ? ? ?int result = Season.SPRING.compareTo(Season.WINTER);
? ? ? ?System.out.println(result);//-3
? ? ? ?System.out.println("-----------------------------");
?
// ? ? ? String toString() ? 返回枚举常量的名称
? ? ? ?String s = Season.SPRING.toString();
? ? ? ?System.out.println(s);
? ? ? ?System.out.println("-----------------------------");
?
// ? ? ? static <T> T valueOf(Class<T> type,String name)
// ? ? ? 获取指定枚举类中的指定名称的枚举值
? ? ? ?Season spring = Enum.valueOf(Season.class, "SPRING");
? ? ? ?System.out.println(spring);
? ? ? ?System.out.println(Season.SPRING == spring);
? ? ? ?System.out.println("-----------------------------");
?
// ? ? ? values() ? ? ? 获得所有的枚举项
? ? ? ?Season[] values = Season.values();
? ? ? ?for (Season value : values) {
? ? ? ? ? ?System.out.println(value);
? ? ? }
? }
}