【Java】TCP Socket编程案例——文件传输&聊天工具

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();
	}

}

猜你喜欢

转载自blog.csdn.net/shimadear/article/details/88030011