Java学习日记——从Socket类到多线程80年代聊天室

    技术2025-07-21  7

    关键字:IP,TCP,UDP,客户端,服务器端,C/S ,B/S,Echo,ServerSocket,Socket

    简介

    网络编程可以让多台电脑实现串联,网络编程就是服务器端与客户端编程的开发。主流网络编程有两种形式:

    第一种是C/S结构,也就是客户、服务器结构,缺点是麻烦,维护不便,因为要开发两套代码,也要维护两套代码。但是好处是安全,因为使用自己的连接端口,使用自己的通讯协议。

    第二种是B/S结构,只开发一套服务器端代码,客户端使用浏览器访问。好处是利于开发和维护,坏处是使用公共的HTTP协议和公共的80端口,造成我们的程序安全性不高。

    在C/S结构中,程序又可以分为两类,TCP程序与UDP程序。TCP使用可靠的连接方式进行传输。UDP使用不可靠的协议,属于数据包协议。

    在Java中为我们提供的类就是ServerSocket类(服务器类)和Socket类(客户端类)。服务器类主要在服务器端工作,用于接收用户请求。客户端类表示每一个连接到服务器的用户。

    我们使用一个经典的程序开发模型——Echo程序来做一个简单的演示。

    要求:我们要实现客户端发出一个数据,服务器端接收到之后 加上一个"Echo"进行返回。允许多次输入,不能在每次连接之后都关闭服务器端。如果输入"exit"则退出程序。

    我们来看一段代码。

    package com.chuchu.server ; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class EchoServer { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(9999) ; System.out.println("等待客户端连接。。。"); Socket client = server.accept() ; // 客户端连接 Scanner scan = new Scanner(client.getInputStream()) ; PrintStream out = new PrintStream(client.getOutputStream()) ; boolean flag = true ; while(flag) { if(scan.hasNext()) { String value = scan.next() ; if("Exit".equalsIgnoreCase(value)) flag = false ; else out.println("【ECHO】" + value); } } server.close(); client.close(); scan.close(); } } package com.chuchu.client ; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; public class EchoClient { private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)) ; public static void main(String[] args) throws Exception { Socket client = new Socket("localhost", 9999) ; Scanner scan = new Scanner(client.getInputStream()) ; scan.useDelimiter("\n") ; PrintStream out = new PrintStream(client.getOutputStream()) ; boolean flag = true ; while(flag) { String input = getString("请输入要发送的内容") ; out.println(input); if(scan.hasNext()) { System.out.println(scan.next()); } if("Exit".equalsIgnoreCase(input)) { flag = false ; } } client.close(); scan.close(); INPUT.close(); } public static String getString(String promot) throws Exception { System.out.println(promot); String value = INPUT.readLine() ; return value ; } }

    在上面的代码中我们首先使用了Java提供的两个类实例化了服务器端和客户端,在服务器端我们使用ServerSocket对象中的accept()方法连接到客户端,获取Socket对象,然后再利用Socket对象中的getInputStream()方法和getOutputStream()方法获取输入输出流。在这里我们可以使用PrintStream类帮助我们在每次获取完成数据之后进行刷新操作。

    客户端类大同小异,只不过我们可以将输入数据的代码封装成一个方法,并将键盘输入流封装成一个常量。

    我们来看一下运行结果: 通过运行结果我们看到,服务器端成功接收到数据并将其处理后返回给了客户端。一个简单的Echo模型就算是完成了。但是当我们尝试再次启动一个客户端的时候,问题就出现了。

    单线程Echo程序带来的问题

    我们发现报错,Exception in thread “main” java.net.ConnectException: Connection refused: connect 这是由于我们的这个程序是单线程的,要向被多个用户访问我们需要将其改造成多线程。服务器端的每一个线程都为一个客户端实现Echo服务支持。

    对刚才代码的改进

    package com.chuchu.server; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import com.sun.security.ntlm.Client; public class EchoServer { private static class ClientThread implements Runnable { private Socket client = null; private Scanner scan = null; private PrintStream out = null; private boolean flag = true; public ClientThread(Socket client) throws Exception { this.client = client; scan = new Scanner(client.getInputStream()); scan.useDelimiter("\n"); out = new PrintStream(client.getOutputStream()); } @Override public void run() { while (flag) { if (scan.hasNext()) { String value = scan.next(); if ("exit".equalsIgnoreCase(value)) { out.println("欢迎下次使用"); flag = false; } else { out.println("【Echo】" + value); } } } try { scan.close(); out.close(); client.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(9999); System.out.println("等待客户端连接。。。"); boolean flag = true; while (flag) { Socket client = server.accept(); new Thread(new ClientThread(client)).start(); } server.close(); } } package com.chuchu.client; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; public class EchoClient { private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)); public static void main(String[] args) throws Exception { Socket client = new Socket("localhost", 9999); Scanner scan = new Scanner(client.getInputStream()); scan.useDelimiter("\n"); PrintStream out = new PrintStream(client.getOutputStream()); boolean flag = true; while (flag) { String input = getString("请输入要发送的内容"); out.println(input); if (scan.hasNext()) { System.out.println(scan.next()); } if ("Exit".equalsIgnoreCase(input)) { flag = false; } } client.close(); scan.close(); INPUT.close(); } public static String getString(String promot) throws Exception { System.out.println(promot); String value = INPUT.readLine(); return value; } }

    在上面代码中我们实现了一个内部类,这个类实现Runnable接口,构造方法里面为客户对象赋值,获取输入输出流。在run()方法中我们执行所有业务操作。

    而在main()方法中我们写一个while循化接收客户端对象,没接收到一个就为此对象单开一个线程,为其服务。

    最后不要忘记关闭资源。

    Processed: 0.014, SQL: 9