TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;
确认应答(ACK)机制
每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发
超时重传机制
- 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发;
但是还有一种情况是主机A未收到B发来的确认应答,也可能是因为ack丢失了
因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.
这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果
连接管理机制
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
实现TCP服务器
class TCPServer{
public static void main(String[] args) throws IOException {
//监听socket
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
//双方通信socket
System.out.println("等待client连接");
Socket socket = serverSocket.accept();
System.out.println("由client连接上来");
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(
new OutputStreamWriter(os,"UTF-8"),
false
);
Scanner scanner = new Scanner(is,"UTF-8");
while (scanner.hasNext()){
String message = scanner.nextLine();
System.out.println("收到对方消息" + message);
String responseMessage = message;
System.out.println("发给对方的消息" + message);
printWriter.println(responseMessage);
printWriter.flush();
}
socket.close();
}
}
}
cmd命令窗口 telnet127.0.0.1
ctrl + ] 停止
Client客户端
class Client{
//客户端
public static void main(String[] args) throws IOException {
// String messages = "Cat\r\ndog\r\n";
Socket socket = new Socket("127.0.0.1",8888);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner 读本地用户准备发送的Scanner = new Scanner(System.in);
Scanner 读Server发送的Scanner = new Scanner(is, "UTF-8");
while (true) {
System.out.println("请提交命令");
String messages = 读本地用户准备发送的Scanner.nextLine();
byte[] sendBuffer = messages.getBytes("UTF-8");
os.write(sendBuffer);
os.write('\r');
os.write('\n');
//读
System.out.println(读Server发送的Scanner.nextLine());
}
// socket.close();
}
}
同时启动
服务器
等待client连接
由client连接上来
收到对方消息Cat
发给对方的消息Cat
收到对方消息dog
发给对方的消息dog
客户端
Cat
dog
TCP短链接
package package1225;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPShortConnection {
}
class Server2{
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
System.out.println("等待连接");
Socket socket = serverSocket.accept();
System.out.println("连接建立成功");
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(
new OutputStreamWriter(os,"UTF-8"),
false
);
Scanner scanner = new Scanner(is,"UTF-8");
System.out.println("等待对方输入");
String message = scanner.nextLine();
System.out.println("接受了对方的输入" + message);
String echoMessage = message;
printWriter.println(echoMessage);
printWriter.flush();
socket.shutdownInput();
}
}
}
class Client2{
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
Socket socket = new Socket("127.0.0.1",8888);//不停键连接
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
String message = scanner.nextLine();
message = message + "\r\n";
os.write(message.getBytes("UTF-8"));
Scanner TcpScaner = new Scanner(is,"UTF-8");
System.out.println(TcpScaner.nextLine());
socket.close();
}
}
}
TCP长连接
public class Server {
private static class Woker implements Runnable{
private final Socket socket;
Woker(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(is,"UTF-8");
PrintStream out = new PrintStream(os,false,"UTF-8");//写
while (scanner.hasNext()){
System.out.println(Thread.currentThread().getName() + " 等待客户端发送消息");
String message = scanner.nextLine();
System.out.println(Thread.currentThread().getName() + " 收到消息" + message);
String echoMessage = message;
out.println(echoMessage);
}
}catch (IOException e){
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
int i = 0;
while (true){
System.out.println("main:等待连接");
Socket socket = serverSocket.accept();//accept 出来的连接通信的socket
System.out.println("main: 连接建立");
Thread thread = new Thread(new Woker(socket),"工作人员( " + i++ + " )");
thread.start();
}
}
}
class Client2{
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
Socket socket = new Socket("127.0.0.1",8888);//不停键连接
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
String message = scanner.nextLine();
message = message + "\r\n";
os.write(message.getBytes("UTF-8"));
Scanner TcpScaner = new Scanner(is,"UTF-8");
System.out.println(TcpScaner.nextLine());
socket.close();
}
}
}
用线程池来解决
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
ExecutorService pool = new ThreadPoolExecutor(
//线程池
10,1000,1, TimeUnit.SECONDS,queue
);
int i = 0;
while (true){
System.out.println("main:等待连接");
Socket socket = serverSocket.accept();//accept 出来的连接通信的socket
System.out.println("main: 连接建立");
pool.execute(new Woker(socket));
}
}
1UDP面向报文 无连接
只要读即可(无连接),读到的数据包一定是发送的数据(面向报文的特点)
.
2.TCP 面向连接,面向流
先accept然后read(面向连接),读到的数据可能不是你发送时的样子(面向流)
1.面向流
数据分割(1.通过特殊字符分隔\r\n 2.带上长度 3.通过关闭连接通知你结束了)
2.有连接
写server accept()上可能阻塞/read()可能阻塞(没人进来,没人发)
解决方案:线程池解决
-1.建立socket
2.bind本地ip+本地端口
3.listen
ServerSocket(8888)
while(true):
4.等待客户端连接
Socket socket = severSocket.accept();
5.把socket交给线程池去处理
线程池任务:
while()
1.从socket中读取一行数据 scanner.nextLine()
2.进行业务处理(echo|字典|计算器)
3.把结果包装成消息(尾巴添加\r\n)
4.写入消息 out.println(message)