[Java学习日记] 网络编程拓展
目录
一.使用TCP协议进行多发多收
二.实现服务端对客户端消息的接收与反馈
三.上传文件案例
四.接收多用户上传文件案例
五.线程池优化上传文件案例
六.控制台版聊天室(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;}@Overridepublic 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;}@Overridepublic 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();}}
}
六.控制台版聊天室(TCP协议)
没必要的数据就无需去放在服务器里面:放在客户端即可! 服务器就做一个最基本的接收-处理-发送
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<BufferedWriter> 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();}}@Overridepublic 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,返回turesend("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();}
}

