|
TCP socket 要掌握的类:
- ServerSokcet , 是创建 TCP 服务器 Socket.
- Socket, 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
构造方法:
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法:
方法签名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口 (创建时绑定的端口) ,有客户端连接后,返回一个服务端 Socket 对象,并基于该 Socket 建立与客户端的连接,否则阻塞等待 |
void close( ) | 关闭此套接字 |
构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
后两种方法, 通过 socket 可以获取到两个流对象, 分别用来读和写
服务器
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;
/**
* User: gu'jiu
* Description:服务器
*/
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) {
//先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//再处理这个连接
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) {
//读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读完了, 连接可以断开了
System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//根据请求计算相应
String response = process(request);
//把响应写回给客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭 socket
clientSocket.close();
}
}
//回显
public static String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9092);
server.start();
}
}
部分代码解释
通过 clientSocket 来获取到 inputStream 和 outputStream.
由于我们所说的
TCP
是面向字节流的, 和之前所介绍到的文件操作是完全相同的. 所以我们也是通过前面所学习过的这些字节流这些类, 来去完成我们数据的一个读写操作.这里我们的流对象就不再代表着磁盘文件了, 而是代表着 socket. 换句话说, 我们从这个
inputStream
读取数据, 就是从我们的网卡上读取数据; 从outputStream
写数据, 就是在向网卡中写数据.cilentSocket 代表着的是服务器的网卡, 向 clientSocket 读写数据, 就是相当于向网卡中读写数据, 也就相当于在客户端中读写数据.
socket 也是一个文件, 我们之前说过, 一个进程能同时打开的文件个数是有上限的.
- listenSocket 是在 TCP 服务器中, 只有唯一一个对象, 就不太会把文件描述符表沾满(随着进程的退出, 自动释放)
- 而 clientSocket 是在循环中, 每个客户端连上都要分配一个. 如果不关闭, 就会持续消耗文件描述符, 因此就需要把不再使用的 clientSocket 及时释放掉.
客户端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* User: gu'jiu
* Description:客户端
*/
public class TcpEchoClient {
//客户端需要使用这个 socket 对象来建立链接
Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException {
//和服务器建立链接, 需要知道服务器的 ip 和端口号
socket = new Socket(serverIP, serverPort);
}
public void start() {
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while(true) {
//从控制台读取数据, 构造成一个请求
System.out.println("------------------");
System.out.print("-> ");
String request = scanner.next();
//发送请求给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
//从服务器读取响应
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//把响应显示到界面上
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9092);
client.start();
}
}
运行结果展示
启动服务器
此时没有客户端连接, accept( ) 是阻塞的状态
启动客户端
启动客户端之后的服务器 (accept( )
阻塞结束)
在客户端输入"HelloGujiu"
关闭客户端后的服务器
一个服务器一般情况下, 都给多个客户端提供服务.
我们勾选一下 "Allow multiple instances"
这时我们启动两个客户端, 但是这时的服务器并没有出现两次 “客户端上线!”
为什么会出现这种情况呢?
如果没有客户端建立连接, 服务器就会阻塞到 accept
如果有一个客户端过来了, 此时就会进入 start() 方法
此时代码就阻塞在 .hasNext() 这里
于是, 我们就无法第二次调用到 accept, 也就无法处理第二个客户端了
为什么UDP 没有这个问题, TCP 有这个问题呢?
UDP 客户端直接发消息即可
TCP 建立连接之后, 要处理客户端的多次请求, 才导致无法快速的调到 accept (长连接)
如果 TCP 每个连接只处理一个客户端的请求, 也能够保证快速调到 accept (短连接)
解决方案
使用线程池
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;
/**
* User: gu'jiu
* Description:服务器
*/
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) {
//先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//再处理这个连接
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) {
//读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读完了, 连接可以断开了
System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//根据请求计算相应
String response = process(request);
//把响应写回给客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭 socket
clientSocket.close();
}
}
public static String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9092);
server.start();
}
}
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!