java中的socket
这次在java实验的时候,要求使用server socket
编写服务器和客户端的网络通信。最开始认为应该是挺简单的,但是后来发现低估了它。出现了不少的问题,所以也在这里与大家分享。
问题描述
服务器程序的处理规则如下:
1) 向客户端程序发送Verifying Server!。
2) 若读口令次数超过3次,则发送Illegal User!给客户端,程序退出。否则向下执行步骤3)。
3) 读取客户端程序提供的口令。
4) 若口令不正确,则发送PassWord Wrong!给客户端,并转步骤2),否则向下执行步骤5)。
5) 发送Registration Successful!给客户端程序。客户端程序的处理规则如下:
1) 读取服务器反馈信息。
2) 若反馈信息不是Verifying Server!,则提示Server Wrong!,程序退出。否则向下执行步骤3)
3) 提示输入PassWord并将输入的口令发送给服务器。
4) 读取服务器反馈信息。
5) 若反馈信息是Illegal User!,则提示Illegal User!,程序退出。否则向下执行步骤6)
6) 若反馈信息是PassWord Wrong!,则提示PassWord Wrong!,并转步骤3),否则向下执行步骤。
7) 输出Registration Successful!。
初步实现
首先,我们一定要清楚,这次和之前的程序不同,虽然都是在本地上,但是服务器
和客户端
需要两个启动程序来实现,以达到我们模拟远程连接的效果。
然后就是如何利用socket
实现我们的功能了。
通过上面的图示,我们可以知道,首先需要先开启服务器
,然后等待客户端的连接。
当客户端通过socket
进行连接后,服务器端也会建立一个socket
对象来帮助实现服务器和客户端的通信。
这时候就建立了一个TCP连接
,我们就可以在服务器写数据,然后在客户端读取了,实现双方通信。
最后,当有一方决定通信结束,就会关闭连接。通信结束。
下面是初步实现的代码:
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 服务器 */ public class ServerTest { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8080); Socket clientSocket = serverSocket.accept(); String welcome = "verifying server!"; OutputStream outputStream = clientSocket.getOutputStream(); outputStream.write(welcome.getBytes()); InputStream inputStream = clientSocket.getInputStream(); int time = 0; BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String password = bufferedReader.readLine(); // 获取登录信息,允许3次登录 while (time < 3) { if (password.equals("123")) { outputStream.flush(); outputStream.write("Registration Successful!".getBytes()); break; } else { outputStream.write("PassWord Wrong!".getBytes()); outputStream.flush(); password = bufferedReader.readLine(); time++; } } if (time >= 3) { outputStream.flush(); outputStream.write("Illegal User!".getBytes()); } outputStream.close(); clientSocket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
import java.io.*; import java.net.Socket; /** * 客户端 */ public class ClientTest { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 8080); String aline = new String(); // 获取输入流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); aline = bufferedReader.readLine(); // 访问失败 if (!aline.equals("verifying server!")) { System.out.println("Server Wrong!"); return; } // 获取响应结果 String feedback = bufferedReader.readLine(); while (feedback == null || feedback.equals("PassWord Wrong!")) { if (feedback != null) { if (feedback.equals("PassWord Wrong!")) { System.out.println("PassWord Wrong!"); } else if (feedback.equals("Registration Successful!")) { System.out.println("Registration Successful!"); break; } } System.out.println("输入密码:"); // 输入密码 Scanner scanner = new Scanner(System.in); OutputStream outputStream = socket.getOutputStream(); String password = scanner.nextLine(); outputStream.write(password.getBytes()); feedback = bufferedReader.readLine(); } // 关闭连接 socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
初步实现后,运行:一片空白
。什么都没有发生。
出问题了很正常,断点调试一下吧。发现问题所在:
客户端执行到这一行时,停止,然后服务器端接着执行;
同样的服务器端也到这行就停止了。
原因是服务器一方等着输入密码,而客户端一方等着响应结果,然后又没有开始输入密码。所以双方就这么一直等着,谁都不动了。
解决
通过上面的分析,我们只要将僵局打破就能够解决问题。所以就先输入密码
,然后再获取响应结果
。这样就不会出问题了。
所以服务器端的代码并不需要做什么改动,只用修改客户端程序就行了。
import java.io.*; import java.net.Socket; /** * 客户端 */ public class ClientTest { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 8080); String aline = new String(); // 获取输入流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); aline = bufferedReader.readLine(); // 访问失败 if (!aline.equals("verifying server!")) { System.out.println("Server Wrong!"); return; } // 主要修改这里,不先获取响应结果 // 初始化响应结果 String feedback = null; while (true) { if (feedback != null) { if (feedback.equals("PassWord Wrong!")) { System.out.println("PassWord Wrong!"); } else if (feedback.equals("Registration Successful!")) { System.out.println("Registration Successful!"); break; } } System.out.println("输入密码:"); // 输入密码 Scanner scanner = new Scanner(System.in); OutputStream outputStream = socket.getOutputStream(); String password = scanner.nextLine(); outputStream.write(password.getBytes()); feedback = bufferedReader.readLine(); } // 关闭连接 socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
成功实现!
升级:利用多线程实现
功能成功实现之后,还不能满足于当前的状况。由于在实际使用socket
的时候,很多时候都不是简单的一对一的情况,这种情况下,就需要使用多线程来帮助我们了。
使用多线程,我们可以实现一个服务器多个客户端的情况,每当有一个客户端请求连接,我们就会建立一个线程。
1.服务器端线程实现
首先将服务器独立成一个线程:
/** * 服务器线程 */ public class ServerThread extends Thread { private ServerSocket serverSocket; private Socket clientSocket; public ServerThread(int port) { try { serverSocket = new ServerSocket(port); // 接受客户端连接请求 clientSocket = serverSocket.accept(); } catch (IOException e) { e.printStackTrace(); } } }
在构造函数中我们初始化服务器的socket
,然后等待客户端的连接。接着就是最主要的部分了,线程主要执行的逻辑功能:run
@Override public void run() { try { // 提示连接成功 DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream()); dataOutputStream.writeUTF("verifying server!"); // 获取输入流 InputStream inputStream = clientSocket.getInputStream(); DataInputStream dataInputStream = new DataInputStream(inputStream); String password = dataInputStream.readUTF(); // 获取登录信息,允许3次登录 int time = 0; // 密码校验,允许输入3次 while (time < 3) { if (password.equals("123")) { dataOutputStream.writeUTF("Registration Successful!"); break; } else { dataOutputStream.writeUTF("PassWord Wrong!"); password = dataInputStream.readUTF(); time++; } } if (time >= 3) { dataOutputStream.writeUTF("Illegal User!"); } } catch (IOException e) { e.printStackTrace(); } }
run
没有什么可以说的,主要就是实现服务器与客户端交互的时候执行的功能。然后在执行线程的时候,会自动调用run
方法。
最后就是启动服务器端了:
/** * 服务器端 */ public class ServiceTest { public static void main(String[] args) { ServerThread serverThread = new ServerThread(8080); serverThread.start(); } }
同样,启用8080
端口。
2.客户端线程实现
类似服务器端,首先先建立客户端线程:
/** * 客户端线程 */ public class ClientThread extends Thread { private Socket socket; public ClientThread(String host, int port) { try { this.socket = new Socket(host, port); } catch (IOException e) { e.printStackTrace(); } } }
在构造函数中,建立客户端的socket
对象。然后实现run
方法。
@Override public void run() { try { // 获取输入流 DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); String aline = dataInputStream.readUTF(); // 连接服务器失败 if (!aline.equals("verifying server!")) { System.out.println("Server Wrong!"); return; } // 获取响应结果 String feedback = null; // 进行密码输入 while (true) { if (feedback != null) { if (feedback.equals("PassWord Wrong!")) { System.out.println("PassWord Wrong!"); } else if (feedback.equals("Registration Successful!")) { System.out.println("Registration Successful!"); System.exit(1); } } System.out.println("输入密码:"); Scanner scanner = new Scanner(System.in); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); String password = scanner.nextLine(); dataOutputStream.writeUTF(password); // 获取响应结果 feedback = dataInputStream.readUTF(); } } catch (IOException e) { e.printStackTrace(); } }
run
方法中,主要实现了输入密码的功能。
最后就是启动客户端线程:
/** * 客户端 */ public class ClientTest { public static void main(String[] args) { ClientThread clientThread = new ClientThread("127.0.0.1", 8080); clientThread.start(); } }
总结
这次的实验给我提了个醒,看似简单的东西,也永远不要轻视它。只有拿出应有的实例去解决问题,问题才是你眼中那个简单的问题。