TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象。
通信之前要保证连接已经建立。
有两个很重要的对象 :
Scoket:服务端和客户端都要使用,负责传输数据。
ServerSoket:服务端使用,负责接收客户端的连接请求。
通过Socket产生IO流来进行网络通信。
TCP三次握手是指,客户端与服务器连接时:
第一次握手,客户端向服务器发送连接请求,等待服务器确认;
第二次握手,服务器向客户端返回一个响应,告诉客户端收到了请求;
第三次握手,客户端向服务器再次发出确认信息,建立连接。
正是因为有三握手,保证了TCP协议客户端与服务器的连接。
TCP四次挥手是指,客户端与服务器断开连接时:
第一次挥手,客户端向服务器发出取消连接请求;
第二次挥手,服务器向客户端返回一个响应,表示收到请求;
第三次挥手,服务器向客户端发出确认取消信息;
第四次挥手,客户端再次发送确认消息,连接断开。
TCP发送数据步骤
TCP接收数据步骤
public class Client {
public static void main(String[] args) throws IOException {
//客户端发送消息
//1.创建客户端socket对象
Socket socket = new Socket("127.0.0.1", 3000);
//2.因为要发送消息,所以准备输出流,这个输出流是输出到网络
OutputStream outputStream = socket.getOutputStream();
//3.发送消息,把数据写到输出流
outputStream.write("Hello,Server".getBytes(StandardCharsets.UTF_8));
//4.关闭,释放资源
outputStream.close();
socket.close();
//没有连接发送会失败报错
}
}
public class Server {
public static void main(String[] args) throws IOException {
//接收客户端发送的消息
System.out.println("服务器端启动");
//1.创建一个服务器端socket
ServerSocket serverSocket = new ServerSocket(3000);
//2.等待客户端的连接,并创建一个客户端的socket对象
Socket socket = serverSocket.accept();//会阻塞,有连接才会往下执行
System.out.println("有客户端连接");
//3.获取输入流
InputStream inputStream = socket.getInputStream();
int length = 0;
byte[] datas = new byte[1024];
length = inputStream.read(datas);
String message = new String(datas, 0, length, StandardCharsets.UTF_8);
System.out.println(message);
}
}
先启动服务器端,且服务器端可以接收多个客户端的请求,并且不止接收一次,一般不会关闭,需要用到多线程。
TCP中,客户端上传文件到服务器,与文件流的copy相似。
此时的I(InPut),是本地文件,数据从本地文件中来;O(OutPut),是网络,通过网络将文件上传到服务器,数据到网络中去。
public class FileClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端启动,正在连接服务器");
//一般不这样写,这样写叫做魔术值,一般将它拆分成一个字符串存储的IP地址,一个int类型的端口号
Socket socket = new Socket("127.0.0.1", 3000);
//上传文件
//I:数据来自于本地硬盘文件
System.out.println("开始读取本地文件");
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\工程\\成\\fifth.jpg");
//O:数据去往网络
OutputStream outputStream = socket.getOutputStream();
//文件流读写操作
byte[] datas = new byte[1024];
int length = 0;
System.out.println("文件读取完成,正在上传");
while ((length = fileInputStream.read(datas)) != -1){
outputStream.write(datas, 0, length);
}
System.out.println("文件上传完毕");
outputStream.close();
fileInputStream.close();
socket.close();
}
}
public class FileServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器端启动");
ServerSocket serverSocket = new ServerSocket(3000);
System.out.println("等待客户端连接");
Socket socket = serverSocket.accept();
System.out.println("客户端连接,准备接收文件");
//创建输入流,数据从网络来
InputStream inputStream = socket.getInputStream();
//创建输出流,数据到本地
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.jpg");
byte[] datas = new byte[1024];
int length = 0;
System.out.println("正在接收文件");
System.out.println("正在将文件写入磁盘");
while ((length = inputStream.read(datas)) != -1){
fileOutputStream.write(datas, 0, length);
}
System.out.println("文件写入完毕");
fileOutputStream.close();
inputStream.close();
socket.close();
}
}
上面的代码虽然实现了客户端与服务器的文件传输,但是一般来说,服务器接收完之后应该向客户端再返回一个确认消息,告知数据已经接收完毕,客户端根据这个消息来判断是否断开连接。
public class FileClientPlus {
public static void main(String[] args) throws IOException {
System.out.println("客户端启动,正在连接服务器");
Socket socket = new Socket("127.0.0.1", 3000);
//上传文件
//I:数据来自于本地硬盘文件
System.out.println("开始读取本地文件");
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\工程\\成\\fifth.jpg");
//O:数据去往网络
OutputStream outputStream = socket.getOutputStream();
byte[] datas = new byte[1024];
int length = 0;
System.out.println("文件读取完成,正在上传");
while ((length = fileInputStream.read(datas)) != -1){
outputStream.write(datas, 0, length);
}
//关闭output流,告诉服务器流数据传输完毕,但是不断掉与服务器的连接,这样才能继续接收服务器的消息,如果不关闭,服务器无法得知上传已经完成,所以也不会继续往后执行,返回客户端一个确认消息
socket.shutdownOutput();
System.out.println("文件上传完毕");
System.out.println("等待服务器发送确认消息");
//接收服务器端发送的确认消息,接收到以后再关闭
//1.接收首先要有一个输入流
InputStream inputStream = socket.getInputStream();
length = inputStream.read(datas);
String message = new String(datas, 0, length, StandardCharsets.UTF_8);
System.out.println("已收到来自服务器端的确认消息:" + message);
//全部操作完成后关闭输入输出流、socket
inputStream.close();
outputStream.close();
fileInputStream.close();
socket.close();
}
}
public class FileServerPlus {
public static void main(String[] args) throws IOException {
System.out.println("服务器端启动");
ServerSocket serverSocket = new ServerSocket(3000);
System.out.println("等待客户端连接");
Socket socket = serverSocket.accept();
System.out.println("客户端连接,准备接收文件");
//创建输入流,数据从网络来
InputStream inputStream = socket.getInputStream();
//每一次存的文件名字不再固定
String uuid = UUID.randomUUID().toString();
//创建输出流,数据到本地
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\"+ uuid + ".jpg");
//也可以用时间戳,目的都是为了就算上传的文件一致,但是都可以储存
// String currentTimeMillis = System.currentTimeMillis() + "";
// FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\"+ currentTimeMillis + ".jpg");
byte[] datas = new byte[1024];
int length = 0;
System.out.println("正在接收文件");
System.out.println("正在将文件写入磁盘");
while ((length = inputStream.read(datas)) != -1){
fileOutputStream.write(datas, 0, length);
}
System.out.println("文件写入完毕");
System.out.println("准备向客户端发送确认消息");
//发送消息给客户端,告知结果
OutputStream outputStream = socket.getOutputStream();
outputStream.write("ok".getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完成");
outputStream.close();
fileOutputStream.close();
inputStream.close();
socket.close();
}
}
如果客户端没有使用**socket.shutdownOutput()**关闭输出流的话,运行的结果将会是这样的
服务器不知道有没有上传完,无法进入下一步。
客户端得不到服务器的确认消息,也无法继续执行。
这样一来,就陷入了死循环。