背景
进行网络编程,就需要传输层为应用层提供的api,称为socket api,其中传输层提供了两种协议,TCP和UDP,其所对应的socket api也是不同的,下面介绍的是TCP协议,前面写过UDP,下面有些与UDP类似的操作不再细说。
1.特点
1.有连接(像电话一样需要先建立连接后才能通信)
2.可靠传输(就像钉钉一样,发送消息,若对方看见了则会显示已读)
3.面向字节流(以字节为传输的基本单位)
4.全双工(支持双向通信,可以在A给B通信的同时,B给A通信)
2.核心类
1.ServerSocket:(服务器端使用的socket)
2.Socket:(服务器和客户端都会使用的socket)
3.回显服务器的实现
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;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService service = Executors.newCachedThreadPool();//此处使用自动扩容的线程池,因为不知道客户端的数量
while (true) {
Socket clientSocket = serverSocket.accept();// 连接是系统内部做的事情,连接建立好以后将连接拿到应用程序里面,若连接还没有建立,就会阻塞,相当于别人给你打电话的接电话操作
// processConnect(clientSocket);//
// Thread thread = new Thread(()->{
// try {
// processConnect(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// });
// thread.start();//如果有好多客户端去访问会频繁的创建销毁线程,故使用线程池代替
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnect(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//通过这个方法给连上的客户端提供服务
//服务方式有两种,短连接:一个请求和一个响应。长连接:多个请求和多个响应(多次数据交互)
//此处是长连接版本
public void processConnect(Socket clientSocket) throws IOException {
System.out.println("建立连接 " + " IP:" + clientSocket.getInetAddress().toString() + " 端口 " + clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();//获取Socket内部的输入流对象,用来接收数据
OutputStream outputStream = clientSocket.getOutputStream()) {//发送数据
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
//用循环来获取多次交互的情况
while (true) {
if (!scanner.hasNext()) {
//连接断开,读完了的时候
System.out.println("断开连接 " + " IP:" + clientSocket.getInetAddress().toString() + " 端口 " + clientSocket.getPort());
break;
}
//1.读取请求
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.把响应写回客户端
printWriter.println(response);
printWriter.flush();//刷新缓冲区,避免因为数据在缓冲区而没有发送到客户端
System.out.println("请求 " + request + "响应 " + response + "IP:" + clientSocket.getInetAddress().toString() + " 端口 " + clientSocket.getPort());
}
}finally {
clientSocket.close();
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(6888);
tcpEchoServer.start();
}
}
4.客户端的实现
import javax.imageio.stream.ImageInputStream;
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",6888);
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
//长连接:一个连接会处理n个请求和响应
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scanner1 = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
//1.从控制台读入用户输入
String request = scanner.next();
//2.把请求发送给服务器
printWriter.println(request);
printWriter.flush();
//3.从服务器读取响应
String response = scanner1.next();
//4.把结果显示到界面上
System.out.println("req:"+request+" response:"+response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient();
tcpEchoClient.start();
}
}
5.注意事项
1.在输入请求的时候,scanner.next()会读取你的空白符,并且不会保留这个空白符而返回给接收的变量,而这个函数是读到空白符停止。
2.使用多线程解决无法多个客户端访问服务器的问题
3.使用线程池解决频繁创建和销毁线程的问题。