基于Socket的网络通信(Java实现)
记录一下计算机网络的作业,顺便就当作Socket编程的一点点入门教程了
首先,你需要了解Socket的本质。
Socket,即套接字,是一种API,底层可以是TCP,UDP等协议,你可以认为Socket是通信的媒介,载体。。。你可以通过Socket获取输入流/输出流(这里的输入和输出都是像Client向Sever输入这种,有着通信含义的输入输出)。
对Socket的介绍就到这了,我感觉介绍到这已经足够了(
接下来介绍一下Socket的核心(我认为),即输入输出(流),毕竟通信离不开输入和输出,即信息的交换。
下面以输入为例,输出和输入没啥区别
输入流分为以下几种(Socket里的)
- InputStream
- InputStreamReader
- BufferedReader
- DataInputStream
总结对比
类 | 底层流 | 数据类型 | 缓冲 | 主要用途 |
---|---|---|---|---|
InputStream | 字节流 | byte | ❌ | 读取二进制数据 |
InputStreamReader | 字节→字符 | char | ❌ | 字节流→字符流(即读取文本) |
BufferedReader | 字符流 | String | ✅ | 高效读取文本(支持readLine() ) |
DataInputStream | 字节流 | 结构化数据 | ❌ | 高效读取二进制数据(int 、long 等) |
因此,显然可以发现在读取文本的时候可以使用BufferedReader,在读取文件的时候更适合使用DataInputStream(当然你可以都用InputStream,不过会比较慢)
下面就给出一些Socket编程的例子,首先是Client与Server之间的文字通信,在这里以“系统时间查询”作为例子。
这里简单说一下需要实现的功能
- 客户端向服务器端发送字符串”Time”。
- 服务器端收到该字符串后,返回当前系统时间。
- 客户端向服务器端发送字符串”Exit”。
- 服务器端返回”Bye”,然后结束TCP连接。
很显然,这里用BufferedReader就很不错,至于输出,值得一提的是这里我并没有用“BufferedWriter”,而是用了“PrintWriter”,PrintWriter相对于BufferedWriter多了一个格式化输出(自动换行之类的,比较方便,虽然会慢一点)
下面直接上源码
GetTimeServer.java
public class GetTimeServer {
private final ServerSocket serverSocket;
public GetTimeServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
private BufferedReader getReader(Socket socket) throws IOException {
return new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取这个socket的输入流
}
private PrintWriter getWriter(Socket socket) throws IOException {
return new PrintWriter(socket.getOutputStream(), true);//获取这个socket的输出流
}
public void service() throws IOException {
Socket socket;
socket = serverSocket.accept();
BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);
writer.println(socket.getInetAddress() + ":" + socket.getPort() + "已连接");
System.out.println(socket.getInetAddress() + ":" + socket.getPort() + "已连接");
while (true) {
String msg = null;
while ((msg = reader.readLine()) != null) {
System.out.println("Client "+socket.getInetAddress()+"说: " + msg);
if (msg.equals("Exit")) {
writer.println("OK,Bye~");
socket.close();
return;
}
if (msg.equals("Time")) {
writer.println("现在是: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}
}
}
public static void main(String[] args) throws IOException {
new GetTimeServer(6666).service();
}
}
GetTimeClient.java
public class GetTimeClient {
private final Socket socket;
public GetTimeClient(String host,int port) throws IOException {
this.socket = new Socket(host,port);
}
private PrintWriter getWriter() throws IOException {
return new PrintWriter(socket.getOutputStream(),true);
}
private BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public void connect() throws IOException {
BufferedReader reader = getReader();
PrintWriter writer = getWriter();
System.out.println("Sever: "+reader.readLine());
Scanner sc = new Scanner(System.in);
String msg;
while((msg = sc.next())!=null){
writer.println(msg);
System.out.println("Sever: "+reader.readLine());
if (msg.equals("Exit")){
socket.close();
break;
}
}
}
public static void main(String[] args) throws IOException {
new GetTimeClient("47.107.253.223",6666).connect();
}
}
附上一张运行效果图~
再举一个文件上传的例子
- 客户端从用户输入获得待请求的文件名。
- 客户端向服务器端发送文件名。
- 服务器端收到文件名后,传输文件。
- 客户端接收文件,重命名并存储在硬盘。
看到这里,你可能会想,用BufferedReader读取文件名,再用DataInputStream写入文件?
很遗憾,这并不合适,不同的流串用会导致缓冲区混乱,从而导致写入的文件有缺失。
因此统一使用DataInputStream是更合适的选择
下面直接给源码
FileServer.java
public class FileServer {
private final ServerSocket serverSocket;
public FileServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
void service() throws IOException {
Socket socket = serverSocket.accept();
System.out.println("收到"+socket.getInetAddress()+"的请求,已建立连接...");
DataInputStream in = new DataInputStream(socket.getInputStream());
String fileName = in.readUTF();
long fileSize = in.readLong();
System.out.println("接收到客户端上传文件的参数,上传文件名为: "+fileName+",预计上传文件大小为: "+fileSize+"字节");
String filename = "/study/"+socket.getInetAddress()+"_"+fileName;
FileOutputStream out = new FileOutputStream(filename);//写入到本地的流
byte[] data = new byte[64];//缓冲区为64字节
int read,cnt=0;//read记录每次写入一共写入几个字节,cnt记录总共写入了几个字节
while ((read = in.read(data)) != -1) {
out.write(data, 0, read);
cnt+=read;
System.out.println("写入了"+read+"字节");
}
System.out.println("实际共写入了"+cnt+"字节,程序结束,Bye~");
out.close();
in.close();
socket.close();
serverSocket.close();
}
public static void main(String[] args) throws IOException {
new FileServer(6666).service();
}
}
FileClient.java
public class FileClient {
private final Socket socket;
public FileClient(String host, int port) throws IOException {
this.socket = new Socket(host,port);
}
void upload(File file) throws IOException {
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
FileInputStream in = new FileInputStream(file);
out.writeUTF(file.getName());//写入文件名字到socket中
out.writeLong(file.length());//写入文件大小到socket中
out.flush();
byte[] data = new byte[64];
int read,cnt=0;
while ((read = in.read(data)) != -1) {
out.write(data, 0, read);
cnt+=read;
System.out.println("上传了"+read+"字节");
}
System.out.println("上传完毕,共上传了"+cnt+"字节");
in.close();
out.close();
socket.close();
}
public static void main(String[] args) throws IOException {
System.out.println("请输入要上传的文件路径: ");
String filePath = new Scanner(System.in).nextLine();
File file = new File(filePath);
new FileClient("47.107.253.223",6666).upload(file);
}
}
附上一张运行效果图~