上一篇:15【IO流增强】
下一篇:17【测试单元、反射、注解、Lombok插件】
目录:【JavaSE零基础系列教程目录】
计算机网络是通过传输介质(网线)、通信设施(路由器、交换机等)和网络通信协议,把分散在不同地点的计算机设备互连起来的,用来实现数据共享。
网络编程就是编写程序使互联网的多个设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。
通过网络发送数据是一项复杂的操作,通过网络将数据从一台主机发送到另外的主机,这个过程是通过计算机网络通信来完成。
网络通信的不同方面被分解为多个层,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解批次定义好的规则和约定。将网络分层,这样就可以修改甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。
世界上第一个网络体系结构在1974年由IBM公司提出,名为SNA。以后其他公司也相继提出自己的网络体系结构。为了促进计算机网络的发展,国际标准化组织ISO在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM(Open System Interconnection Reference Model)。
TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议,Internet国际互联网络的基础。
TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、网络层(IP层)、传输层(TCP层)、应用层。
IP(Internet Protocol Address):全称互联网协议地址,简称IP地址;IP地址用于标识互联网上的唯一一台机器,互联网就是通过IP地址锁定到我们的这台电脑,相当于家庭地址;
IP地址的分类
IPv4
:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。IPv6
:由于互联网的网民日益增多,IPv4的IP地址资源有限。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。ipconfig
ping 空格 IP地址
ping 163.177.151.109
tips:在每台计算机出厂时,都有一个用于标识自己电脑的地址:
127.0.0.1
,和一个本机域名:localhost
在两台计算机通信时,更准确的来说是两台计算机的某个进程(应用程序)在通信,IP地址可以唯一标识网络中的设备,那么端口号就是唯一标识计算机中的某个应用程序了;
tips:端口号的取值范围为065536。其中01023之间的端口号用于计算机内置的一些进程,我们自己的程序的端口号尽量设置在1023以上的端口,保证端口不会占用冲突;
如同人与人之间相互交流是需要遵循一定的规则(如语言)一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。
OSI参考模型和TCP/IP模型在不同的层次中有许多不同的网络协议,如图所示:
我们今天主要讨论的是传输层的协议,即考虑应用程序之间的逻辑通信。简单来说就是数据该如何发送给其他机器;
UDP(User Datagram Protocol):用户数据报协议;UDP是面向无连接的通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
UDP是面向报文传递数据的;在UDP传输过程中,分别为发送端和接受端;
发送端使用UDP发送数据时,首先将其包裹成一个UDP报文(包含数据与首部格式)通过网络将其发送给接收端;接受端接收到UDP报文后,首先去掉其首部,将数据部分交给应用程序进行解析;
需要注意的是,UDP不保证数据传递的可靠性,在传递过程中可能出现丢包等情况,另外,即使接收方不存在报文依旧被发送出去(丢包)。但正是因为UDP不需要花费额外的资源来建立可靠的连接,因此UDP传输速度快,资源消耗小;
一个完整的UDP报文包含首部和载荷(数据)两部分,首部由 4 个 16 位(2 字节)长的字段,共8个字节组成,分别说明该报文的源端口、目的端口、报文长度和校验值。
UDP 报文中每个字段的含义如下:
由于使用UDP协议消耗资源小,通信效率高;因此一般用于实时性要求比较高的场合如:音频、视频的传输等;例如视频会议都使用UDP协议,如果出现了网络丢包情况也只是造成卡帧现象,对整体影响不大
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。
TCP(Transmission Control Protocol):传输控制协议;TCP协议是面向连接的通信协议;即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端(发送端)与服务器端(接收端),由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”;
一个完整的TCP报文同样也是由首部和数据载荷组成;TCP的全部功能都体现在它首部中各字段的作用。
序号占4个字节,32个比特位,取值范围
2^32-1
,序号增加到最后一个时,下一个序列号又回到0;
确认号和序号一样,占4个字节,32个比特位,取值范围
2^32-1
,确认号增加到最后一个时,下一个确认号又回到0;
A向B发送数据:
B向A发送数据:
若确认号=n,则表明,序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据;
- 本次的序列号:上次的确认号
- 本次的确认号:上次的序列号+1
如图:
保留字段:占6个比特,保留为今后使用,但目前为0;
窗口: 占2个字节,16个比特;用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区大小确定自己的接收窗口值,告诉对方,使对方可以确定发送数据的字节数。
校验和:占2个字节,16比特;检查报文的首部和数据载荷两部分,底层依赖于具体的校验算法;
紧急指针:占2个字节,16比特;用来指明紧急数据的长度;当发送端有紧急数据时,可将紧急数据插队到发送缓存的最前面,并立刻封装到一个TCP报文段中进行发送。紧急指针会指出本报文段数据载荷部分包含了多长的紧急数据,紧急数据之后是普通数据
选项:附加一些额外的首部信息;
填充:由于选项的长度可变,因此用来填充的确认报文首部能被4整除(因为数据偏移字段,也就是首部长度字段,是以4字节为单位的);
标志位:
由于TCP是基于可靠通信的,在发送数据之前必须建立可靠的连接;TCP建立连接的过程分为三个步骤,我们称为"三次握手";
简单的过程如下图所示:
我们结合TCP报文原理来具体分析一下三次握手的详细流程:
原理图如下:
1)第一次握手:首先A使用TCP连接向B发送报文(此报文不携带载荷,只有首部),报文中的SYN记为1(如果发送失败,重新发送记为2),序号记为x
2)第二次握手:B接受到A发送的报文后,给A发送一个确认报文(该报文也仅有首部),报文中SYN和ACK都取值为1,用于标志这是一个确认报文段;同时此次响应的序号记为y,确认号为上一次序号x加1的值,表示已经成功接收到x+1之前的所有内容,下一次开始接受x+1之后的数据(包含x+1的数据);
3)第三次握手:A再次向B发送TCP确认,序号为x+1,代表发送序号x之后的数据;确认号为y+1,代表已经接收了来自B的响应的全部数据;
TCP建立连接时需要"三次握手",断开连接时则需要"四次挥手";
原理图如下:
使用TCP协议传输数据时,必须要建立可靠连接(三次握手),当连接关闭时还需要四次挥手,对性能损耗较大,如果频繁的创建和关闭TCP连接性能势必会有很大影响。但是由于TCP的可靠传输,对一些数据完整性要求较高的场合比较适用,如文件上传下载等;
java.net.InteAddress
类是用于描述IP地址和域名的一个Java类;
常用方法如下:
public static InetAddress getByName(String host)
:根据主机名获取InetAddress对象
public String getHostName()
:获取该对象对应的主机名
public String getHostAddress()
获取该对象对应的IP地址
示例代码:
package com.dfbz.demo01;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws UnknownHostException {
// 根据域名获取InetAddress对象(获取本机对象)
InetAddress t1 = InetAddress.getByName("localhost");
System.out.println(t1.getHostName()); // localhost
System.out.println(t1.getHostAddress()); // 127.0.0.1
System.out.println("------------------------");
// 根据域名获取InetAddress对象(获取百度的InetAddress对象)
InetAddress t2 = InetAddress.getByName("www.baidu.com");
System.out.println(t2.getHostName()); // www.baidu.com
System.out.println(t2.getHostAddress()); // 163.177.151.109
}
}
java.net.DatagramPacket
类用于封装一个UDP数据报文
public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
:创建一个数据包对象
buf
:要发送的内容length
:要发送的内容⻓度,单位字节address
:接收端的ip地址port
:接收端⼝号public DatagramPacket(byte buf[], int length)
:创建一个数据包对象示例代码:
package com.dfbz.demo01;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) throws UnknownHostException {
byte[] data1 = "hello~".getBytes();
DatagramPacket packet1 = new DatagramPacket(data1, data1.length);
byte[] data2 = "你好".getBytes();
InetAddress address = InetAddress.getByName("localhost");
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, 6868);
}
}
public synchronized int getLength()
:获取此UDP数据包载荷的数据长度(单位字节)public synchronized int getPort()
:获取此UDP数据包的目的端口号public synchronized byte[] getData()
:获取此UDP数据包的载荷部分(数据)示例代码:
package com.dfbz.demo01;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) throws UnknownHostException {
byte[] data = "hello".getBytes();
DatagramPacket packet = new DatagramPacket(
data,
data.length,
InetAddress.getByName("localhost"),
6868
);
System.out.println(packet.getLength()); // 5
System.out.println(packet.getPort()); // 6868
System.out.println(new String(packet.getData())); // hello
}
}
java.net.DatagramSocket
类用于描述一个UDP发送端或接收端;
public DatagramSocket(int port)
:通过端口构建一个发送端/接收端示例代码:
DatagramSocket socket = new DatagramSocket(6969);
public void send(DatagramPacket p)
:发送一个UDP数据包public synchronized void receive(DatagramPacket p)
:接收一个UDP数据包public void close()
:释放该Socket占用的资源package com.dfbz.demo01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @author lscl
* @version 1.0
* @intro: 发送端
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
String str = "你好";
// 准备一个UDP数据包
DatagramPacket packet = new DatagramPacket(
str.getBytes(),
str.getBytes().length,
InetAddress.getLocalHost(),
6868);
// 套接字
DatagramSocket socket = new DatagramSocket();
// 发送数据包
socket.send(packet);
socket.close();
}
}
package com.dfbz.demo01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author lscl
* @version 1.0
* @intro: 接收端
*/
public class Demo05 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(666);
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//接受数据字节的长度
System.out.println("接收端端:");
// 接受一个UDP数据包
socket.receive(packet);
int len = packet.getLength();
System.out.println("已经接收到:" + len + "个字节");
// 转换为字符串打印
System.out.println(new String(bytes, 0, len));
socket.close();
}
}
在TCP通信中,分为数据的发送端(客户端)和接收端(服务器),当建立连接成功后(三次握手),才可以进行数据的发送;
在Java中,提供了两个类用于实现TCP通信程序:
1)客户端:java.net.Socket
类表示;用于与服务器端建立连接,向服务器端发送数据报文等;
2)服务端:java.net.ServerSocket
类表示;用于与客户端的交互;
java.net.Sokcet
用于封装一个TCP应用程序的客户端;
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为本机地址。示例代码:
Socket client = new Socket("127.0.0.1", 6868);
public InputStream getInputStream()
: 返回此套接字的输入流。关闭生成的InputStream也将关闭相关的Socket。public OutputStream getOutputStream()
: 返回此套接字的输出流。关闭生成的OutputStream也将关闭相关的Socket。public void close()
:关闭此套接字。关闭此socket也将关闭相关的InputStream和OutputStream 。public void shutdownOutput()
: 禁用此套接字的输出流。任何先前写出的数据将被发送,随后终止输出流。ServerSocket
类:这个类实现了服务器套接字,该对象等待通过网络的请求。
public ServerSocket(int port)
:使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。构造举例,代码如下:
ServerSocket server = new ServerSocket(6666);
public Socket accept()
:监听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。package com.dfbz.demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author lscl
* @version 1.0
* @intro: 客户端程序
*/
public class Demo01_Client {
public static void main(String[] args) throws IOException {
// 创建一个客户端,指定要连接服务器的IP与端口
Socket socket = new Socket("127.0.0.1", 6969);
// 获取与此服务器的输入流(用于读取该服务器的数据)
InputStream is = socket.getInputStream();
// 获取与此服务器的输出流(用于向该服务器发送数据)
OutputStream os = socket.getOutputStream();
// 向服务器发送数据
os.write("在吗?".getBytes());
// 准备一个字节数组用于接收数据
byte[] bytes = new byte[1024];
// 读取服务器发送过来的数据(若服务器一直未发送数据,则程序阻塞在此)
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
// 向服务器写出数据
os.write("买糖".getBytes());
// 读取服务器的数据
len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
// 释放连接资源
socket.close();
}
}
package com.dfbz.demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author lscl
* @version 1.0
* @intro: 服务端程序
*/
public class Demo02_Server {
public static void main(String[] args) throws IOException {
// 创建一个服务器,并指定该TCP程序的端口(IP地址就是本机)
ServerSocket serverSocket=new ServerSocket(6969);
System.out.println("等待客户端: ");
// 接收一个客户端(若没有客户端来连接服务器,则程序阻塞在此)
Socket client = serverSocket.accept();
// 获取与客户端的输入流(用于读取客户端的数据)
InputStream is = client.getInputStream();
// 获取与客户端的输出流(用于向客户端发送数据)
OutputStream os = client.getOutputStream();
byte[] bytes=new byte[1024];
// 读取客户端发送过来的数据
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 向客户端写出数据
os.write("在哦!亲~,想买点什么?".getBytes());
// 读取客户端的数据
len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 向客户端写出数据
os.write("糖没了,不卖!".getBytes());
client.close();
serverSocket.close();
}
}
上述程序中,发送内容都是写死在代码中,我们使用Scanner来接受键盘录入的数据进行发送;
package com.dfbz.demo02;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* @author lscl
* @version 1.0
* @intro: 客户端程序
*/
public class Demo01_Client {
public static void main(String[] args) throws IOException {
// 创建一个客户端,指定要连接服务器的IP与端口
Socket socket = new Socket("127.0.0.1", 6969);
// 获取与此服务器的输入流(用于读取该服务器的数据)
InputStream is = socket.getInputStream();
// 获取与此服务器的输出流(用于向该服务器发送数据)
OutputStream os = socket.getOutputStream();
// 获取scanner扫描器
Scanner scanner = new Scanner(System.in);
// 死循环进行发送
while (true) {
System.out.println("请输入要发送给服务器的信息: ");
String sendInfo = scanner.next();
// 如果输入end代表程序结束
if ("end".equals(sendInfo)) {
System.out.println("程序结束...");
break;
}
// 向服务器发送数据
os.write(sendInfo.getBytes());
// 准备一个字节数组用于接收数据
byte[] bytes = new byte[1024];
// 读取服务器发送过来的数据(若服务器一直未发送数据,则程序阻塞在此)
int len = is.read(bytes);
System.out.println("接收到来自服务器" + socket.getInetAddress().getHostAddress() + "的信息: " + new String(bytes, 0, len));
}
// 释放连接资源
socket.close();
}
}
package com.dfbz.demo02;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* @author lscl
* @version 1.0
* @intro: 服务端程序
*/
public class Demo02_Server {
public static void main(String[] args) throws IOException {
// 创建一个服务器,并指定该TCP程序的端口(IP地址就是本机)
ServerSocket serverSocket = new ServerSocket(6969);
System.out.println("等待客户端: ");
// 接收一个客户端(若没有客户端来连接服务器,则程序阻塞在此)
Socket client = serverSocket.accept();
// 获取与客户端的输入流(用于读取客户端的数据)
InputStream is = client.getInputStream();
// 获取与客户端的输出流(用于向客户端发送数据)
OutputStream os = client.getOutputStream();
Scanner scanner = new Scanner(System.in);
while (true) {
byte[] bytes = new byte[1024];
// 读取客户端发送过来的数据
int len = is.read(bytes);
String receiveInfo = new String(bytes, 0, len);
// 如果客户端发送过来的是end则程序结束
if ("end".equals(receiveInfo)) {
System.out.println("程序结束...");
break;
}
System.out.println("接受到来自客户端" + client.getInetAddress().getHostAddress() + "的信息: " + receiveInfo);
String sendInfo = scanner.next();
// 向客户端写出数据
os.write(sendInfo.getBytes());
}
client.close();
serverSocket.close();
}
}
我们刚刚使用了TCP完成了聊天功能的编写;我们会发现我们的程序是由问题的,就是读写是串行的!
我们整个应用程序只有一个线程,那就mian线程,代码都是从上往下执行,如果main线程当前在读操作,那么就不能写。而且如果此时一方如果没有发送信息给另一方,那么另一方的read方法将会一直处于阻塞状态,代码不会往下执行;此时想往对方写出数据肯定是不行的;
我们利用多线程技术来改造我们之前的代码,让我们的代码既可以一直读,又可以一直写;
package com.dfbz.demo01;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author lscl
* @version 1.0
* @intro: 多线程实现客户端
*/
public class Demo01_Client {
public static void main(String[] args) throws Exception {
// 获取到一个新的客户端
Socket socket = new Socket("127.0.0.1", 6969);
// 读取客户端数据
InputStream is = socket.getInputStream();
// 缓冲字符输入流(读取客户端数据更加方便)
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 读线程
new Thread() {
@Override
public void run() {
try {
String info;
while (true) {
// 死循环读取客户端的信息
info = br.readLine();
if (info.equals("end")) {
socket.close();
// 退出应用程序
System.exit(0);
System.out.println("bye bye....");
break;
} else {
System.out.println("接收到: " + socket.getInetAddress().getHostAddress() + "来自的消息: " + info);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
// 给客户端写回数据
OutputStream os = socket.getOutputStream();
// 缓冲字符输出流(往客户端写数据更加方便)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
// 写线程
new Thread() {
@Override
public void run() {
try {
Scanner scanner = new Scanner(System.in);
String info;
while (true) {
// 获取键盘输入的信息
info = scanner.nextLine();
// 往客户端写数据
bw.write(info);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
package com.dfbz.demo01;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* @author lscl
* @version 1.0
* @intro: 多线程实现服务器
*/
public class Demo02_Server {
public static void main(String[] args) throws Exception {
// 创建一台服务器
ServerSocket serverSocket = new ServerSocket(6969);
while (true) {
// 获取到一个新的客户端
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress().getHostAddress() + "已与您连接成功");
// 读取客户端数据
InputStream is = socket.getInputStream();
// 缓冲字符输入流(读取客户端数据更加方便)
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 读线程
new Thread() {
@Override
public void run() {
try {
String info;
while (true) {
// 死循环读取客户端的信息
info = br.readLine();
if (info.equals("end")) {
// 关闭与客户端的连接
socket.close();
System.out.println(socket.getInetAddress().getHostAddress() + "已与您断开连接: ");
break;
} else {
System.out.println("接收到: " + socket.getInetAddress().getHostAddress() + "来自的消息: " + info);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
// 给客户端写回数据
OutputStream os = socket.getOutputStream();
// 缓冲字符输出流(往客户端写数据更加方便)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
// 写线程
new Thread() {
@Override
public void run() {
try {
Scanner scanner = new Scanner(System.in);
String info;
while (true) {
// 获取键盘输入的信息
info = scanner.nextLine();
// 往客户端写数据
bw.write(info);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
图片上传流程:
1)客户端首先通过输入流将自己磁盘中的图片读取到内存中
2)客户端通过TCP连接的输出流,向服务器写出刚刚读取到的图片数据
3)服务器通过TCP连接的输入流,将客户端刚刚发送过来的图片数据读取到内存中
4)服务器通过输出流将内存中的数据写入到服务器的磁盘中
tips:我们学习过程中,将服务器和客户端放在同一台机器。但实际开发中服务器和客户端不是在同一台机器,
图片上传代码实现:
package com.dfbz.demo02;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_Server {
public static void main(String[] args) throws Exception {
// 声明服务器
ServerSocket serverSocket = new ServerSocket(8888);
// 接收到一个客户端
Socket client = serverSocket.accept();
System.out.println(client.getInetAddress().getHostAddress() + "连接成功");
// 读取客户端传递过来的数据
InputStream is = client.getInputStream();
// 随机生成一个文件名写出到磁盘
FileOutputStream fos = new FileOutputStream(UUID.randomUUID().toString() + ".png");
byte[] data = new byte[1024];
int len;
while ((len = is.read(data)) != -1) {
// 写出到磁盘
fos.write(data, 0, len);
}
// 释放资源
fos.close();
client.close();
}
}
package com.dfbz.demo02;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Client {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("localhost", 8888);
// 读取磁盘中的图片
FileInputStream fis = new FileInputStream("C:\\Users\\Horizon\\Desktop\\001.png");
// 往服务器传递数据
OutputStream os = socket.getOutputStream();
byte[] data=new byte[1024];
int len;
while ((len=fis.read(data))!=-1){
// 写出到服务器端
os.write(data,0,len);
}
fis.close();
socket.close();
}
}
实际开发中一个服务器对应N多个客户端,其他客户端均可以上传图片。我们的代码在同一时间只允许一个人上传图片,如果这个人上传的文件较大,那么势必会造成其他用户处于等待状态;针对这种情况我们可以使用多线程来解决。
服务器每次接受到一个客户端时,都开启一个线程来独立处理这个客户端的上传任务。这样在很多人同时来上传文件时,都可以一起上传。
package com.dfbz.demo03;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author lscl
* @version 1.0
* @intro: 多线程改进图片上传服务器
*/
public class Demo01_Server {
public static void main(String[] args) throws Exception {
// 声明服务器
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// 接收到一个客户端
Socket socket = serverSocket.accept();
// 每接收到一个客户端都开启一个独立的线程为该客户端提供服务
new Thread(){
@Override
public void run() {
System.out.println(socket.getInetAddress().getHostAddress() + "连接成功");
try {
// 读取客户端传递过来的数据
InputStream is = socket.getInputStream();
// 写出到文件
FileOutputStream fos = new FileOutputStream(UUID.randomUUID().toString() + ".png");
byte[] data = new byte[1024];
int len;
while ((len = is.read(data)) != -1) {
// 写出到磁盘
fos.write(data, 0, len);
}
System.out.println("文件上传成功!");
// 释放资源
fos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
上一篇:15【转换流、缓冲流、序列流、打印流】
下一篇:17【测试单元、反射、注解、Lombok插件】
目录:【JavaSE零基础系列教程目录】