目录
TCP特点
基于TCP建立服务端
基于TCP建立客户端
TCP特点
1.有连接:通信双方都建立好连接,才能进行通信;那无连接是什么?便是通信双方在不建立连接的情况下也可以通信;
2.可靠传输:A给B传输信息,A可以知道B是否接收到(复杂的网络环境不能保证百分百B能接收到数据)数据;那可靠传输又是什么?便可想而知了;
3.面向字节流:以字节为基本单位;
4.全双工:一个通道,双向通信(同时上传和下载),为何一个通道可以双向通信?这一个通道里不止一个网线,例如有8根,那么就会分成两组:4进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.ExecutorService;
import java.util.concurrent.Executors;
//服务器
public class TcpEchoServer {
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();
}
}
});
}
}
public void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%s] 客户端上线!\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()) {
//客户端断开连接的时候,hasNext()就会返回false,所以,客户端一下线就结束该线程
System.out.printf("[%s:%s] 客户端下线!\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:%s] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//clientSocket在循环中,每来一个客户端就会为他分配一个;
//对象会反复被new出实例,每创建一个,都要消耗一个文件描述符;
//因此就要把不需要的clientSocket释放掉
clientSocket.close();
}
}
//这是一个回显服务器
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
问题1:为什么finally那里要进行clientSocket.close() ?listenSocket不用释放吗?
? ? ? ? ? ? clientSocket在循环中,每来一个客户端就会为他分配一个,对象会反复被new出实例,每创建一个,都要消耗一个文件描述符,因此就要把不需要的clientSocket释放掉;
? ? ? ? ? ? ?listenSocket在TCP服务器中只有唯一一个对象,并且随着进程的退出自定释放,不会把文件描述符表占满;
问题2:为什么要用线程池?
? ? ? ? 有两个概念有必要了解一下:
? ? ? ? 长连接:一个连接处理多个请求 (TCP建立连接后,要处理客户端的多次请求);
? ? ? ? 短链接:一个连接处理一个请求(TCP每个连接只处理一个客户端请求);
? ? ? ? 想要一个连接会处理 N 个请求和响应,就需要使用多线程;但是单单用循环来创建多线程可行吗?可行是可行,但是一旦需要频繁创建销毁线程,高并发的情况下,负担还是很重的,所以通过线程池来服务多个客户端;
基于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;
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient() throws IOException {
//new 这个对象的时候就需要和服务器建立连接,就要知道服务器在哪
socket = new Socket("127.0.0.1", 9090);
}
public void start() {
//长连接,一个连接会处理N个请求和响应
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanSocket = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true) {
//1.从控制台读入请求
System.out.print("->");
String request = scanner.next();
//2.将请求发给客户端
printWriter.println(request);
//刷新缓存区,确保信息发送
printWriter.flush();
//3.从服务器读取响应
String response = scanSocket.next();
//4.打印响应
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient();
client.start();
}
}
执行效果如下:
?
|