网络编程需要依靠Socket API,在java标准库中有两种风格: 1.(UDP)DatagramSocket:面向数据报(发送接收数据,必须以一定的数据报为单位进行传输) 2.(TCP)ServerSocket:面向字节流
UDP和TCP就是传输层的两个最重要的协议
TCP
服务器逻辑:
1.初始化服务器 2.进入主循环 1)先去从内核中获取到一个TCP的连接 2)处理这个TCP的连接 a)读取请求并解析 b)根据请求计算响应 c)把响应写回给客户端
服务器实现:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpEchoServer {
//1.初始化服务器
//2.进入主循环
// 1)先去从内核中获取到一个TCP的连接
// 2)处理这个TCP的连接
// a)读取请求并解析
// b)根据请求计算响应
// c)把响应写回给客户端
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
// 1)先去从内核中获取到一个TCP的连接
//TCP的连接管理是由操作系统内核来管理的(先描述,再组织【使用一个阻塞队列来组织若干个连接对象】)
//当连接建立成功,内核已经把这个连接对象放到了阻塞队列中了,代码中调用到accept就是从阻塞队列中取出一个连接对象
//在应用程序中就是Socket对象
//如果服务器启动后,没有客户端建立连接,此时代码中的accept就会阻塞,直到有客户建立连接了才停止阻塞
Socket clientSocket = serverSocket.accept();
// 2)处理这个TCP的连接
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//clientSocket.getInetAddress().toString():获得出IP
//clientSocket.getPort()):获得端口号
//通过 clientSocket 来和客户端交互,先做好准备工作,获取到clientSocket中流对象
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
//getInputStream();getOutputStream():字节流
//InputStreamReader;OutputStreamWriter:把字节流转成字符流,
//BufferedReader;BufferedWriter:套上缓冲区
//此处是长连接版本:一次连接的过程中,需要处理多个请求和响应
//短连接就是去掉while循环
while (true) {
// a)读取请求并解析
String request = bufferedReader.readLine();
//此处暗含一个信息(协议):
//客户端发的数据必须是一个按行发送的数据(每一条数据占一行)
// b)根据请求计算响应
String response = process(request);
// c)把响应写回给客户端(客户端要按行来读)
bufferedWriter.write(response+"\n");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
这里的服务器方法实现中使用到了长连接,那么对应的就是短连接 长连接:一个连接中,客户端和服务器之间交互N次,直到满足一定条件在断开 短连接:一个连接中,客户端和服务器之间交互一次,交互完毕就断开连接
长连接比短连接效率更高
客户端逻辑:
1.启动客户端(一定不要绑定端口号) 2.进入主循环 a)读取用户输入内容 b)构造一个请求发送给服务器 c)读取服务器的响应数据 d)把响应数据显示到界面上
客户端:
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
//1.启动客户端(一定不要绑定端口号)
//2.进入主循环
// a)读取用户输入内容
// b)构造一个请求发送给服务器
// c)读取服务器的响应数据
// d)把响应数据显示到界面上
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))){
while (true){
// a)读取用户输入内容
System.out.println("->");
String request = scanner.nextLine();
if ("exit".equals(request)){
break;
}
// b)构造一个请求发送给服务器
bufferedWriter.write(request + "\n");//按行写
bufferedWriter.flush();
// c)读取服务器的响应数据
String response = bufferedReader.readLine();
// d)把响应数据显示到界面上
System.out.println(response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
存在的问题
以上的服务器和客户端交互的过程中,第一个客户端发送请求,就会进入while循环,只有当第一个客户端退出的时候,第二个客户端发送的请求才会被响应,其原因就是客户端大于一个的时候,就会在accept方法中阻塞,这时,为了提高效率,也就是说为了让多个客户端一起被服务器响应,就可以利用多线程的方式
代码如下:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpThreadEchoServer {
private ServerSocket serverSocket = null;
public TcpThreadEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
Socket clientSocket = serverSocket.accept();
//针对这个连接,单独创建一个线程负责处理
Thread t = new Thread(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
t.start();
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
while (true) {
// a)读取请求并解析
String request = bufferedReader.readLine();
// b)根据请求计算响应
String response = process(request);
// c)把响应写回给客户端(客户端要按行来读)
bufferedWriter.write(response+"\n");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
server.start();
}
}
(只有start()方法变了,其他均与之前代码一样)
但是这也会存在一个问题,如果客户端太多了,那么创建的线程也太多了,服务器需要频繁的创建和销毁线程,这时就可以使用标准库中的线程池
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpThreadPoolEchoServer {
private ServerSocket serverSocket = null;
public TcpThreadPoolEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
//先创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
while (true) {
// a)读取请求并解析
String request = bufferedReader.readLine();
// b)根据请求计算响应
String response = process(request);
// c)把响应写回给客户端(客户端要按行来读)
bufferedWriter.write(response+"\n");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
server.start();
}
}