目录
一.使用TCP协议进行多发多收
二.实现服务端对客户端消息的接收与反馈
三.上传文件案例
四.接收多用户上传文件案例
五.线程池优化上传文件案例
六.控制台版聊天室(TCP协议)
使用TCP协议进行多发多收 1.在这个案例中,需要如何创建输入输出流? 使用的套接字对象与io流只用创建一次就行,通过循环进行多发多收 2.在这个案例中如何设置停止条件呢? 当客户端与服务器断开连接时(发送886),服务端的读取也会结束 3.如何让接收端(服务端)使用字符缓冲流的readline方法接收数据? 4.在这个案例中,如果使用缓冲流发送数据应该注意什么?
public class Demo341_Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10086);
OutputStream os = socket.getOutputStream();
while (true) {
String str = new Scanner(System.in).nextLine();
//3.这里加一个换行方便readline读取数据,或者是用其他方法显示换行
//4.在这里不使用缓冲流的write字符串方法,无法及时发出去..除非自己刷新
os.write((str+"\n").getBytes());
if ("886".equals(str)) break;
}
socket.close();
}
}
1.如何启动服务器之后通过浏览器访问? 在这里只启动这服务器,然后浏览器输入127.0.0.1:10086就能访问 2.在此处的输入流接收到一条数据之后没有发送消息不会断开连接,为什么?
public class Demo341_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
Socket socket = ss.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str;
System.out.println("2.在服务端的Reader中,读不到数据理应停止,但是这里却不是————只要有连接就能一直读:");
System.out.println("服务端获取到的字节输入流可能会出现没有数据的情况。此时调用的read()方法会出现阻塞,也就是会一直等待直到数据可读。");
while ((str = reader.readLine()) != null)System.out.println(str);
socket.close();
ss.close();
}
}
问题:关掉由socket获取的io流时也会关闭socket,如何解决? 可以使用socket类中的shutdown...方法关闭输入或者输出流
public class Demo342_Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10086);
socket.getOutputStream().write("客户端向服务端写数据!".getBytes());
socket.shutdownOutput();
System.out.println(new String(socket.getInputStream().readAllBytes()));
socket.close();
}
}
public class Demo342_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
Socket socket = ss.accept();
System.out.println(new String(socket.getInputStream().readAllBytes()));
socket.shutdownInput();
socket.getOutputStream().write("服务端向客户端返回数据!".getBytes());
socket.close();
ss.close();
}
}
public class Demo343_Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10086);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream("C:\\Users\\33428\\Videos\\haniwa\\いつだって戦ってる.mp4");
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes))!=-1)os.write(bytes,0,len);
fis.close();
socket.shutdownOutput();
System.out.println(new String(socket.getInputStream().readAllBytes()));
socket.close();
}
}
类UUID:可以生成随机唯一的字符串,字符串内容唯一 如何使用UUID获取随机字符串?
public class Demo343_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
System.out.println("使用UUID的静态方法randomUUID,变成字符串再替换掉-即可生成随机字符串,由字母数字组成");
String fileName = UUID.randomUUID().toString().replace("-","");
FileOutputStream fos = new FileOutputStream("C:\\Users\\33428\\Desktop\\" +fileName+".mp4");
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes))!=-1)fos.write(bytes,0,len);
fos.close();
socket.shutdownInput();
socket.getOutputStream().write("服务端已经接收到所有数据!".getBytes());
socket.close();
ss.close();
}
}
接收多用户上传文件案例 1.如果想要接受多个用户的上传的文件,应该怎么办呢? 循环是不行的:只能一个个的处理用户,这里使用了多线程 2.应该在哪个地方截取多线程的代码? 循环accept接收部分:每接收一个用户,都开启一条线程
public class Demo344_Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10086);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream("C:\\Users\\33428\\Videos\\haniwa\\いつだって戦ってる.mp4");
int len;
byte[] bytes = new byte[256];
while ((len = fis.read(bytes))!=-1)os.write(bytes,0,len);
fis.close();
socket.shutdownOutput();
System.out.println(new String(socket.getInputStream().readAllBytes()));
socket.close();
}
}
public class Demo344_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
while (true) {
Socket socket = ss.accept();
new Demo344_Thread(socket).start();
}
}
}
class Demo344_Thread extends Thread {
Socket socket;
public Demo344_Thread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
String fileName = UUID.randomUUID().toString().replace("-","");
FileOutputStream fos = new FileOutputStream("C:\\Users\\33428\\Desktop\\" +fileName+".mp4");
byte[] bytes = new byte[256];
int len;
while ((len = is.read(bytes)) != -1) fos.write(bytes, 0, len);
fos.close();
socket.shutdownInput();
socket.getOutputStream().write("服务端已经接收到所有数据!".getBytes());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Demo345_Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10086);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream("C:\\Users\\33428\\Videos\\haniwa\\いつだって戦ってる.mp4");
int len;
byte[] bytes = new byte[128];
while ((len = fis.read(bytes))!=-1)os.write(bytes,0,len);
fis.close();
socket.shutdownOutput();
System.out.println(new String(socket.getInputStream().readAllBytes()));
socket.close();
}
}
public class Demo345_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,8,10,
TimeUnit.MINUTES, new ArrayBlockingQueue<>(9),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while (true) {
Socket socket = ss.accept();
pool.submit(new Demo345_Runnable(socket));
}
}
}
class Demo345_Runnable implements Runnable {
Socket socket;
public Demo345_Runnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
String fileName = UUID.randomUUID().toString().replace("-","");
FileOutputStream fos = new FileOutputStream("C:\\Users\\33428\\Desktop\\" +fileName+".mp4");
byte[] bytes = new byte[256];
int len;
while ((len = is.read(bytes)) != -1) fos.write(bytes, 0, len);
fos.close();
socket.shutdownInput();
socket.getOutputStream().write("服务端已经接收到所有数据!".getBytes());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
没必要的数据就无需去放在服务器里面:放在客户端即可! 服务器就做一个最基本的接收-处理-发送
public class Client {
public static void main(String[] args) throws IOException {
//建立连接,开始运行,其实可以写到一个类里面
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 10000);
MyRunnableForClient runnable = new MyRunnableForClient(socket);
runnable.run();
}
}
public class Server {
//定义路径,定义在这里好修改一点
static String STR_PATH = "JAVA基础\\src\\Day34_InternetPractice\\homework\\userInfo.properties";
public static void main(String[] args) throws IOException {
//定义服务套接字,创建线程池,接收连接并提交任务
ServerSocket serverSocket = new ServerSocket(10000);
ExecutorService pool = Executors.newFixedThreadPool(10);
while (true) {
Socket socket = serverSocket.accept();
pool.submit(new MyRunnableForServer(socket));
}
}
}
1.多次使用对同一个套接字使用socket.getInputStream()获得的是同一个对象吗? 这里需要注意这样获得的对象不是同一个对象,所以我这里就只用了一个输入流,一个输出流 2.如何一起表示多行字符串? 使用文本块"""文本内容"""即可换行(JDK15引入)不用一个个加换行辣!!!
public class MyRunnableForClient {
//提前定义需要用到的变量
Socket socket;
BufferedReader br;
BufferedWriter bw;
Scanner scanner;
public MyRunnableForClient(Socket socket) {
//在构造方法里面初始化上面的变量
this.socket = socket;
scanner = new Scanner(System.in);
try {
//多次定义br会有不同的对象地址
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() throws IOException {
System.out.println("服务器启动成功");
while (true) {
//文本块
System.out.println("""
======欢迎来到聊天室======
1.登录
2.注册
请输入您的选择""");
//在这里输入不同的数字进行选择,返回输入的数字
if ("2".equals(chooseNum())) {
//选择2则代表注册
sendRegInfo();
//返回的信息状态代表不同的注册情况
if ("1".equals(br.readLine())) System.out.println("注册成功");
else System.out.println("注册失败!用户名重复");
continue;
}
//用户选择了登录
login();
//返回登录状态,做出不同回应
String num = br.readLine();
if ("2".equals(num)) System.out.println("密码有误");
else if ("3".equals(num)) System.out.println("用户名不存在");
else break;
}
System.out.println("登录成功");
//在发送信息前提前开启一个接收信息的线程
new Thread(() -> {//基本上一模一样,除了输入输出流对象
try {
while (true) System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}).start();
while (true) send(scanner.nextLine());
}
//循环输入,直到正确为止,发送选择并且返回输入的字符串
private String chooseNum() throws IOException {
String str;
while (true) {
str = scanner.nextLine();
if ("1".equals(str) || "2".equals(str)) break;
System.out.println("请输入正确的数字!");
}
send(str);
return str;
}
//循环发送注册信息:直到正确为止
private void sendRegInfo() throws IOException {
while (true) {
System.out.println("请输入用户名");
String username = scanner.nextLine();
System.out.println("请选择密码");
String password = scanner.nextLine();
if (username.matches("[a-zA-Z]{6,18}") && password.matches("[a-zA-Z][0-9]{2,7}")) {
send("username=" + username + "&" + "password=" + password);
break;
}
System.out.println("输入格式错误!");
}
}
//登录选择信息即可,不需要校验
private void login() throws IOException {
System.out.println("请输入用户名");
String username = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
send("username=" + username + "&" + "password=" + password);
}
private void send(String str) throws IOException {
bw.write(str + "\n");
bw.flush();
}
}
/*
properties也能够去接收txt文件
CSDN上面看到的:我这里就只创建一个输入输出流对象,这里可以了解一下
关于输入输出流的顺序与对象个数以后可以实验一下
服务器Socket和客户端Socket可以创建多个输入输出流对象,但是两端创建的个数必须保持对应
即通过客户端Socket创建多少个输入输出流对象,对应的服务器端的ServerSocket
通过accept()方法接收到Socket也必须创建多少个输入输出流对象。
*/
public class MyRunnableForServer implements Runnable {
//定义输出流集合:这需要是同一个对象:每一个连接都需要这个集合,故为静态代码,其实定义在这里也只是为了少传一个参数,初始化
static ArrayList arrayList= new ArrayList<>();
//四个常用的变量:套接字连接,输入,输出,键盘输入
Socket socket;
BufferedWriter bw;
BufferedReader br;
Scanner scanner;
//用户信息,登陆的用户名
Properties properties;
String name;
public MyRunnableForServer(Socket socket) {
//每创建一个对象都要初始化上面的七个变量
this.socket = socket;
scanner = new Scanner(System.in);
properties = new Properties();
try {
properties.load(new FileInputStream(Server.STR_PATH));
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
//接收数据,这一块用户用户的登录与注册
while (true) {
//用户选择注册
if ("2".equals(br.readLine())) {
receiveUserReg();
continue;
}
//用户选择登录
if (receiveUserInfo()) break;
}
//开始聊天了:接收数据,然后转发给所有人
//先把能聊天的加入集合
arrayList.add(bw);
while (true) {
//接收客户端的数据,接收到了再发
String str = br.readLine();
//接收到信息直接遍历字符输出流:用输出流去发消息
for (BufferedWriter writer : arrayList) {
writer.write(name + "说:" + str + "\n");
writer.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//接收用户数据,返回注册数据:成功或者失败:默认发过来的数据是符合规范的
private void receiveUserReg() throws IOException {
String userInfo = br.readLine();
String username = userInfo.split("&")[0].replace("username=", "");
String password = userInfo.split("&")[1].replace("password=", "");
//2代表用户名已存在,注册失败
if (properties.containsKey(username)) {
send("2");
return;
}
//1代表可以注册,更新文件中的数据,也把数据放到当前所有用户的集合当中
properties.put(username, password);
properties.store(new FileWriter(Server.STR_PATH), "");
send("1");
}
//用户登录并判断,返回true环开始聊天打破循,返回false继续循环
//发给客户端状态码
private boolean receiveUserInfo() throws IOException {
String userInfo = br.readLine();
String username = userInfo.split("&")[0].replace("username=", "");
String password = userInfo.split("&")[1].replace("password=", "");
//在这里可以传入字符串进行判断:一开始写错了,写成了contains,注意方法名
if (properties.containsKey(username)) {
if (properties.get(username).equals(password)) {
//包含用户名且密码正确返回状态码1,返回ture
send("1");
name = username;
return true;
//其他根据情况返回状态码,返回false
} else send("2");
} else send("3");
return false;
}
//这里由于是缓冲流:需要刷新,然后接收方才能有输出
private void send(String str) throws IOException {
bw.write(str + "\n");
bw.flush();
}
}