实现目的,本来是要用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. 这里没有使用线程池,实际测试过程中,发现每次启动一个线程来处理,消息发送的效率不高。