文章目录
紧接着 Java Socket通信之UDP协议 再来!
一、 Java流套接字通信模型
1.TCP模型
TCP的整个通信流程如下如所示:
服务器的任务:
①创建ServerSocket对象并绑定一个端口,成为listenSocket(类似于大街拉拢客人的中介)。
②listenSocket调用ServerSocket的accept()方法,把内核建立好的连接拿到代码中进行处理。如果接收到来自客户端的请求时,accept()会返回一个Socket实例,称为clientSocket(类似于中介把客人介绍给前台小姐姐,由她负责之后的业务流程,中介继续去拉拢客人),如果没有接收到请求,则一直处于阻塞等待状态。
③使用clientSocket的getInputStream和OutputStream得到字节流对象,可进行字节的读取和写入。
④当客户端断开连接后,服务器要及时关闭clientSocket,否则会出现文件资源泄露的情况。
客户端的任务:
①创建一个Socket对象,同时指定服务器的IP和Port (建立TCP连接三次握手,由内核完成,用户感知不到)。
②客户端通过Socket对象getInputStream和getOutputStream和服务器进行通信。
关于端口被占用问题:
通常情况下,两个进程无法绑定到同一个端口号!(除特殊情况:eg. Linux中 fork系统调用)
解决办法:如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B;如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。
2.TCP Socket常见API
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API;
ServerSocket构造方法:
方法 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字socket,并绑定到响应端口上 |
ServerSocket方法:
方法 | 说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),如果有客户端连接,返回一个socket对象,如果没有,一直处于阻塞等待状态。(用户代码调用accept,才是真的把连接拿到用户代码中) |
void close() | 关闭此套接字(否则随着连接的增多,socket文件会出现文件资源泄露的情况) |
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对方端信息,即用来与对方收发数据的。
注:Socket是服务器和客户端都需要使用;ServerSocket是只在服务器中使用的。
Socket 构造方法:
方法 | 说明 |
---|---|
Socket(String host, int post) | 创建一个客户端流套接字socket,并与对应IP和port的进程建立连接 |
Socket 方法:TCP Socket 是基于字节流的,进行具体读写时和文件类似。
方法 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
二、TCP流套接字编程
1.回显服务器
服务器代码:
/**
* 代码有几个值得注意的点:
* ① 将构造好的响应写会给客户端时,要用println
* ② 返回响应时要用flush()将缓冲区中的数据冲刷到内存中
* ③ 当前简易的程序是一个服务器只能同时为一个客户端收发数据,如果再有一个客户端要通讯,需等待上一个客户端释放完成之后再通讯
* 关键问题在于:如果第一个客户端没有退出,此时服务器的逻辑一直在processConnection内部打转,没有机会再次调用到accept,
* 也没有办法处理第二个连接~
* 解决办法:使用多线程!
* 主线程里面循环调用accept()
*/
package TCPEcho;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket listenSocket = null;
//构造函数--端口绑定
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
//accept()相当于一个监听程序,TCP是一个有连接的操作,首先要建立连接
//如果客户端没有发来请求,accept()阻塞等待
//如果服务器接收到客户端发来的请求,accept()会返回一个Socket对象
//客户端和服务器的进一步交互就交给clientSocket来完成了
while (true) {
Socket clientSocket = listenSocket.accept();
processConnection(clientSocket);
}
}
//处理一个请求,这个请求中可能涉及客户端和服务器的多次交互
private void processConnection(Socket clientSocket) throws IOException {
String log = String.format("[%s:%d]客户端已上线!",
clientSocket.getInetAddress().toString(),clientSocket.getPort());
System.out.println(log);
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
while(true) {
//1.接收请求并解析
//①可以使用inputStream的read()方法读取数据到byte[]中,然后在转成String
//②第一种方法比较麻烦,还可以借助Scanner来完成这个工作
Scanner scanner = new Scanner(inputStream);
//如果连接关闭,才会触发到这个情况!(读取到EOF)
if (!scanner.hasNext()) {
log = String.format("[%s:%d]客户端已下线!",
clientSocket.getInetAddress().toString(), clientSocket.getPort());
System.out.println(log);
break;
}
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.构造响应并返回
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush(); //刷新
log = String.format("[%s:%d],request: %s, response: %s",
clientSocket.getInetAddress().toString(), clientSocket.getPort(),
request, response);
System