TCP(传输控制协议)是面向连接的可靠数据传输协议。TCP连接一旦建立起来,一直占用,直到关闭连接。另外,TCP为了保证数据的正确性,会重发一切没有收到的数据,还会对数据内容进行验证,并保证数据传输的正确顺序。因此TCP协议对系统资源的要求较多。
案例一:文件上传工具
上传过程是一个单向的socket通信过程。
客户端通过文件输入流读取文件,然后从Socket获得输入流写入数据,写入数据完成上传成功,客户端任务完成。
服务端从Socket获得输入流,然后写入文件输出流,写入数据完成上传成功,服务端任务完成。
服务器端UploadServer代码如下:
package wangluobiancheng;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadServer {
public static void main(String[] args) {
System.out.println("服务端运行...");
try {
//创建一个ServerSocket监听8080端口的客户请求
ServerSocket server=new ServerSocket(8080);
//使用accept()阻塞当前线程,等待客户端请求
Socket socket=server.accept();
BufferedInputStream in=new BufferedInputStream(socket.getInputStream());
//由文件输出流创建缓冲输出流
FileOutputStream out=new FileOutputStream("TestDir/subDir/coco.jpg");
//准备一个缓冲区
byte[] buffer=new byte[1024];
//首次从Socket读取数据
int len=in.read(buffer);
while(len!=-1)
{
//写入数据到文件
out.write(buffer,0,len);
//再次从Socket读取数据
len=in.read(buffer);
}
System.out.println("接收完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
(注意:由于当前线程是主线程,所以server.accept()会阻塞主线程,阻塞主线程是不明智的,如果是在一个图形界面的应用程序,阻塞主线程会导致无法进行任何的界面操作,就是常见的“卡”现象,所以最好是把server.accept()语句放到子线程中。)
客户端UploadClient代码如下:
package wangluobiancheng;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class UploadClient {
public static void main(String[] args) {
System.out.println("客户端运行...");
//向本机的8080端口发出请求
Socket socket;
try {
socket = new Socket("127.0.0.1",8080);
//由Socket获得输出流,并创建缓冲输出流
BufferedOutputStream out=new BufferedOutputStream(socket.getOutputStream());
//创建文件输入流
FileInputStream fin=new FileInputStream("TestDir/coco.jpg");
//由文件输入流创建缓冲输入流
BufferedInputStream in=new BufferedInputStream(fin);
//准备一个缓冲区
byte[] buffer=new byte[1024];
//首次读取文件
int len=in.read(buffer);
while(len!=-1)
{
//数据写入Socket
out.write(buffer,0,len);
//再次读取文件
len=in.read(buffer);
}
System.out.println("上传成功!");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试案例时,先运行服务器,再运行客户端。
注意当前运行的路径是工程根目录,需要将用到的文件夹创建在根目录下。本案例文件结构如下所示:
测试Socket程序要查看多个控制台信息,可以在Eclipse控制台上面的工具栏中单击“选择控制台”按钮实现切换。
案例二:聊天工具
案例一介绍的只是单向传输的Socket,Socket可以双向数据传输,比较有代表性的案例就是聊天工具。
首先客户端通过键盘输入字符串,通过标准输入流读取字符串,然后通过Socket获得输入流,将字符串写入输入流。
接着服务器通过Socket获得输入流,从输入流中读取来自客户端发送过来的字符串,然后通过标准输入流输出到显示器的控制台。服务器向客户端传送字符串过程类似。
服务器端ChatServer代码入下:
package wangluobiancheng;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) {
System.out.println("服务器运行...");
Thread t=new Thread(()->{
try ( //使用自动资源管理
//创建一个ServerSocket监听端口8080客户请求
ServerSocket server=new ServerSocket(8080);
//使用accept()阻塞等待客户端请求
Socket socket=server.accept();
//从socket中获得数据输入流
DataInputStream in=new DataInputStream(socket.getInputStream());
//从socket中获得数据输出流
DataOutputStream out=new DataOutputStream(socket.getOutputStream());
//System.in是标准输入流,使用标准输入流创建缓冲输入流
BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in))
){
while(true)
{
/*接收数据*/
//数据输入流读取UTF编码的字符串
String str=in.readUTF();
//打印接收的数据
System.out.printf("从客户端接收的数据:【%s】\n",str);
/*发送数据*/
//读取键盘输入的字符串
String keyboardInputString=keyboardIn.readLine();
//结束聊天
if(keyboardInputString.equals("bye"))
{
break;
}
//发送
out.writeUTF(keyboardInputString);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
上述代码创建一个子线程,将网络通信放到子线程中处理是一种很好的做法,因为网络通信往往有线程阻塞过程,放到子线程中处理就不会阻塞主线程了。
自动资源管理:一般try-catch语句后面还跟有一个finally代码块,无论try正常结束还是catch异常结束都会执行finally代码块,可以在finally语句中释放资源。而使用finally代码块释放资源会导致程序代码大量增加,在Java 7之后提供的自动资源管理技术可以代替finally代码块,不需要自己关闭这些资源,释放过程交给了JVM。自动资源管理是在try语句上的扩展,在try语句后面添加一对小括号"()",其中是声明或初始化资源语句,可以有多条语句,语句之间用分号";"分隔。
注意:所有可以自动管理的资源需要实现AutoCloseable接口,具体哪些资源实现AutoCloseable接口需要查询API文档。
客户端ChatClient代码如下:
package wangluobiancheng;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class ChatClient {
public static void main(String[] args) {
System.out.println("客户端运行...");
Thread t=new Thread(()->{
try (
//向127.0.0.1主机8080端口发出连接请求
Socket socket=new Socket("127.0.0.1",8080);
DataInputStream in=new DataInputStream(socket.getInputStream());
DataOutputStream out=new DataOutputStream(socket.getOutputStream());
BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in))
){
while(true)
{
/*发送数据*/
//读取键盘输入的字符串
String keyboardInpuStream=keyboardIn.readLine();
//结束聊天
if(keyboardInpuStream.equals("bye"))
{
break;
}
//发送
out.writeUTF(keyboardInpuStream);
out.flush();
/*接收数据*/
String str=in.readUTF();
//打印接收的数据
System.out.printf("从服务器接收的数据:【%s】\n",str);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端停止!");
});
t.start();
}
}