基于TCP连接的Socket通信
TCP协议提供可靠的数据传输服务是通过建立TCP连接实现的。一条“TCP连接”连接的两端是Internet上分别在两台主机运行的两个进程,一个是发送进程,一个是接收进程,每个进程用一个Socket(IP地址和端口)唯一确定。一对Socket唯一标识一条TCP连接。TCP连接是全双工和点对点的,全双工指数据可双向传输,点对点是指每条TCP连接只有两个端点。
使用一对socket进行tcp连接,连接成功后,就可以通过socket发送或接收字节流数据,完成点到点、全双工的通信。
Java的TCP Socket通信
服务端通过创建指定端口号的ServerSocket
对象,调用accept()方法等待客户端连接请求,等待期间当前进程处于堵塞状态。当服务端接收到客户端的连接请求时,进程继续运行,并建立一条TCP连接,accept()完成并返回一个Socket对象,通过该Socket对象与客户端的Socket对象实现实时的数据通信。
ServerSocket serverSocket = new ServerSocket(7000);
Socket socket = serverSocket.accept();
客户端创建Socket对象,指定服务器主机的IP和端口,发出TCP连接请求,待服务器接受连接请求后Socket对象创建成功,此时可以通过此Socket对象与服务端Socket对象实时通信。
Socket socket = new Socket("127.0.0.1", 7000);
使用Socket对象输出/读取字节流数据
从控制台获取输入的字符串,字符串数据将从PrintWriter"流入"OutputStreamWriter在"流入"socket的OutputStream中,通过TCP建立的链路,流到与之对应的Socket的InputStream中。注意,Socket总是成对的。
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
message = scanner.nextLine();
writer.println(message);
writer.flush();
在另一端的Socket中,便可以通过InputStream把发送的数据读取出来,此时数据的流向为InputStream->InputStreamReader->BufferedReader->控制台。
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
整个的数据流向为:客户端控制台->PrintWriter->OutputStreamWriter->客户端Socket的OutputStream->TCP链路->服务端Socekt的InputStream->InputStreamReader->BufferedReader->服务端控制台。通过socket便可以轻易的将数据从客户端发送到服务端了。
示例
了解了Socekt通信后,我们可以使用Socket仿照QQ群聊效果:当某个用户发送消息时,所有用户都接收到此消息并显示到控制台上。
客户端
客户端需要两个进程,一个进程不停的从控制台中读取数据,读取到用户输入时即向服务端发送读取到的数据,把它称为写进程。另一个进程接收服务端发送的数据,并把它显示到控制台上,称为读进程。
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
Socket socket = new Socket("127.0.0.1", 7000);
// 获取输入流和输出流
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 开启两个线程分别读写
// 写线程
new Thread(() -> {
// 循环从控制台读取数据
String message = "";
while (!message.equals("exit")) {
message = scanner.nextLine();
writer.println(message);
writer.flush();
}
writer.close();
}).start();
// 读线程
new Thread(() -> {
// 不停从input流获取数据
String line = "";
try {
while ((line = bufferedReader.readLine()) != null) {
System.out.format("%50s", line);
System.out.println();
}
bufferedReader.close();
} catch (IOException e) {
}
}).start();
}
服务端
为了响应多个客户端连接,需要在循环中不停的调用accept()方法,每当获取到一个新TCP连接时,把获取到的Socket对象存入set中,对每个Socket对象都开启一个线程,主要的任务是不停的从InputStream中读取数据,即接收发送客户端数据。获取到数据后,再发送给所有已连接的客户端Socket(除了发送数据的客户端Socket),即遍历set发送数据。
public class Server {
public static int userCount = 0;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7000);
Set<Socket> socketSet = new HashSet<>();
while (true) {
Socket socket = serverSocket.accept();
socketSet.add(socket);
userCount++;
// 开启一个新线程
Thread thread = new Thread(() -> {
// 不停从input流获取数据
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String userName = Thread.currentThread().getName();
String line = "";
System.out.println("start Read");
while ((line = bufferedReader.readLine()) != null) {
System.out.println(userName + "消息:" + line);
for (Socket tem :
socketSet) {
if (!tem.equals(socket)) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(tem.getOutputStream()));
writer.println(line + ":" + userName);
writer.flush();
}
}
}
bufferedReader.close();
} catch (IOException e) {
}
System.out.println("finish Read");
});
thread.setName("用户" + userCount);
thread.start();
}
}
}
在终端运行的效果图:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。