此次在java实验的时候,要求使用server socket
编写服务器和客户端的网络通讯。最开始认为应该是挺简单的,可是后来发现低估了它。出现了很多的问题,因此也在这里与你们分享。java
服务器程序的处理规则以下:
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
进行链接后,服务器端也会创建一个socket
对象来帮助实现服务器和客户端的通讯。ide
这时候就创建了一个TCP链接
,咱们就能够在服务器写数据,而后在客户端读取了,实现双方通讯。函数
最后,当有一方决定通讯结束,就会关闭链接。通讯结束。this
下面是初步实现的代码:spa
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(); } } }
初步实现后,运行:一片空白
。什么都没有发生。.net
出问题了很正常,断点调试一下吧。发现问题所在:
客户端执行到这一行时,中止,而后服务器端接着执行;
一样的服务器端也到这行就中止了。
缘由是服务器一方等着输入密码,而客户端一方等着响应结果,而后又没有开始输入密码。因此双方就这么一直等着,谁都不动了。
经过上面的分析,咱们只要将僵局打破就可以解决问题。因此就先输入密码
,而后再获取响应结果
。这样就不会出问题了。
因此服务器端的代码并不须要作什么改动,只用修改客户端程序就好了。
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
的时候,不少时候都不是简单的一对一的状况,这种状况下,就须要使用多线程来帮助咱们了。
使用多线程,咱们能够实现一个服务器多个客户端的状况,每当有一个客户端请求链接,咱们就会创建一个线程。
首先将服务器独立成一个线程:
/** * 服务器线程 */ 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
端口。
相似服务器端,首先先创建客户端线程:
/** * 客户端线程 */ 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(); } }
此次的实验给我提了个醒,看似简单的东西,也永远不要轻视它。只有拿出应有的实例去解决问题,问题才是你眼中那个简单的问题。