网络编程
1.什么是网络编程?
网络编程,就是在网络通信协议下,不同计算机上运行的程序,进行的数据传输
应用场景:
即时通信、网游对战、金融证券、国际贸易、邮件等等,不管是什么场景,都是加算计跟计算机之间通过网络进行数据传输
Java中可以使用java.net包下的技术轻松开发出常见的网络应用程序
(1)常见的软件架构
2.网络编程三要素
(1)IP地址:
全程:Internet Protocol,是互联网协议地址,也是IP地址,是分配给上网设备的数字标签,是设备在网络中的地址,是唯一的标识,
常见的IP分类为IPV4、IPV6
①IPv4:
全程Internet Protocol version 4,互联网通信协议第四版
采用32位地址长度。分成4组,采用点分十进制表示法,
是目前的主流方案,最多只有2的32次方个ip,目前已经用完了
1>IPv4的地址分类形式:
★公网地址(万维网使用)和私有地址(局域网使用)
★192.168.开头的就是私有地址,范围即为192.168.0.9-192.168.255.255,专门为组织机构内部使用,以此节省ip
2>特殊IP地址
127.0.0.1,也可以是localhost:是回送地址也称本地回环地址,也称本机ip,永远只会寻找当前所在本机
②IPv6
全程Internet Protocol version 4,互联网通信协议第四版
采用128位地址长度。分成8组,采用冒分十六进制表示法
特殊情况:如果计算出的16进制表示形式中间有多个连续的0,将0省略
例如FF01:0:0:0:0:0:0:1101,采用0位压缩表示法=》FF01:1101
为了解决IPv4不够用而出现的,最多有2的128次方个IP,可以为地球上的每一粒啥子分配ip
③InetAddress的使用
所有已实现的接口:Serializable
直接已知子类:Inet4Address、Inet6Address
package com_12_SocketNet._01InetAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressTest01 {
public static void main(String[] args) throws UnknownHostException {
/*
static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
*/
InetAddress address = InetAddress.getByName("192.254.72.171");
System.out.println(address);
String hostName = address.getHostName();
System.out.println(hostName);
String hostAddress = address.getHostAddress();
System.out.println(hostAddress);
}
}
(2)端口号:
应用程序在设备中唯一的标识
端口号:由两个字节表示的整数,取值范围:0-65535
其中0-1023之间的端口号用于一些知名的网络服务或者应用
我们自己使用1024以上的端口号即可
注意:一个端口号只能被一个应用程序使用
(3)协议:
在计算机网络中,连接和通信的规则被称为网络通信协议
协议就是数据在网络中传输的规则,创建的协议有UDP、TCP、HTTP、HTTPS、FTP
★OSI参考模型:世界互联协议标准,全球通新规范,但模型过于理想化,未能在因特网上进行广泛推广
★TCP/IP参考没模型(或TCP/IP协议):事实上的国际标准
①UDP协议
★是用户数据报协议(User Datagram Protocol)
★UDP是面向无连接(不检查网络链接,直接发送,不管能不能接收到)协议
特点:速度快,有大小限制一次最多发送64k,数据不安全,易丢失数据
应用场景:语音通话、在线视频、视频会议,丢失数据没有影响
②TCP协议
★传输控制协议TCP(Transmession Control Protocol)
★TCP协议是面向链接协议
特点:速度慢,不限制大小,数据安全
应用场景:软件下载、文字聊天、邮件等对数据精度要求比较高的操作
3.UDP通信程序
(1)发送数据(步骤)
①创建发送端的DatagramSoket对象
②数据打包(Datagram Packet)
③发送数据
④释放资源
package com_12_SocketNet._02UDPTest;
import java.io.IOException;
import java.net.*;
public class SendTest {
public static void main(String[] args) throws IOException {
/*步骤:
①创建发送端的DatagramSoket对象
②数据打包(Datagram Packet)
③发送数据
④释放资源*/
//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();
}
}
(2)接收数据(步骤)
①创建接收端的DatagramSocket对象
②接收打包好的数据
③解析数据包
④释放资源
package com_12_SocketNet._02UDPTest;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class _02ReciveTest {
public static void main(String[] args) throws IOException {
/*步骤:
①创建接收端的DatagramSocket对象
②接收打包好的数据
③解析数据包
④释放资源*/
//1.创建接收端的DatagramSocket对象
//细节:
//在接收的时候一定要绑定端口,
//而且绑定的端口一定要和发送的端口保持一致
DatagramSocket ds = new DatagramSocket(10086);
//2.接受打包好的数据
byte [] bytes = new byte [1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//该方法是阻塞的
//程序执行到这一步的时候,会在这里死等
//等发送端发送消息
System.out.println(11111);
ds.receive(dp);
System.out.println(2222);
//3.解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("接收到数据" + new String(data,0,len));
System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的");
//4.释放资源
ds.close();
}
}
练习
package com_12_SocketNet._03UDPTest02;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class SendTest {
public static void main(String[] args) throws IOException {
/*
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
//1.创建DatagramSocket对象
DatagramSocket ds = new DatagramSocket();//未指定端口,从随机端口发送
Scanner sc = new Scanner(System.in);
while (true) {
//2.打包数据
System.out.println("请输入你要说的话:");
String str = sc.nextLine();
if ("886".equals(str)){
break;
}
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
//指定端口发送
int port = 10086;
//打包数据
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//发送数据
ds.send(dp);
}
//释放资源
ds.close();
}
}
package com_12_SocketNet._03UDPTest02;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReciveTest {
public static void main(String[] args) throws IOException {
/*
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
//创建DatagramSocket对象
DatagramSocket ds = new DatagramSocket(10086);
//接收数据包
byte [] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
while (true) {
ds.receive(dp);
//解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
String hostAddress = dp.getAddress().getHostAddress();
String hostName = dp.getAddress().getHostName();
//打印数据
System.out.println("ip为"+hostAddress+",主机名为"+hostName+"的人,发送了数据"+new String(data,0,len));
}
}
}
(3)UDP的三种通信方式
①单播
一对一发送数据,以前的代码就是单播
②组播
一台电脑对一组电脑发送数据
组播地址:224.0.0.0~239.255.255.255,每一个ip地址代表多台电脑
其中224.0.0.0~240.0.0.255为预留的组播地址
//发送端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class SendTest {
public static void main(String[] args) throws IOException {
/*组播发送端代码*/
//1.创建组播对象
MulticastSocket ms = new MulticastSocket();
//2.创建DatagramPacket对象
String str = "雷猴啊";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("224.0.0.1");
int port = 10086;
DatagramPacket dp =new DatagramPacket(bytes,bytes.length,address,port);
ms.send(dp);
//3.释放资源
ms.close();
}
}
//接收端,只要组播地址一样,可以创建多个接收端
package com_12_SocketNet._04UDPTest03;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class ReciveTest01 {
public static void main(String[] args) throws IOException {
/*组播接收端代码*/
//1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10086);
//2.将当前主机添加到224.0.0.1这一组当中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
//3.创建DatagramPacket对象
byte [] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//4.接收数据
ms.receive(dp);
//解析数据
//获取主机ip地址
String hostAddress = dp.getAddress().getHostAddress();
//获取发送的数据
byte[] data = dp.getData();
//获取数据的长度
int len = dp.getLength();
//获取主机名
String hostName = dp.getAddress().getHostName();
//将数据变成字符串
String str = new String(data, 0, len);
System.out.println("ip为:"+hostAddress+",主机名为"+hostName+ "的人,发送了"+str);
//6.释放资源
ms.close();
}
}
③广播
广播地址:255.255.255.255
和单播代码基本一样,注意将地址改为255.255.255.255,那么所处于同一个局域网的主机都能收到发出的消息
package com_12_SocketNet._05UDPTest04;
import java.io.IOException;
import java.net.*;
public class SendTest {
public static void main(String[] args) throws IOException {
/*广播发送端代码*/
//创建DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//创建DatagramPacket对象
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);
//发送数据
ds.send(dp);
//释放资源
ds.close();
}
}
4.TCP通信程序
TCP通信协议是一种可靠的网络通信协议,它在通信的两端各建立一个Socket对象,
通信之前,要保证链接已经建立
通过Socket产生IO流来进行网络通信
(1)客户端发送代码书写步骤
①创建客户端的Socket对象与指定接收端链接
Socket (String host,int port)
②获取输出流,写出数据
OutputStream getOutputStream()
③释放资源
void close()
package com_12_SocketNet._03TCPTest._01TCPTest01;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//TCP协议,发送数据
//1.创建Socket对象
//细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10086);
//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("aaa".getBytes());
//3.释放资源
os.close();
socket.close();
}
}
注意:
在创建连接的时候,需要先启动服务端,在启动客户端,当连接成功后,会在服务端返回一个Socket对象,这个时候在客户端和服务端之间有一个连接通道,相对于连接通道,客户端是往外写出数据,是输出流,而服务端是往里面读取数据,是输入流,并且这个输入流和输出流是在连接通道里面的,所以在释放资源的时候,只需要关闭连接通道,IO流管不管都可
(2)服务器端接受代码书写步骤
①创建服务器端的SeverSocket对象
SeverSocket(int port)//这里的端口要和客户端连接的端口保持一致
②监听客户端连接,返回一个Socket对象
Socket accept()
③获取输入流,读数据,并把数据展示在控制台
InputStream getInputStream()
④释放资源
void close()
package com_12_SocketNet._03TCPTest._01TCPTest01;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10086);
//2.监听客户端的链接
Socket accept = ss.accept();
//3.从连接通道中获取输入流读取数据
InputStream is = accept.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.println((char)b);
}
//4.释放资源
is.close();
ss.close();
}
}
(3)发送中文出现乱码的问题
在服务器端接受数据的时候,因为不知道发送端发送的数据类型,只能一个字节一个字节的读取,而中文是三个字节,所以读取中文的时候读取不完整
解决方案:在服务端接受数据的时候,利用转换流将字节输入流转成字符输入流即可
package com_12_SocketNet._03TCPTest._02TCPTest02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10086);
//2.监听客户端的链接
Socket accept = ss.accept();
//3.从连接通道中获取输入流读取数据
/*InputStream is = accept.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);*/
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
int b;
while((b = br.read()) != -1){
System.out.println((char)b);
}
//4.释放资源
br.close();
ss.close();
}
}
5.三次握手/四次挥手
(1)TCP三次握手
(2)TCP四次挥手
6.综合练习
(1)多发多收
客户端:多次发送数据
服务器:接受多次接收数据,并打印
package com_12_SocketNet._04SocketTest.Test01;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
/*客户端:多次发送数据
* 服务端:接收多次接收数据,并打印*/
//1.创建Socket对象并连接服务端
Socket socket = new Socket("127.0.0.1",10086);
//2.写出数据
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream();
while (true) {
System.out.println("请输入你要说的话");
String str = sc.nextLine();
if ("886".equals(str)){
break;
}
os.write(str.getBytes());
}
//释放资源
socket.close();
}
}
package com_12_SocketNet._04SocketTest.Test01;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
/*客户端:多次发送数据
* 服务端:接收多次接收数据,并打印*/
//1.创建对象绑定端口
ServerSocket ss = new ServerSocket(10086);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while ((b = br.read()) != -1){
System.out.print(b);
}
socket.close();
ss.close();
}
}
(2)接收和反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务端:接收数据并打印,再给客户返回消息
package com_12_SocketNet._04SocketTest.Test02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
/*
客户端:发送一条数据,接收服务端反馈的消息并打印
服务端:接收数据并打印,再给客户返回消息*/
//1.创建Socket对象建立链接
Socket socket = new Socket("127.0.0.1",10086);
//2.写出数据
String str = "见到你很开心!";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//写一个结束标记
socket.shutdownOutput();
//3.接收服务端回写的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while((b = br.read()) != -1){
System.out.println((char) b);
}
//释放资源
socket.close();
}
}
package com_12_SocketNet._04SocketTest.Test02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
/*
客户端:发送一条数据,接收服务端反馈的消息并打印
服务端:接收数据并打印,再给客户返回消息*/
//1.创建对象并绑定10086端口
ServerSocket ss = new ServerSocket(10086);
//2.等待客户端链接
Socket socket = ss.accept();
//3.socket获取输入流读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while((b = br.read()) != -1) {
System.out.println((char) b);
}
//4.回写数据
OutputStream os = socket.getOutputStream();
String str = "到底有多开心呢?";
os.write(str.getBytes());
//5.释放资源
socket.close();
ss.close();
}
}
(3)上传文件
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈
package com_12_SocketNet._04SocketTest.Test03;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//1.创建Socket对象,并链接到服务端
Socket socket = new Socket("127.0.0.1",10086);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\bbb\\aaa\\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.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();
}
}
package com_12_SocketNet._04SocketTest.Test03;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D\\bbb\\2.png"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
socket.close();
ss.close();
}
}
(4)文件名重复
在上面代码操作本地文件在服务端保存到本地时有可能会文件名重复
可使用UUID类中的randomUuid静态方法生成随机不重复文件名,特殊符号可以使用toString方法变成字符串,然后使用replace方法替换掉
package com_12_SocketNet._04SocketTest.Test04;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
public class Sever {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String s = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D\\bbb\\"+s+".png"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
socket.close();
ss.close();
}
}
(5)多线程版的服务端
想要服务器不停止,能接收很多用户上床的文件,利用循环或者多线程实现
package com_12_SocketNet._04SocketTest.Test05;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//1.创建Socket对象,并链接到服务端
Socket socket = new Socket("127.0.0.1",10086);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\bbb\\aaa\\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.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();
}
}
package com_12_SocketNet._04SocketTest.Test05;
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class MyRunnable implements Runnable{
Socket socket;
public MyRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String s = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D\\bbb\\"+s+".png"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
package com_12_SocketNet._04SocketTest.Test05;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
while (true){
//开启一条线程
//一个用户就对应一条线程
new Thread(new MyRunnable(socket)).start();
}
}
}
(6)线程池优化服务端
频繁创建线程销毁线程比较浪费资源,使用线程池将上面代码优化
改写上面代码的服务端
package com_12_SocketNet._04SocketTest.Test06;
import com_12_SocketNet._04SocketTest.Test05.MyRunnable;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Sever {
public static void main(String[] args) throws IOException {
/*
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户端上传的文件,上传完毕之后给出反馈*/
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
16,//线程池总大小
60,//空闲时间
TimeUnit.SECONDS,//空闲时间(单位)
new ArrayBlockingQueue<>(2),//队列
Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
new ThreadPoolExecutor.AbortPolicy()//阻塞队列
);
//2.等待客户端来连接
Socket socket = ss.accept();
while (true){
//开启一条线程
//一个用户就对应一条线程
pool.submit(new MyRunnable(socket));
}
}
}
(7)通信练习-B/S(接收浏览器的消息并打印)
客户端:不用写
服务端:接收数据并打印
package com_12_SocketNet._04SocketTest.Test07;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws IOException {
/*客户端:多次发送数据
* 服务端:接收多次接收数据,并打印*/
//1.创建对象绑定端口
ServerSocket ss = new ServerSocket(10086);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
System.out.print((char)b);
}
socket.close();
ss.close();
}
}
//接收到的浏览器的数据
D:\Java\jdk18\bin\java.exe "-javaagent:E:\IntelliJ IDEA 2022.2.3\lib\idea_rt.jar=2720:E:\IntelliJ IDEA 2022.2.3\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath D:\workspace\idea_space\Java_based\out\production\study;D:\workspace\idea_space\Java_based\lib\gson-2.6.2.jar;D:\workspace\idea_space\Java_based\lib\commons-io-2.11.0.jar;D:\workspace\idea_space\Java_based\lib\hutool-all-5.8.10.jar com_12_SocketNet._04SocketTest.Test07.Sever
GET / HTTP/1.1
Host: 127.0.0.1:10086
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Microsoft Edge";v="114"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.51
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
(8)TCP练习-网络编程(课后大作业)
完成控制台版的聊天室
客户端
package com_12_SocketNet._04SocketTest.Test08;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
//链接服务端
Socket socket = new Socket("127.0.0.1",10086);
System.out.println("服务器链接成功");
//获取流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (true) {
System.out.println("================欢迎来到Kinbow聊天室===================");
System.out.println("1.登录");
System.out.println("2.注册");
System.out.println("请输入您的选择:");
Scanner sc = new Scanner(System.in);
String choose = sc.nextLine();
switch (choose){
case "1" -> login(socket);
case "2" -> regesiter();
default -> System.out.println("没有这个选项,请重新输入");
}
}
}
private static void regesiter() {
System.out.println("用户选择了注册选项");
}
public static void login(Socket socket) throws IOException {
//获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//键盘录入
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
//拼接
StringBuilder sb = new StringBuilder();
//username=zhangsan&password=123
sb.append("username=").append(username).append("&password=").append(password);
//第一次写的是执行登录操作
bw.write("login");
bw.newLine();
bw.flush();
//第二次写的是用户名和密码的信息
//往服务器写出用户名和密码
bw.write(sb.toString());
bw.newLine();
bw.flush();
//接收数据
//获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = br.readLine();
System.out.println(message);
//1:登录成功 2 密码有误 3 用户名不存在
if ("1".equals(message)) {
System.out.println("登录成功,开始聊天");
//开一条单独的线程,专门用来接收服务端发送过来的聊天记录
new Thread(new ClientMyRunnable(socket)).start();
//开始聊天
talk2All(bw);
} else if ("2".equals(message)) {
System.out.println("密码输入错误");
} else if ("3".equals(message)) {
System.out.println("用户名不存在");
}
}
//往服务器写出消息
private static void talk2All(BufferedWriter bw) throws IOException {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要说的话");
String str = sc.nextLine();
//把聊天内容写给服务器
bw.write(str);
bw.newLine();
bw.flush();
}
}
}
class ClientMyRunnable implements Runnable{
Socket socket;
public ClientMyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//循环,重复的接受
while (true) {
try {
//接收服务器发送过来的聊天记录
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine();
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端:
package com_12_SocketNet._04SocketTest.Test08;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;
public class Sever {
static ArrayList<Socket> list = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
//与客户端链接
Properties prop = new Properties();
FileReader fr = new FileReader("D:\\workspace\\idea_space\\Java_based\\study\\src\\com_12_SocketNet\\_04SocketTest\\Test08\\userInfo.txt");
prop.load(fr);
fr.close();
//2.接收客户端的数据
//只要来一个客户端,就开启一条线程处理
while (true) {
Socket socket = ss.accept();
new Thread(new myRunnable(socket,prop)).start();
System.out.println("客户端链接成功");
}
}
}
class myRunnable implements Runnable {
Socket socket;
Properties prop;
public myRunnable(Socket socket, Properties prop) {
this.prop = prop;
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String choose = br.readLine();
switch (choose) {
case "login" -> login(br);
case "register" -> System.out.println("用户选择了注册操作");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//获取用户登录时,传递过来的信息。
//并进行判断
public void login(BufferedReader br) throws IOException {
System.out.println("用户选择了登录操作");
String userinfo = br.readLine();
//username=zhangsan&password=123
String[] userInfoArr = userinfo.split("&");
String usernameInput = userInfoArr[0].split("=")[1];
String passwordInput = userInfoArr[1].split("=")[1];
System.out.println("用户输入的用户名为:" + usernameInput);
System.out.println("用户输入的密码为:" + passwordInput);
if (prop.containsKey(usernameInput)) {
//如果用户名存在,继续判断密码
String rightPassword = prop.get(usernameInput) + "";
if (rightPassword.equals(passwordInput)) {
//提示用户登录成功,可以开始聊天
writeMessage2Client("1");
//登录成功的时候,就需要把客户端的连接对象Socket保存起来
Sever.list.add(socket);
//写一个while(){}表示正在聊天
//接收客户端发送过来的消息,并打印在控制台
talk2All(br, usernameInput);
} else {
//密码输入有误
writeMessage2Client("2");
}
} else {
//如果用户名不存在,直接回写
writeMessage2Client("3");
}
}
private void talk2All(BufferedReader br, String username) throws IOException {
while (true) {
String message = br.readLine();
System.out.println(username + "发送过来消息:" + message);
//群发
for (Socket s : Sever.list) {
//s依次表示每一个客户端的连接对象
writeMessage2Client(s, username + "发送过来消息:" + message);
}
}
}
public void writeMessage2Client(String message) throws IOException {
//获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
public void writeMessage2Client(Socket s, String message) throws IOException {
//获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
}