客户端Socket类
TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
表示客户端的类:
java.net.Socket:此类实现客户端套接字。
构造方法:
Socket(String host, int port)
创建一个流套接字并将并将其连接到指定主机上的指定端口号。
参数:
String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号
成员方法:
OutputStream getOutputStream()返回此套接字的输出流
InputStream getInputStream()返回此套接字的输入流
void close()关闭此套接字
实现步骤:
- 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
- 使用Socket对象中的getOutputStream()获取网络字节输出流OutputStream对象
- 使用网络字节输出流OutputStream对象中的方法write()给服务器发数据
- 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream中的read()方法读取服务器回写的数据
- 释放资源(Socket)
注意:
- 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
- 当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
- 如果这时服务器没有启动,那么就会抛出异常Exception in thread “main” java.net.ConnectException: Connection refused: connect
- 如果服务器已经启动,那么就可以进行交互了
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
socket.close();
}
}
服务器ServerSocket类
TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据。
表示服务器的类:
java.net.ServerSocket:此类实现服务器套接字
构造方法:
ServerSocket(int port)
创建绑定到特定端口的服务器套接字
- 服务器必须明确一件事情,必须知道是那个客户端请求的服务器,所以可以使用accept()方法获取到请求的客户端对象Socket
成员方法:
Socket accept()侦听并接收到此套接字的连接
服务器的实现步骤
- 创建服务器ServerSocket对象和系统要指定的端口号
- 使用ServerSocket对象中的方法accept(),获取到请求的客户端对象Socket
- 用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
- 使用InputStream对象中的方法read()方法读取客户端发送的数据
- 获取网络字节输出流OutputStream对象,使用write()给客户端回写数据
- 释放资源(Socket, ServerSocket)
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes, 0, len));
OutputStream os = socket.getOutputStream();
os.write("收到,谢谢".getBytes());
socket.close();
server.close();
}
}
综合实例
配合使用FileInputStream和Socket类实现客户端、服务器端的文件上传。
客户端:
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\work_space\\demo.mkv"));
int len = 0;
byte[] bytes = new byte[1024];
System.out.println("正在上传......");
while((len = bis.read(bytes)) != -1){
os.write(bytes, 0, len);
}
/*
上传完文件,给服务器写一个结束标记
void shutdownOutput()禁用此套接字的输出流
对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP正常连接中止序列。
*/
socket.shutdownOutput();
InputStream is = socket.getInputStream();
while((len = is.read(bytes)) != -1){
System.out.println(new String(bytes, 0, len));
}
bis.close();
socket.close();
}
}
服务器:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
File file = new File("D:\\work_space\\Project01\\src\\demo18net\\fileupload");
if(!file.exists()){
file.mkdir();
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file + "\\upload.mkv"));
int len = 0;
byte[] bytes = new byte[1024];
System.out.println("正在接收数据......");
long start = System.currentTimeMillis();
while((len = is.read(bytes)) != -1){
bos.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("上传用时: " + (end - start) + "ms.");
OutputStream os = socket.getOutputStream();
os.write("上传完成".getBytes());
bos.close();
socket.close();
}
}
注意:如果客户端的Socket对象没有调用shutdownOutput()方法,服务器的read方法读取不到文件的结束标记,使得read方法进入阻塞状态,一直等待数据读取,于是服务器的后续语句将得不到执行。
文件上传优化
对于上面的示例有以下几个问题:
- 文件名称写死,最终导致服务器硬盘只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
- 循环接收问题。服务器只保存一个文件就关闭了,之后的用户无法再上传,应该使用循环,可以不断接收不同用户的文件。
while(true){
Socket socket = server.accept();
//file transfer
}
- 效率问题,使用多线程技术同时接收几个用户的上传
SocketServer server = new SocketServer(8888);
while(true){
Socket socket = server.accept();
new Thread(new Runnable() {
@Override
public void run(){
//完成文件上传
}
});
}
注意:Runnable接口的run方法没有抛出异常,所以要用try-catch块捕捉IOException。
改进后服务器如下:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
while(true){
Socket socket = server.accept();
/*
使用多线程技术,提高程序效率
有一个客户端上传文件,就开启一个线程,完成文件的上传
*/
new Thread(new Runnable() {
//完成文件的上传
@Override
public void run() {
try{
InputStream is = socket.getInputStream();
String path = "D:\\work_space\\Project01\\src\\demo18net\\fileupload\\";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path + System.currentTimeMillis() + ".mkv"));
int len = 0;
byte[] bytes = new byte[1024];
System.out.println(Thread.currentThread().getName() + "正在接收数据......");
long start = System.currentTimeMillis();
while((len = is.read(bytes)) != -1){
bos.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("上传用时: " + (end - start) + "ms.");
OutputStream os = socket.getOutputStream();
os.write("上传完成".getBytes());
bos.close();
socket.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
}
}