实现目的,本来是要用Java实现一个TCP的代理服务器,这里首先实现利用serverSocket来实现TCP的通讯,然后再在这个基础上实现JAVA版本的代理。
一 实现的业务逻辑过程:
1. 服务端开启监听
2. 客户端通过socket连接客户端
3. 服务端接收到客户端连接后,开启一个线程单独处理每一个客户进程。
二 业务代码
服务端代码: SocketServerTest.java
package com.example.demo; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * @author freew */ public class SocketServerTest { public static void main(String[] args) { try { //创建一个服务器端的Socket,即ServerSocket,绑定需要监听的端口 ServerSocket serverSocket = new ServerSocket(8888); Socket socket = null; //记录连接过服务器的客户端数量 int count = 0; System.out.println("***服务器即将启动,等待客户端的连接***"); while(true){//循环侦听新的客户端的连接 //调用accept()方法侦听,等待客户端的连接以获取Socket实例 socket = serverSocket.accept(); //创建新线程 Thread thread = new Thread(new ServerThread(socket)); thread.start(); count++; System.out.println("服务器端被连接过的次数:"+count); InetAddress address = socket.getInetAddress(); System.out.println("当前客户端的IP为:"+address.getHostAddress()); } //serverSocket.close();一直循环监听,不用关闭连接 } catch (IOException e) { e.printStackTrace(); } } }
线程处理器代码:ServerThread.java
package com.example.demo; import java.io.*; import java.net.Socket; import java.net.SocketException; public class ServerThread implements Runnable { Socket socket = null;//和本线程相关的Socket public ServerThread(Socket socket) { this.socket = socket; } public String receiveData(Socket connection) { String buffer = ""; try { connection.setSoTimeout(2000); InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "UTF8"); BufferedReader br = new BufferedReader(isr); String data = null; while ((data = br.readLine()) != null) {//循环读取客户端的信息 buffer += data; } } catch (SocketException e) { //e.printStackTrace(); } catch (UnsupportedEncodingException e) { //e.printStackTrace(); } catch (IOException e) { //e.printStackTrace(); } if (buffer.length() > 0) { buffer = "我是服务器,客户端提交信息为:" + buffer; } return buffer; } @Override public void run() { while (true) {
//要注意这里,把收数据的代码单独拿出去是很有必要的,注意,这里是消息循环 String recevieData = receiveData(socket); if (recevieData.length() > 0) { System.out.println(recevieData); OutputStream os = null; try { os = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } //输出流包装为打印流 PrintWriter pw = new PrintWriter(os); //向服务器端发送信息 //写入内存缓冲区 pw.write(String.format("%s",recevieData+"\n")); pw.flush();//刷新缓存,向服务器端输出信息 } } } }
客户端代码:SocketClientTest.java
package com.example.demo; import java.io.*; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Random; import java.util.Scanner; /** * @author freew */ public class SocketClientTest { public static String receiveData(Socket connection) { String buffer = ""; try { connection.setSoTimeout(2000); InputStream is = connection.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "UTF8"); BufferedReader br = new BufferedReader(isr); String data = null; while ((data = br.readLine()) != null) {//循环读取客户端的信息 buffer += data; } } catch (SocketException e) { //e.printStackTrace(); } catch (UnsupportedEncodingException e) { //e.printStackTrace(); } catch (IOException e) { //e.printStackTrace(); } if (buffer.length() > 0) { buffer = "我是客服端,服务端提交信息为:" + buffer; } return buffer; } public static void main(String[] args) throws IOException { try { //创建客户端Socket,指定服务器地址和端口 Socket socket = new Socket("localhost", 8888); String msg = ""; while (true) { Scanner scan = new Scanner(System.in); // 从键盘接收数据// nextLine方式接收字符串 System.out.println("nextLine方式接收:"); // 判断是否还有输入 if (scan.hasNextLine()) { String str2 = scan.nextLine(); System.out.println("输入的数据为:" + str2 + "\n"); //建立连接后,获取输出流,向服务器端发送信息 OutputStream os = socket.getOutputStream(); //输出流包装为打印流 PrintWriter pw = new PrintWriter(os); //向服务器端发送信息 //写入内存缓冲区 pw.write(String.format("%s", str2 + "\n")); pw.flush();//刷新缓存,向服务器端输出信息 msg = receiveData(socket); if (msg.length() > 0) { System.out.println(msg); } } msg = receiveData(socket); if (msg.length() > 0) { System.out.println(msg); } } } catch (UnknownHostException e) { e.printStackTrace(); } } }
注意:上面的红色部分,这里要添加两次读出入流的操作,是为了输入后,很快就能看到服务端的返回消息。
三 测试。
启动服务端,监听 8888
启动客户端:连接 8888
客户端输入:第一个消息 回车
此时服务端接收的消息为:
马上切换到客户端查看返回的消息:
可以继续输入。
四 一些坑
1. 在java中,直接通过SOCKET进行连接,发送消息时,消息的末尾一定要加上 \n 回车符,否则,socket会认为消息还没有发完,于是就会发现,好像客户端发送了消息,服务端切收不到消息的情况。这时候真实的原因是客户端的消息其实是没有发出的。
2. 这里没有使用线程池,实际测试过程中,发现每次启动一个线程来处理,消息发送的效率不高。