day15 网络编程
网络编程的作用:实现跨主机跨进程的通信
网络编程概述
网络编程三要素:IP地址,端口号和传输协议
IP+Port = 唯一进程
java进程之间通信的过程主要看传输层的传输过程,底层已经被实现了
且传输层传输的是字节数据:
udp协议和tcp协议在java语言中的传送过程:
network编程实现
ip地址: 在java语言中一个ip地址,对应的是一个对象InetAddress对象
static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
主机名可以是机器名(如 "java.sun.com"),也可以是其 IP 地址的文本表示形式
拿到ip地址的方法(在jdk文档中没有说IP地址的构造方法,所以只能通过一些方法获取IP地址的返回值),以下是几种方法:
基于UDP来实现的聊天
版本一:传输一个字符串常量
public class Demo1 {
public static void main(String[] args) throws UnknownHostException {
InetAddress ipByLocalHost = InetAddress.getByName("localhost");
// 127.0.0.1
System.out.println(ipByLocalHost);
// 根据计算机名称
InetAddress shine = InetAddress.getByName("shine");
System.out.println(shine);
// 根据ip地址文本表示形式
InetAddress byIpText = InetAddress.getByName("192.168.4.119");
System.out.println(byIpText);
// 根据ip地址文本表示形式
InetAddress byLoopIp = InetAddress.getByName("127.0.0.1");
System.out.println(byLoopIp);
}
}
发送端:
实现基于UDP协议的发送端代码
1.建立udp的socket对象
DatagramSocket: 此类表示用来发送和接收数据报包的套接字。
// 套接字: ip + port
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。
2.将要发送的数据封装成数据包
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
length 参数必须小于等于 buf.length。
参数:
buf - 包数据。
offset - 包数据偏移量。
length - 包数据长度。
address - 目的地址。
port - 目的端口号。
3.通过udp的socket对象,将数据包发送出
public void send(DatagramPacket p)
1)从此套接字发送数据报包。
2)DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
4. 释放资源
udp是一种无连接的不可靠的协议:
不关心数据有没有发送成功,只关心将数据发出去就可以了
类似于发短信
发送端的代码:(传输一个字符串)
public class Sender {
public static void main(String[] args) throws IOException {
// 应用层
String data = "hello, udp";
//-----------------------------------------------------------------
// 传输层
// DatagramSocket(int port)
DatagramSocket datagramSocket = new DatagramSocket(10086);
// 将待发送的数据封装到数据报包
// DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
byte[] dataBytes = data.getBytes();
int offset = 0;
int len = dataBytes.length;
InetAddress targetIp = InetAddress.getByName("127.0.0.1");
int port = 10087;
// 待发送数据的数据报包
DatagramPacket datagramPacket = new DatagramPacket(dataBytes, offset, len, targetIp, port);
// 发送数据报包
datagramSocket.send(datagramPacket);
// 关闭Socket释放资源
datagramSocket.close();
}
}
接收端注意:
接收端:
1.建立udp的socket对象.
DatagramSocket: 此类表示用来发送和接收数据报包的套接字。
// 套接字: ip + port
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。
2.创建用于接收数据的数据报包
DatagramPacket(byte[] buf, int offset, int length)
构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
参数:
buf - 保存传入数据报的缓冲区。
offset - 缓冲区的偏移量
length - 读取的字节数。
3.通过socket对象的receive方法接收数据
public void receive(DatagramPacket p)
1)从此套接字接收数据报包。
2)当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据
3)此方法在接收到数据报前一直阻塞
4)数据报包也包含发送方的IP地址和发送方机器的端口号
4.可以对资源进行释放
通过数据包对象的功能来完成对接收到数据进行解析.
接收端代码:
public class Receiver {
public static void main(String[] args) throws IOException {
// 传输层(接收端先写传输层,再写应用层)
DatagramSocket datagramSocket = new DatagramSocket(10087);
//创建用于接收数据的数据报包
// DatagramPacket(byte[] buf, int offset, int length)
byte[] byteBuf = new byte[1024];
int offset = 0;
int len = byteBuf.length;
DatagramPacket datagramPacket = new DatagramPacket(byteBuf, offset, len);
// 接收数据
datagramSocket.receive(datagramPacket);
// 关闭套接字对象,释放系统资源
datagramSocket.close();
// -----------------------------------------
// 应用层
// 从数据报包中,获取存放了接收到的数据的字节数组缓冲区
byte[] data = datagramPacket.getData();
// 字节数组缓冲区中,数据填充的起始位置
int off = datagramPacket.getOffset();
// 真正读取到的字节个数
int length = datagramPacket.getLength();
// 解析
String s = new String(data, off, len);
System.out.println(s);
}
}
运行的结果:
接收端收到了发送端发出的代码
接收端的注意事项
注意事项:
1. DatagramSocket的receive方法是一个阻塞方法
2. 如果让两个相同的有相同端口的进程同时运行,会报错:
BindException:Address already in use:Sannot bind
这里的address指的是套接字地址:ip + port
一个端口号就是一个进程的逻辑地址,一个端口号只能绑定一个进程
但是,一个进程可以绑定多个端口号
从接收端接收发送方的IP地址:
//获得发送方的IP地址:
InetAddress address = datagramPacket.getAddress();
//获得发送方的端口号
int port = datagramPacket.getPort();
//如果在接收端最后加上一句:
System.out.println("ip/port:" + address + "/" + port + "---" + s);
就会得到结果:
版本二:发送键盘录入的字符串
在版本一的基础上,首先是录入单行的字符串,写一份发送端与接收端的代码:
只要new一个BufferedReader对象,读入一行数据即可:
发送端的代码:
public class Sender {
public static void main(String[] args) throws IOException {
// 1024 - 65535
// 创建套接字对象
DatagramSocket datagramSocket = new DatagramSocket(9999);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
// 读取一行数据,发送一行数据
sendData(datagramSocket, line);
byte[] bytes = line.getBytes();
InetAddress targetIp = InetAddress.getByName("localhost");
int port = 9998;
// 创建用于发送数据的数据报包,并封装待发送的数据
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);
// 发送数据报包
datagramSocket.send(sendPacket);
// 关闭套接字对象
datagramSocket.close();
}
}
接收端代码:
public class Receiver {
public static boolean flag = true;
public static void main(String[] args) throws IOException {
// 传输层
DatagramSocket datagramSocket = new DatagramSocket(9998);
// 准备好用于接收数据的数据报包
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
// 接收别人发送的数据
String s = receiveData(datagramSocket, receivePacket);
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int offset = receivePacket.getOffset();
int length = receivePacket.getLength();
// 解析
String s = new String(data, offset, length);
System.out.println(s);
return s;
// 关闭套接字对象
datagramSocket.close();
}
}
但是上述思路只能发送1行信息,无法连续发送信息,为了实现连续发送信息,继续对代码进行改进:
- 这次改进不再区分传输层和应用层
- 将传输层的套接字代码都封装成一个private方法
- 在接收端,为了骗过编译器,设置一个flag,用while循环将这个套接字的方法放在方法体中,并将其返回值改为String(改不改都可以)
发送端的代码
public class Sender {
public static void main(String[] args) throws IOException {
// 1024 - 65535
// 创建套接字对象
DatagramSocket datagramSocket = new DatagramSocket(9999);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
// 读取一行数据,发送一行数据
sendData(datagramSocket, line);
}
// 关闭套接字对象
datagramSocket.close();
}
private static void sendData(DatagramSocket datagramSocket, String line) throws IOException {
byte[] bytes = line.getBytes();
InetAddress targetIp = InetAddress.getByName("localhost");
int port = 9998;
// 创建用于发送数据的数据报包,并封装待发送的数据
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);
// 发送数据报包
datagramSocket.send(sendPacket);
}
}
接收端代码:
public class Receiver {
public static boolean flag = true;
public static void main(String[] args) throws IOException {
// 传输层
DatagramSocket datagramSocket = new DatagramSocket(9998);
// 准备好用于接收数据的数据报包
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
while (flag) {
// 接收别人发送的数据
String s = receiveData(datagramSocket, receivePacket);
}
// 关闭套接字对象
datagramSocket.close();
}
private static String receiveData(DatagramSocket datagramSocket, DatagramPacket receivePacket) throws IOException {
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int offset = receivePacket.getOffset();
int length = receivePacket.getLength();
// 解析
String s = new String(data, offset, length);
System.out.println(s);
return s;
}
}
版本三:模拟两个人的聊天过程
这个版本只能实现发送端发消息,接收端接收消息后就发送回去这个方法才能正常执行
这个版本与上一个版本的区别:
在接收端/发送端的while循环中放入了发送/接收的方法:
发送端代码:
public class Sender {
public static void main(String[] args) throws IOException {
// 1024 - 65535
// 创建套接字对象
DatagramSocket datagramSocket = new DatagramSocket(9999);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
// 读取一行数据,发送一行数据
sendData(datagramSocket, line);
// 接收接收端发送给我的数据
String s = receiveData(datagramSocket);
if ("886".equals(s)) {
break;
}
}
// 关闭套接字对象
datagramSocket.close();
}
private static String receiveData(DatagramSocket datagramSocket) throws IOException {
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
// 阻塞方法,当没有数据的时候,会阻塞等待
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int length = receivePacket.getLength();
int offset = receivePacket.getOffset();
String s = new String(data, offset, length);
System.out.println(s);
return s;
}
private static void sendData(DatagramSocket datagramSocket, String line) throws IOException {
byte[] bytes = line.getBytes();
InetAddress targetIp = InetAddress.getByName("localhost");
int port = 9998;
// 创建用于发送数据的数据报包,并封装待发送的数据
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, port);
// 发送数据报包
datagramSocket.send(sendPacket);
}
}
接收端代码:
public class Receiver {
public static boolean flag = true;
public static void main(String[] args) throws IOException {
// 传输层
DatagramSocket datagramSocket = new DatagramSocket(9998);
// 准备好用于接收数据的数据报包
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
while (flag) {
// 接收别人发送的数据
String s = receiveData(datagramSocket, receivePacket);
sendResponse(datagramSocket, bufferedReader);
// 依赖自定义协议
if ("886".equals(s)) {
break;
}
}
// 关闭套接字对象
datagramSocket.close();
}
private static void sendResponse(DatagramSocket datagramSocket, BufferedReader bufferedReader) throws IOException {
String sendData = bufferedReader.readLine();
byte[] dataBytes = sendData.getBytes();
InetAddress targetIp = InetAddress.getByName("127.0.0.1");
int port = 9999;
DatagramPacket sendPacket = new DatagramPacket(dataBytes, 0, dataBytes.length, targetIp, port);
datagramSocket.send(sendPacket);
}
private static String receiveData(DatagramSocket datagramSocket, DatagramPacket receivePacket) throws IOException {
// 阻塞方法
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int offset = receivePacket.getOffset();
int length = receivePacket.getLength();
// 解析
String s = new String(data, offset, length);
System.out.println(s);
return s;
}
}
这里遇见的问题是读入一行数据,被receive方法给阻塞了,所以只能接一句,发一句
运行结果:
receive线程中没有收到最后一句话
版本四:同时接送,同时发送,且可以下线(引入多线程)
两个线程:
- SenderTask
- ReceiveTask
- 主线程OnePerson
建立两个package,在两个包内分别放入3个线程
说明:当两个都人发送886后,两个进程终止了
线程的模型:
这里有问题:当anotherperson中的senderTask先发送886,,ReceiverTask关闭了onePerson中的套接字对象,而onePerson中的SenderTask后发送886,这个时候就会将异常向上报告,所以,何时才能正确的关闭DatagarmSocket对象?(分析)
以oneperson里的class对象来看:
OnePerson对象中的代码:
public class OnePerson {
// 该变量作为SenderTask是否发出了886的标志位
public static boolean senderOffline = false;//修改的部分
// 创建一个锁对象
public static Object lockObj = new Object();//修改的部分
public static void main(String[] args) throws SocketException, UnknownHostException {
DatagramSocket datagramSocket = new DatagramSocket(8080);
// 发生任务,接收任务
String ip = "127.0.0.1";
int port = 9876;
SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
ReceiverTask receiverTask = new ReceiverTask(datagramSocket);
Thread senderThread = new Thread(senderTask);
Thread receiveThread = new Thread(receiverTask);
// 启动发送接收线程
senderThread.start();
receiveThread.start();
}
}
在ReceiverTask中:
public class ReceiverTask implements Runnable{
private DatagramSocket datagramSocket;//修改的部分
public ReceiverTask(DatagramSocket datagramSocket) {
this.datagramSocket = datagramSocket;
}
@Override
public void run() {
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
boolean flag = true;
try {
while (flag) {
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int offset = receivePacket.getOffset();
int length = receivePacket.getLength();
String s = new String(data, offset, length);
System.out.println(s);
if ("886".equals(s)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 接收到了886,才会执行finally代码块
// 关闭套接字
while (true) {
synchronized (OnePerson.lockObj) {//修改的部分
if (OnePerson.senderOffline ) {
// 发送线程发出了886
break;
}
}
// 等待一小段时间
try {
Thread.sleep(50);//如果不这样做的话,CPU会一直在while循环中,这样会使得CPU打满,所以我么休眠一段时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
datagramSocket.close();//关闭套接字进程都在receiverTask中关闭
}
}
}
在SendTask线程中:
public class SenderTask implements Runnable {
// 用于发送数据套接字对象
private DatagramSocket datagramSocket;
private InetAddress targetIp;
int targetPort;
public SenderTask(DatagramSocket datagramSocket, String targetIp, int targetPort) throws UnknownHostException {
this.datagramSocket = datagramSocket;
this.targetIp = InetAddress.getByName(targetIp);
this.targetPort = targetPort;
}
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
try {
while ((line = br.readLine()) != null) {
byte[] bytes = line.getBytes();
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
datagramSocket.send(sendPacket);
if ("886".equals(line)) {
synchronized (OnePerson.lockObj) {//修改的部分!!!
OnePerson.senderOffline = true;
}
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
anotherperson的版本中,只有主线程的端口号的设置不一样:
public class AnotherPerson {
public static boolean isSenderOffline = false;
public static Object lockObj = new Object();
public static void main(String[] args) throws SocketException, UnknownHostException {
DatagramSocket datagramSocket = new DatagramSocket(9876);
// 发生任务,接收任务
String ip = "127.0.0.1";
int port = 8080;
SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
ReceiverTask receiverTask = new ReceiverTask(datagramSocket);
Thread senderThread = new Thread(senderTask);
Thread receiveThread = new Thread(receiverTask);
// 启动发送接收线程
senderThread.start();
receiveThread.start();
}
}
版本五:模拟聊天室程序
一个人发消息,其他人都能看得到
- 只要将ip地址改为广播地址即可(查询方式:在cmd窗口中,输入ipconfig命令查询一下本子链接的IPv4地址即可)(加入ip地址为:192.168.4.199,则广播地址为:192.168.4.255)
- 将端口号改为所有人共同绑定的端口号,即发送端口号和接收端口号为同一个端口号
- 但是有一个弊端:即所有人都不能停止运行进程,即不能发了886以后就进程终止,这样,所有的人都不能再发送信息了:
public class ChatRoom {
// 该变量作为SenderTask是否发出了886的标志位
public static boolean senderOffline = false;
// 创建一个锁对象
public static Object lockObj = new Object();
public static void main(String[] args) throws SocketException, UnknownHostException {
DatagramSocket datagramSocket = new DatagramSocket(8080);
// 发生任务,接收任务
// ip地址改为广播地址
String ip = "192.168.4.255";
int port = 8080;
SenderTask senderTask = new SenderTask(datagramSocket, ip, port);
ReceiverTask receiverTask = new ReceiverTask(datagramSocket);
Thread senderThread = new Thread(senderTask);
Thread receiveThread = new Thread(receiverTask);
// 启动发送接收线程
senderThread.start();
receiveThread.start();
}
}
public class ReceiverTask implements Runnable{
private DatagramSocket datagramSocket;
public ReceiverTask(DatagramSocket datagramSocket) {
this.datagramSocket = datagramSocket;
}
@Override
public void run() {
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, 0, bytes.length);
boolean flag = true;
try {
while (flag) {
datagramSocket.receive(receivePacket);
byte[] data = receivePacket.getData();
int offset = receivePacket.getOffset();
int length = receivePacket.getLength();
String s = new String(data, offset, length);
InetAddress address = receivePacket.getAddress();
int port = receivePacket.getPort();
System.out.println(address + ":"+ port +"---" + s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class SenderTask implements Runnable {
// 用于发送数据套接字对象
private DatagramSocket datagramSocket;
private InetAddress targetIp;
int targetPort;
public SenderTask(DatagramSocket datagramSocket, String targetIp, int targetPort) throws UnknownHostException {
this.datagramSocket = datagramSocket;
this.targetIp = InetAddress.getByName(targetIp);
this.targetPort = targetPort;
}
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
try {
while ((line = br.readLine()) != null) {
byte[] bytes = line.getBytes();
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
datagramSocket.send(sendPacket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP协议传送数据
TCP和UDP协议的区别:TCP在传送数据前要先建立可靠链接
接收数据和发送数据的过程(在服务器端和客户端):
实现客户端向服务器端发数据
客户端的注意事项和代码实现
1. 建立客户端的Socket服务,并明确要连接的服务器。
Socket: 此类实现客户端套接字
Socket(String host, int port)(jdk中的一种构造方式)
创建一个流套接字并将其 连接到 指定主机上 的指定端口号
a. 这里的host, port,要连接的服务器端的ip地址和端口号(这里和udp的不同)
b. 创建出的Socket对象本身, 默认绑定本机ip ,绑定一个随机端口
参数:
host - 主机名,或者为 null,表示回送地址。
port - 端口号。
2.如果对象建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
3.根据需要从socket对象中获取输入,或输出流
4.向流中读取或写入数据
5.释放资源
注意事项:
虽然我们可以从Socket中获取流对象,但是Socket中流对象,Socket自己会负责关闭
实现代码:
public class Client {
public static void main(String[] args) throws IOException {
// 1. 创建Socket对象(在创建对象的时候就直接向目标地址发起连接请求)
Socket socket = new Socket("127.0.0.1", 9876);
// 2. 只要Socket对象创建成功,我们客户端就可以认为连接建立好了,获取输出流发送数据
OutputStream out = socket.getOutputStream();
//3. 利用流发送数据
out.write("hello,tcp".getBytes());
//4. 关闭套接字
socket.close();
}
}
服务器端的注意事项和实现代码:
服务器端:
1.创建Serversocket对象,在指定端口,监听客户端连接请求
ServerSocket: 此类实现服务器套接字
ServerSocket(int port)
创建绑定到特定端口的服务器套接字。
服务器套接字等待请求通过网络传入
2.收到客户端连接请求后,建立Socket连接
public Socket accept()
1. 侦听并接受到此套接字的连接。
2. 此方法在连接传入之前一直 阻塞 。
3.如果连接建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
从socket中根据需要获取输入,或输出流
4.根据需要向流中写入数据或从流中读数据
5.释放资源
注意事项:
1. accept 是一个阻塞方法
2. BindException: Address already in use, 一个端口号只能绑定一个进程
实现代码:
public class Server {
public static void main(String[] args) throws IOException {
// 创建服务器端的套接字对象
ServerSocket serverSocket = new ServerSocket(9876);
// 通过accept方法,真正接收处理连接请求,建立连接
Socket socket = serverSocket.accept();
// 从服务器端建立连接的Socket对象上,获取流对象,进行数据传输
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
String s = new String(bytes, 0, len);
System.out.println(s);
// 关闭Socket
socket.close();
serverSocket.close();
}
}
要实现从服务器端向客户端发送数据:
即,将输入输出流反转一下即可
练习:
客户端文本文件,服务器端输出文本文件
示意图:
客户端:
/*
public void shutdownOutput()
禁用此套接字的输出流。
对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
这个方法能够实现关闭客户端的输出流,从而使得服务器端的进程能够不被read方法阻塞,向客户端输出"文件上传完毕"的字样
*/
public class Client {
public static void main(String[] args) throws IOException {
// System.out.println(System.getProperty("user.dir"));
//
// 1. 创建客户端Socket对象
Socket socket = new Socket("127.0.0.1", 10086);
// 2. 准备待发送的数据(文件)
BufferedReader br = new BufferedReader(new FileReader("Demo1.java"));
OutputStream out = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
// 给服务器端发送文件内容
String line;
while ((line = br.readLine()) != null) {
// 读取一行,发送一行
bw.write(line);
bw.newLine();
bw.flush();//注意这里要刷新缓冲区
// 向客户端返回文件传输结束的方法1:finish约定为文件结束的标志(不完美)
// if ("finish".equals(line)) {
// break;
// }
}
// 方法2:关闭客户端的输出流(完美的解决方案)
socket.shutdownOutput();
// 接收服务器端发送的反馈消息
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
// Socket的输入流的read方法,是阻塞方法!!!!
int len = in.read(bytes);
String s = new String(bytes, 0, len);
System.out.println(s);
// 关闭套接字
br.close();
socket.close();
}
}
服务器端:
public class Server {
public static void main(String[] args) throws IOException {
// 创建服务器端套接字对象
ServerSocket serverSocket = new ServerSocket(10086);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
FileWriter fileWriter = new FileWriter("copy.java");
String line;
// Socket的InputStream的read,阻塞方法!!!在这里就是br.readLine()
while ((line = br.readLine()) != null) {
// 文件结束标志,在文件的末尾加一个finish
// if ("finish".equals(line)) {
// break;
// }
fileWriter.write(line);
fileWriter.write(System.lineSeparator());
fileWriter.flush();
}
// 发送文件上传完毕的消息
OutputStream out = socket.getOutputStream();
out.write("文件上传完毕".getBytes());
fileWriter.close();
socket.close();
serverSocket.close();
}
}