目录
1. ServerSocket API(给服务器端使用的类)
2. Socket API(既给服务器使用,也给客户端使用)
TCP流套接字编程
1. ServerSocket API(给服务器端使用的类)
构造方法
方法签名 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法
方法签名 | 说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于改Socket建立与客户端的连接,否则阻塞等待(接受客户端的连接) |
void close() | 关闭此套接字 |
2. Socket API(既给服务器使用,也给客户端使用)
构造方法
方法签名 | 说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接(尝试和指定的服务器建立连接) |
方法
方法签名 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址(返回套接字获取到对方的IP地址和端口) |
InputStream getInputStream() | 返回此套接字的输入流(通过Socket可以获取到两个流对象,分别用来读和写) |
OutputStream getOutputStream() | 返回此套接字的输出流 |
3. 写TCP回显—服务器
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;
import java.util.concurrent.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer {
//代码中会设计到多个 socket 对象,使用不同的名字来区分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true) {
//1. 先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//2. 再处理这个连接
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来处理客户端请求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while(true) {
//1.读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读完了,连接可以断开了
System.out.printf("[%s:%d] 客户端下线!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.响应写回到客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区确保数据确实通过网卡发送出去了
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort(),
request,response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
下面思考为啥代码最后finally要执行clientSocket的close,而前面的listenSocket以及UDP程序中的socekt为啥就没close?
那么为什么前面UDP就不需要考虑这个问题,而TCP需要考虑
下面使用多线程,给每个客户端连上来的都分配一个新的线程负责处理请求
4. 使用线程池后的TCP服务器代码(最终)
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;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer {
//代码中会设计到多个 socket 对象,使用不同的名字来区分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService service = Executors.newCachedThreadPool();
while(true) {
//1. 先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//2. 再处理这个连接,这里应该要使用多线程,每个客户端连上来都分配一个新的线程负责处理
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来处理客户端请求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while(true) {
//1.读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读完了,连接可以断开了
System.out.printf("[%s:%d] 客户端下线!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.响应写回到客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区确保数据确实通过网卡发送出去了
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort(),
request,response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//这里要关闭socket,是因为
//socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
5. 写回显-客户端
Udp和Tcp构造的区别,有连接和无连接
6. TCP回显—客户端代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:06
*/
public class TcpEchoClient {
//客户端需要使用这个 Socket 对象来建立连接
private Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException {
//和服务器建立连接,就需要知道服务器在哪
socket = new Socket(serverIP,serverPort);
}
public void start() throws IOException {
Scanner scan = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while(true) {
//1.从控制台读取数据,构造成一个请求
System.out.println("-> ");
String request = scan.next();
//2.发送请求给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
//这个 flush 不要忘记,否则可能导致请求没有真发出去
printWriter.flush();
//3.从服务器读取响应
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//4.把响应显示到界面上
System.out.println(response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
7. 运行回显服务器和客户端