本讲讲解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 s = new Socket("192.168.0.102", 10005);
BufferedReader bufr = new BufferedReader(new FileReader("client.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while ((line = bufr.readLine()) != null) {
out.println(line);
}
BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String lineIn = bufrIn.readLine();
System.out.println(lineIn);
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("上传文本服务器端启动了......");
ServerSocket ss = new ServerSocket(10005);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "......connected");
BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(new FileWriter("server.txt"), true);
String line = null;
while ((line = bufrIn.readLine()) != null) {
pw.println(line);
}
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
out.println("上传成功!");
pw.close();
s.close();
ss.close();
}
}
最后发现运行结果如下:
发现客户端与服务器端都处于阻塞状态,客户端连接上服务端,两端都在等待,没有任何数据传输。原因是read方法或者readLine方法是阻塞式方法。解决办法有两种:自定义结束标记或者使用Socket的禁用输出流方法,下面分别介绍它们。
自定义结束标记
使用Socket的禁用输出流方法
上传图片
首先编写上传图片的客户端程序,需要读取本地一张图片(例如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("上传图片客户端开启......");
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;
}
}
最后,运行结果如下:
多次上传同一张图片,为了防止在服务器中存储的图片名称重名,我们可以依次顺延,如下图所示:
并发访问问题
现实场景中,不只有你一个人上传图片到服务器中,真实情况是有多人同时上传图片,这种情况咋解决?即服务器内部如何实现并发访问。
要解决该问题肯定是要用到多线程技术的。首先编写一个线程任务类——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) {
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) {
Socket s = ss.accept();
new Thread(new UploadPic(s)).start();
}
}
}