本讲讲解TCP编程的两个案例,即如何上传文本或图片至服务器。

上传文本

首先编写上传文本的客户端程序,需要读取本地文本数据,发送给服务端,服务端接收完毕后,回馈"上传成功"字样。

package cn.liayun.net.tcp.uploadtext;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class UploadTextClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		/*
		 * 上传文本的客户端,读取本地文本数据,发送给服务端,服务端接收完毕后,回馈"上传成功"字样。
		 */
		System.out.println("上传文本客户端启动了......");
		//客户端Socket
		Socket s = new Socket("192.168.0.102", 10005);
		
		//1,确定数据源,本地文本文件。
		BufferedReader bufr = new BufferedReader(new FileReader("client.txt"));
		
		//2,确定目的,Socket输出流。
//		BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		
		String line = null;
		while ((line = bufr.readLine()) != null) {
			out.println(line);
		}
		
		//3,通过Socket读取流获取服务器端返回的数据。
		BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		String lineIn = bufrIn.readLine();//阻塞式方法
		System.out.println(lineIn);
		
		//4,关闭
		bufr.close();
		s.close();
	}

}

然后编写上传文本的服务器端程序,接收文本数据,并存储到文件中,服务端接收完毕后,回馈"上传成功"字样。

package cn.liayun.net.tcp.uploadtext;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadTextServer {

	public static void main(String[] args) throws IOException {
		/*
		 * 上传文本的服务器端,接收文本数据,并存储到文件中,服务端接收完毕后,回馈"上传成功"字样。
		 */
		System.out.println("上传文本服务器端启动了......");
		//1,服务器端对象
		ServerSocket ss = new ServerSocket(10005);
		
		//2,获取客户端
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "......connected");
		
		//3,获取读取流,确定源,网络Socket
		BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//4,确定目的,文件。
		PrintWriter pw = new PrintWriter(new FileWriter("server.txt"), true);
		
		//5,频繁读写。
		String line = null;
		while ((line = bufrIn.readLine()) != null) {
			pw.println(line);
		}
		
		//6,给客户端返回信息
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		out.println("上传成功!");
		
		pw.close();
		s.close();
		ss.close();
	}

}

最后发现运行结果如下:

java 通过tcp传输数据 java tcp传输图片_.net


发现客户端与服务器端都处于阻塞状态,客户端连接上服务端,两端都在等待,没有任何数据传输。原因是read方法或者readLine方法是阻塞式方法。解决办法有两种:自定义结束标记或者使用Socket的禁用输出流方法,下面分别介绍它们。

自定义结束标记

java 通过tcp传输数据 java tcp传输图片_上传_02

使用Socket的禁用输出流方法

java 通过tcp传输数据 java tcp传输图片_java 通过tcp传输数据_03

上传图片

首先编写上传图片的客户端程序,需要读取本地一张图片(例如1.jpg),发送给服务端,服务端接收完毕后,回馈"图片上传成功"字样。

package cn.liayun.net.tcp.uploadpic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class UploadPicClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("上传图片客户端开启......");
		//1,创建Socket客户端
		Socket s = new Socket("192.168.0.102", 10006);
		
		File picFile = new File("1.jpg");
		
		FileInputStream fis = new FileInputStream(picFile);
		
		OutputStream out = s.getOutputStream();
		
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = fis.read(buf)) != -1) {
			out.write(buf, 0, len);
		}
		
		//告诉服务器端写完了
		s.shutdownOutput();
		
		//读取服务器端的数据
		InputStream in = s.getInputStream();
		byte[] bufIn = new byte[1024];
		int lenIn = in.read(bufIn);
		String str = new String(bufIn, 0, lenIn);
		System.out.println(str);
		
		fis.close();
		s.close();
	}

}

然后编写上传图片的服务器端程序,接收图片数据,并存储到指定位置的文件中,服务端接收完毕后,回馈"图片上传成功"字样。

package cn.liayun.net.tcp.uploadpic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadPicServer {

	public static void main(String[] args) throws IOException {
		System.out.println("上传图片服务器端开启......");
		//服务器端对象
		ServerSocket ss = new ServerSocket(10006);
		
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "......connected");
		
		File file = getFile("d:\\server_pic", ip);
		
		InputStream in = s.getInputStream();
		FileOutputStream fos = new FileOutputStream(file);
			
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = in.read(buf)) != -1) {
			fos.write(buf, 0, len);
		}
			
		//回馈客户端数据
		OutputStream out = s.getOutputStream();
		out.write("图片上传成功".getBytes());
			
		fos.close();
		s.close();
		ss.close();
	}

	private static File getFile(String dir, String ip) {
		File pic_dir = new File(dir);
		if (!pic_dir.exists()) {
			pic_dir.mkdirs();
		}
		int count = 1;
		File file = new File(pic_dir, ip + "(" + count + ").jpg");
		while (file.exists()) {
			count++;
			file = new File(pic_dir, ip + "(" + count + ").jpg");
		}
		return file;
	}

}

最后,运行结果如下:

java 通过tcp传输数据 java tcp传输图片_java_04


多次上传同一张图片,为了防止在服务器中存储的图片名称重名,我们可以依次顺延,如下图所示:

java 通过tcp传输数据 java tcp传输图片_java 通过tcp传输数据_05

并发访问问题

现实场景中,不只有你一个人上传图片到服务器中,真实情况是有多人同时上传图片,这种情况咋解决?即服务器内部如何实现并发访问。
要解决该问题肯定是要用到多线程技术的。首先编写一个线程任务类——UploadPic.java,将来访的客户端都封装到一个单独的线程中。

package cn.liayun.net.tcp.uploadpic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class UploadPic implements Runnable {
	private Socket s;

	public UploadPic(Socket s) {
		super();
		this.s = s;
	}

	@Override
	public void run() {
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "......connected");
		
		File file = getFile("d:\\server_pic", ip);
		
		InputStream in;
		try {
			in = s.getInputStream();
			FileOutputStream fos = new FileOutputStream(file);
			
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
			
			//回馈客户端数据
			OutputStream out = s.getOutputStream();
			out.write("上传图片成功".getBytes());
			
			fos.close();
			s.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	private static File getFile(String dir, String ip) {
		File pic_dir = new File(dir);
		if (!pic_dir.exists()) {
			pic_dir.mkdirs();
		}
		int count = 1;
		File file = new File(pic_dir, ip + "(" + count + ").jpg");
		while (file.exists()) {
			count++;
			file = new File(pic_dir, ip + "(" + count + ").jpg");
		}
		return file;
	}

}

这时,服务器端的程序应该修改为:

package cn.liayun.net.tcp.uploadpic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadPicServer {

	public static void main(String[] args) throws IOException {
		System.out.println("上传图片服务器端开启了......");
		ServerSocket ss = new ServerSocket(10006);
		while (true) {
			
			//服务器端对象
			//ServerSocket ss = new ServerSocket(10006);//异常:java.net.BindException: Address already in use: JVM_Bind
			                                          //两个服务端用的是一个端口,端口被占用。
			
			Socket s = ss.accept();
			
			new Thread(new UploadPic(s)).start();
			
		}
//		ss.close();
	}

}