网络编程
定义:
网络编程就是计算机跟计算机之间通过网络进行数据传输
常见的软件架构:
1.C/S(Client/Server):客户端/服务器模式
2.B/S(Browser/Server):浏览器/服务器模式
区别和优缺点:
C/S需要开发客户端和服务端,
B/S不需要开发客户端。
C/S适合定制专业化的软件如:IDEA、一些画面精美的大型游戏,
B/S适合移动互联网应用,可以在任何地方随时访问的系统。
网络编程三要素
1.IP:
设备在网络中的地址,是唯一的标识
IP的作用:
设备在网络中的地址,是唯一的标识
IPv4有什么特点:
是目前的主流方案
最多只有2^32个ip,目前已经用完了
IPv6有什么特点:
为了解决IPv4不够用而出现的
最多有2^128个ip
IPv4小细节:
1.现在如何解决IPv4不够的问题?
利用局域网IP解决IP不够的问题
2.特殊的IP:
127.0.0.1(永远表示本机)
3.常见的两个CMD命令:
ipconfig:查看本机IP地址
ping(+IP地址/网址):检查网络是否连通
InetAddress类的使用:
代码演示:
public class netAddressDemo1 {
public static void main(String[] args) throws UnknownHostException {
//InetAddress类的使用
/*
static InetAddress getByName(String host) 确定主机名称的IP地址,主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
*/
//获取InetAddress对象
InetAddress address = InetAddress.getByName("LAPTOP-8RU5E3RO");
System.out.println(address);
//String getHostName() 获取此IP地址的主机名
String hostName = address.getHostName();
System.out.println(hostName);
//String getHostAddress() 返回文本显示中的IP地址字符串
String hostAddress = address.getHostAddress();
System.out.println(hostAddress);
}
}
运行结果:
2.端口号:
应用程序在设备中唯一的标识
端口号介绍:
由两个字节表示的整数,取值范围:0~65535,其中0~1023之间的端口号用于一些知名的网络服务或者应用。我们自己使用1024以上的端口号就可以了。
注意:一个端口号只能被一个应用程序使用
3.协议:
数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp
UDP协议:
用户数据报协议(User Datagram Protocol)
UDP是面向无连接通信协议
速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据
适用情况有:网络会议、在线视频、在线通话等
发送数据:
步骤:
1.创建DatagramSocket对象(快递公司)
(参数为端口号,空参表示在所有可用的端口中随机使用一个,有参则使用指定的端口号)
2.打包数据
3.发送数据
4.释放资源
代码演示:
public class SendMessageDemo1 {
public static void main(String[] args) throws IOException {
/*
1.创建DatagramSocket对象(快递公司)
2.打包数据
3.发送数据
4.释放资源
*/
//1.创建DatagramSocket对象(快递公司)
DatagramSocket ds = new DatagramSocket();
//2.打包数据
String str = "要发送的数据";
byte[] bytes = str.getBytes();
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();
}
}
接收数据:
步骤:
1.创建DatagramSocket对象(快递公司)
(接收的时候必须要绑定端口,且绑定的端口要跟发送的端口保持一致)
2.接收数据包
3.解析数据包
4.释放资源
代码演示:
public class ReceiveMessageDemo1 {
public static void main(String[] args) throws IOException {
/*
1.创建DatagramSocket对象(快递公司)
(接收的时候必须要绑定端口,且绑定的端口要跟发送的端口保持一致)
2.接收数据包
3.解析数据包
4.释放资源
*/
//1.创建DatagramSocket对象(快递公司)
DatagramSocket ds = new DatagramSocket(10086);
//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);//该方法是阻塞的,即这句代码运行后,不接受到消息,是不会往下继续运行的
//3.解析数据包
byte[] data = dp.getData();
int length = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("从" + address + "端口号为" + port
+ "接收到长度为" + length + "的数据:" + new String(data,0,length));
//4.释放资源
ds.close();
}
}
运行结果:
简易聊天室小练习:
按照下面的要求实现程序:
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
代码演示:
接收端:
public class ReceiveMessageDemo2 {
public static void main(String[] args) throws IOException {
//1.创建DatagramSocket对象
DatagramSocket ds = new DatagramSocket(10086);
//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
while (true) {
ds.receive(dp);
//3.解析数据
String ip = dp.getAddress().getHostAddress();
String hostName = dp.getAddress().getHostName();
byte[] data = dp.getData();
System.out.println("接收到从IP地址为:" + ip + "主机名为:" + hostName + "的数据:" + new String(data));
}
}
}
发送端:
public class SendMessageDemo2 {
public static void main(String[] args) throws IOException {
/*
按照下面的要求实现程序:
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
//1.创建DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.打包数据
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你要发送的内容(发送886则程序关闭)");
String message = sc.nextLine();
if("886".equals(message)) {
break;
}
byte[] bytes = message.getBytes();
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的三种通讯方式
1.单播
前面的代码就是单播
2.组播
组播地址:224.0.0.0 ~ 239.255.255.255
其中224.0.0.0 ~ 224.0.0.255为预留的组播地址
3.广播
广播地址:255.255.255.255
因为前面的代码就是单播,这里只演示组播和广播代码
组播代码演示:
发送端:
public class SendMessageDemo {
public static void main(String[] args) throws IOException {
/*
1.创建DatagramSocket对象(快递公司)
2.打包数据
3.发送数据
4.释放资源
*/
//1.创建MulticastSocket对象(快递公司)
MulticastSocket ms = new MulticastSocket();
//2.打包数据
String str = "要发送的数据";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("224.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
//3.发送数据
ms.send(dp);
//4.释放资源
ms.close();
}
}
三个接收端:
接收端1:
public class ReceiveMessageDemo1 {
public static void main(String[] args) throws IOException {
//1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10000);
//2.将本机添加到224.0.0.1这一组当中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
//3.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ms.receive(dp);
//4.解析数据
String ip = dp.getAddress().getHostAddress();
String hostName = dp.getAddress().getHostName();
byte[] data = dp.getData();
System.out.println("接收到IP地址为:" + ip + "主机名为:" + hostName + "的数据:" + new String(data));
//5.释放资源
ms.close();
}
}
接收端2:
public class ReceiveMessageDemo2 {
public static void main(String[] args) throws IOException {
//1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10000);
//2.将本机添加到224.0.0.1这一组当中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
//3.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ms.receive(dp);
//4.解析数据
String ip = dp.getAddress().getHostAddress();
String hostName = dp.getAddress().getHostName();
byte[] data = dp.getData();
System.out.println("接收到IP地址为:" + ip + "主机名为:" + hostName + "的数据:" + new String(data));
//5.释放资源
ms.close();
}
}
接收端3:
public class ReceiveMessageDemo3 {
public static void main(String[] args) throws IOException {
//1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10000);
//2.将本机添加到224.0.0.1这一组当中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
//3.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ms.receive(dp);
//4.解析数据
String ip = dp.getAddress().getHostAddress();
String hostName = dp.getAddress().getHostName();
byte[] data = dp.getData();
System.out.println("接收到IP地址为:" + ip + "主机名为:" + hostName + "的数据:" + new String(data));
//5.释放资源
ms.close();
}
}
运行结果:
接收端1:
接收端2:
接收端3:
广播代码演示:
将单播的传输目的地址改为255.255.255.255即可
public class SendMessageDemo {
public static void main(String[] args) throws IOException {
/*
1.创建DatagramSocket对象(快递公司)
2.打包数据
3.发送数据
4.释放资源
*/
//1.创建DatagramSocket对象(快递公司)
DatagramSocket ds = new DatagramSocket();
//2.打包数据
String str = "要发送的数据";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
//3.发送数据
ds.send(dp);
//4.释放资源
ds.close();
}
}
TCP协议:
传输控制协议TCP(Transmission Control Protocol)
TCP协议是面向连接的通信协议。
速度慢,没有大小限制,数据安全。
适用情况有:下载软件、文字聊天、发送邮件等
TCP通信程序:
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
编写步骤:
客户端:
①创建客户端的Socket对象(Socket)与指定服务端连接(创建对象的同时会连接服务器)
Socket(String host, int port)
②获取输出流,写数据(写出的数据应为字节数组)
OutputStream getOutputStream()
③释放资源
void close()
服务端:
①创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
②监听客户端连接,返回一个Socket对象(也是阻塞方法)
Socket accept()
③获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
④释放资源
void close()
代码演示1:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
1.创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host, int port)
2.获取输出流,写数据
OutputStream getOutputStream()
3.释放资源
void close()
*/
//1.创建客户端的Socket对象(Socket)与指定服务端连接
Socket socket = new Socket("127.0.0.1",10000);
//2.获取输出流,写数据
OutputStream os = socket.getOutputStream();
os.write("aaa".getBytes());//注意要写字节数组
//3.释放资源
os.close();
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
/*
1.创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
2.监听客户端连接,返回一个Socket对象
Socket accept()
3.获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
4.释放资源
void close()
*/
//1.创建服务器端的Socket对象(ServerSocket)
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端连接,返回一个Socket对象
Socket socket = ss.accept();
//3.获取输入流,读数据,并把数据显示在控制台
InputStream is = socket.getInputStream();
int b;
while ((b = is.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
is.close();
socket.close();
ss.close();
}
}
运行结果1:
注意:
这种方式发送中文信息会产生乱码,因为汉字转换成字节数组后字节流是一个一个字节去读的。
下面是可以传输中文的代码:
代码演示2
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
1.创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host, int port)
2.获取输出流,写数据
OutputStream getOutputStream()
3.释放资源
void close()
*/
//1.创建客户端的Socket对象(Socket)与指定服务端连接
Socket socket = new Socket("127.0.0.1",10000);
//2.获取输出流,写数据
OutputStream os = socket.getOutputStream();
os.write("你好啊".getBytes());//注意要写字节数组
//3.释放资源
os.close();
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
/*
1.创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
2.监听客户端连接,返回一个Socket对象
Socket accept()
3.获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
4.释放资源
void close()
*/
//1.创建服务器端的Socket对象(ServerSocket)
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端连接,返回一个Socket对象
Socket socket = ss.accept();
//3.获取输入流,读数据,并把数据显示在控制台
InputStream is = socket.getInputStream();
//使用转换流将字节输入流转换成字符输入流(也可以在转换成字符缓冲输入流)
InputStreamReader isr = new InputStreamReader(is);
int b;
while ((b = isr.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
socket.close();
ss.close();
}
}
运行结果2:
三次握手:
四次挥手:
参考模型
OSI参考模型:
应用层 |
表示层 |
会话层 |
传输层 |
网络层 |
数据链路层 |
物理层 |
TCP/IP参考模型:
应用层 |
传输层 |
网络层 |
物理+数据链路层 |
练习题
练习一:
多发多收
客户端:多次发送数据,发送886时结束
服务端:接收多次数据,并打印
代码演示:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
多发多收
客户端:多次发送数据,发送886时结束
服务端:接收多次数据,并打印
*/
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//2.获取输出流
OutputStream os = socket.getOutputStream();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要发送的内容");
String message = sc.nextLine();
if("886".equals(message)) {
break;
}
os.write(message.getBytes());
}
//释放资源
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//2.监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//3.获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
socket.close();
ss.close();
}
}
运行结果:
客户端:
服务端:
练习二:
接收和反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务端:接收数据并打印,再给客户端反馈消息
代码演示:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
接收和反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务端:接收数据并打印,再给客户端反馈消息
*/
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//2.获取输出流
//发送一条数据
OutputStream os = socket.getOutputStream();
os.write("你好".getBytes());
//终止输出流,终止服务端的read方法
socket.shutdownOutput();
//接收服务端反馈的消息
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1) {
System.out.print((char) b);
}
//3.释放资源
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//2.监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//3.获取输入流
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
//注意:read方法会从连接通道中读取数据
//但是,需要有一个结束标记,此时的循环才会停止
//否则,程序就会一直停在read方法这里,等待读取下面的数据
while ((b = isr.read()) != -1) {
System.out.print((char) b);
}
//输出反馈消息
OutputStream os = socket.getOutputStream();
os.write("你也好".getBytes());
//4.释放资源
socket.close();
ss.close();
}
}
运行结果:
客户端:
服务端:
练习三:
上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
代码演示:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
*/
//创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//获取输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//读取文件
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\clientdir\\img.png"));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1) {
//写出文件
bos.write(bytes,0,len);
}
bis.close();
bos.flush();
//终止输出流
socket.shutdownOutput();
//接收反馈
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String feedback = br.readLine();
System.out.println(feedback);
//释放资源
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//获取输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\serverdir\\img.png"));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1) {
//存储文件
bos.write(bytes,0,len);
}
bos.flush();
bos.close();
//反馈已接收文件信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("已接收文件");
bw.flush();
//释放资源
socket.close();
ss.close();
}
}
运行结果:
客户端:
且图片正常复制
练习四:
解决上一题文件名重复问题
代码演示:
客户端:
没有变化
public class Client {
public static void main(String[] args) throws IOException {
/*
解决上一题文件名重复问题
*/
//创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//获取输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//读取文件
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\clientdir\\img.png"));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1) {
//写出文件
bos.write(bytes,0,len);
}
bis.close();
bos.flush();
//终止输出流
socket.shutdownOutput();
//接收反馈
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String feedback = br.readLine();
System.out.println(feedback);
//释放资源
socket.close();
}
}
服务端:
新添加用UUID类来生成随机唯一名字
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//获取输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//利用UUID获取随机文件名称
String name = UUID.randomUUID().toString().replace("-", "");
//定义输出流输出到文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\serverdir\\" + name + ".png"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
//存储文件
bos.write(bytes, 0, len);
}
bos.flush();
bos.close();
//反馈已接收文件信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("已接收文件");
bw.flush();
//释放资源
socket.close();
ss.close();
}
}
运行结果:
客户端:
且图片正常复制,运行两次后结果:
练习五:
上传文件(多线程版)
想要服务器不停止,能接收很多用户上传的图片。
该怎么做? 提示:可以用多线程
代码演示:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
上传文件(多线程版)
想要服务器不停止,能接收很多用户上传的图片。
该怎么做?
提示:可以用多线程
*/
//创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//获取输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//读取文件
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\clientdir\\img.png"));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1) {
//写出文件
bos.write(bytes,0,len);
}
bis.close();
bos.flush();
//终止输出流
socket.shutdownOutput();
//接收反馈
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String feedback = br.readLine();
System.out.println(feedback);
//释放资源
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
while (true) {
//监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//创建线程
MyRunnable mr = new MyRunnable(socket);
Thread thread = new Thread(mr);
thread.start();
}
}
}
MyRunnable类:
public class MyRunnable implements Runnable {
Socket socket;
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//获取输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//利用UUID获取随机文件名称
String name = UUID.randomUUID().toString().replace("-", "");
//定义输出流输出到文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\serverdir\\" + name + ".png"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
//存储文件
bos.write(bytes, 0, len);
}
bos.flush();
bos.close();
//反馈已接收文件信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("已接收文件");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//释放资源
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
运行结果:
客户端:
且图片正常复制
练习六:
上传文件(线程池优化)
频繁创建线程并销毁非常浪费系统资源,所以需要用线程池优化
代码演示:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
/*
上传文件(线程池优化)
频繁创建线程并销毁非常浪费系统资源,所以需要用线程池优化
*/
//创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
//获取输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//读取文件
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\clientdir\\img.png"));
byte[] bytes = new byte[1024];
int len;
while((len = bis.read(bytes)) != -1) {
//写出文件
bos.write(bytes,0,len);
}
bis.close();
bos.flush();
//终止输出流
socket.shutdownOutput();
//接收反馈
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String feedback = br.readLine();
System.out.println(feedback);
//释放资源
socket.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数
16,//总线程数
60,//空闲时间
TimeUnit.SECONDS,//空闲时间单位
new ArrayBlockingQueue<>(2),//队列长度
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
while (true) {
//监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//交给线程池
pool.submit(new MyRunnable(socket));
}
}
}
MyRunnable类:
public class MyRunnable implements Runnable {
Socket socket;
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//获取输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//利用UUID获取随机文件名称
String name = UUID.randomUUID().toString().replace("-", "");
//定义输出流输出到文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\Han\\IdeaProjects\\mysocketnet\\serverdir\\" + name + ".png"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
//存储文件
bos.write(bytes, 0, len);
}
bos.flush();
bos.close();
//反馈已接收文件信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("已接收文件");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//释放资源
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
运行结果:
客户端:
且图片正常复制
练习七:
BS(接收浏览器的消息并打印)
客户端:不需要写
服务器:接收数据并打印
代码演示:
服务端:
public class Server {
public static void main(String[] args) throws IOException {
/*
BS(接收浏览器的消息并打印)
客户端:不需要写
服务器:接收数据并打印
*/
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
//2.监听客户端连接,返回Socket对象
Socket socket = ss.accept();
//3.获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
socket.close();
ss.close();
}
}
开启服务端
打开浏览器,在网址搜索栏上输入127.0.0.1:10001(服务端启动的端口号)
运行结果:
大作业:
控制台版聊天室
代码演示:
Client类:
public class Client {
public static void main(String[] args) throws IOException {
/*服务器已经连接成功
==============欢迎来到聊天室================
1登录
2注册
请输入您的选择:*/
//创建Socket对象
Socket socket = new Socket("127.0.0.1",10001);
ClientRunnable cr = new ClientRunnable(socket);
Scanner sc = new Scanner(System.in);
System.out.println("服务器已经连接成功");
while (true) {
System.out.println("==============欢迎来到聊天室================");
System.out.println("1登录");
System.out.println("2注册");
System.out.print("请输入您的选择:");
//读取用户输入信息
String select = sc.nextLine();
if (select.equals("1")) {
System.out.println("点击了登录");
if (loginEvent(socket)) {
//创建线程进入聊天室聊天
Thread thread = new Thread(cr);
thread.start();
//开始聊天
while(true) {
System.out.println("请输入想说的话:");
String message = sc.nextLine();
//传给服务端
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
}
} else if (select.equals("2")) {
System.out.println("点击了注册");
if (registerEvent()) {
break;
}
} else {
System.out.println("没有这个选项,请重新输入:");
}
}
}
//注册事件
private static boolean registerEvent() throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
System.out.println("请再次输入密码:");
String passwordAgain = sc.nextLine();
//两次输入的密码不一致
if (!(password.equals(passwordAgain))) {
System.out.println("两次输入的密码不一致");
return false;
}
//用户名或密码格式有误
if (!(usernameFormatVerify(username) && passwordFormatVerify(password))) {
System.out.println("用户名或密码格式有误");
return false;
}
//查看用户名是否已存在
BufferedReader br = new BufferedReader(
new FileReader("C:\\Users\\Han\\IdeaProjects\\mychatroom\\src\\com\\han\\chatroom\\usersinfo"));
ArrayList<String> list = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
list.add(line);
}
br.close();
for (String str : list) {
String us = str.split("=")[0];
if (username.equals(us)) {
System.out.println("用户名已存在");
return false;
}
}
//将新用户名和密码存入文件
BufferedWriter bw = new BufferedWriter(
new FileWriter("C:\\Users\\Han\\IdeaProjects\\mychatroom\\src\\com\\han\\chatroom\\usersinfo", true));
//username=zhangsan&password=123
bw.newLine();
bw.write(username + "=" + password);
bw.flush();
bw.close();
System.out.println("注册成功");
return true;
}
//登录事件
private static boolean loginEvent(Socket socket) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的用户名:");
String username = sc.nextLine();
System.out.println("请输入您的密码:");
String password = sc.nextLine();
StringBuffer sb = new StringBuffer();
//以下面这种形式将用户名密码传输给服务器
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//username=zhangsan&password=123
if (usernameFormatVerify(username) && passwordFormatVerify(password)) {
sb.append("username=").append(username).append("&password=").append(password);
//传输给服务端
//获取输出流
bw.write(sb.toString());
bw.newLine();
bw.flush();
//接收输入流并打印
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String result = br.readLine();
System.out.println(result);
if("用户名密码正确,登录成功".equals(result)) {
return true;
}
}
return false;
}
//验证用户名格式
public static boolean usernameFormatVerify(String username) {
//用户名要唯一,长度:6~18位,纯字母,不能有数字或其他符号。
int len = username.length();
//长度:6~18位
if (len < 6 || len > 18) {
System.out.println("用户名长度应为6~18位");
return false;
}
//纯字母
for (int i = 0; i < len; i++) {
char ch = username.charAt(i);
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
System.out.println("用户名应为纯字母,不能有数字或其他符号");
return false;
}
}
return true;
}
//验证密码格式
public static boolean passwordFormatVerify(String password) {
//密码长度3~8位。第一位必须是小写或者大小的字母,后面必须是纯数字。
int len = password.length();
//长度3~8位
if (len < 3 || len > 8) {
System.out.println("密码长度应为3~8位");
return false;
}
//第一位必须是小写或者大小的字母,后面必须是纯数字
char start = password.charAt(0);
if (!((start >= 'a' && start <= 'z') || (start >= 'A' && start <= 'Z'))) {
System.out.println("密码第一位必须是小写或者大小的字母,后面必须是纯数字");
return false;
}
char[] arr = new char[len - 1];
password.getChars(1, len, arr, 0);
for (char ch : arr) {
if (!(ch >= '0' && ch <= '9')) {
System.out.println("密码第一位必须是小写或者大小的字母,后面必须是纯数字");
return false;
}
}
return true;
}
}
CilientRunnable类:
public class ClientRunnable implements Runnable {
Socket socket;
public ClientRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//获取输入流
try {
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = br.readLine();
System.out.println(message);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Server类:
public class Server {
//创建集合管理登录成功的Socket对象
static ArrayList<Socket> socketList = new ArrayList<>();
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10001);
while (true) {
//监听客户端连接,获得Socket对象
Socket socket = ss.accept();
//只要来一个客户端就创建一个线程
ServerRunnable sr = new ServerRunnable(socketList,socket);
Thread thread = new Thread(sr);
thread.start();
}
}
}
ServerRunnable类:
public class ServerRunnable implements Runnable{
ArrayList<Socket> socketList;
Socket socket;
public ServerRunnable(ArrayList<Socket> socketList, Socket socket) {
this.socketList = socketList;
this.socket = socket;
}
@Override
public void run() {
String rebackStr;
try {
//获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str = br.readLine();
//username=zhangsan&password=123
String username = str.split("&")[0].split("=")[1];
String password = str.split("&")[1].split("=")[1];
//判断用户名密码是否正确
//读取文件中的用户名密码
HashMap<String,String> hm = new HashMap<>();
BufferedReader br2 = new BufferedReader(
new FileReader("C:\\Users\\Han\\IdeaProjects\\mychatroom\\src\\com\\han\\chatroom\\usersinfo"));
String line;
while((line = br2.readLine()) != null) {
String[] arr = line.split("=");
hm.put(arr[0],arr[1]);
}
//判断加回写提示
rebackStr = judge(hm,username,password);
//回写
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(rebackStr);
bw.newLine();
bw.flush();
if("用户名密码正确,登录成功".equals(rebackStr)) {
//登录成功了
socketList.add(socket);
while (true) {
//读取数据
String message = username + "发来消息:" + br.readLine();
System.out.println(message);
//群发消息
for (Socket socket : socketList) {
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw2.write(message);
bw2.newLine();
bw2.flush();
}
}
} else {
//登录失败
System.out.println("登录失败");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String judge(HashMap<String,String> hm, String username, String password) {
int flag = 0;
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> entry : entries) {
if(entry.getKey().equals(username)) {
//有相同用户名
flag++;
if(entry.getValue().equals(password)) {
return "用户名密码正确,登录成功";
}
}
}
if(flag == 0) {
return "用户名不存在,登录失败";
} else {
return "密码错误,登录失败";
}
}
}