网络编程
6.TCP
len = is.read(buffer)) != -1的原理详解
客户端
- 连接服务器Socket
- 发送消息
服务器
- 建立服务的端口 ServerSocket
- 等待用户的链接 accept
- 接收用的消息
实现聊天
TcpClientDemo01
//客户端
public class TcpClientDemo01 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
//1.要知道服务器的地址、端口号
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
int port = 9999;
//2.创建一个socket链接
socket = new Socket(serverIP,port);
//3.发送消息IO流
os = socket.getOutputStream();
os.write("你好".getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TcpServerDemo01
//服务端
public class TcpServerDemo01 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.我得有一个地址
serverSocket = new ServerSocket(9999);
//没有while只监听一次,有while(true)一直监听
while (true) {
//2.等待客户端链接过来
//此处socket等于Client中的socket,已经建立连接后就是同一个
socket = serverSocket.accept();
//3.读取客户端信息
is = socket.getInputStream();
//管道流
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
}
/*
byte[] buffer = new byte[1024];
int len;
//样板代码,此处read直接读到buffer最后,len直接为数组长度,如果用if输出是一样的结果
while ((len=is.read(buffer))!=1){
String msg = new String(buffer,0,len);
System.out.println(msg);
}
*/
} catch (IOException e) {
e.printStackTrace();
}finally{
//关闭资源
if(baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果(运行两次Client):
你好
你好
注释掉的代码:如果字节流断了会变成乱码,真正采用的是管道流。
缓冲流、管道流
此处用的是装饰者模式,目的是在不创造更多子类的情况下,将对象的功能加以扩展。此处就是调用toString()对象。
文件上传
TestClientDemo02
public class TcpClientDemo02 {
public static void main(String[] args) throws IOException {
//1. 创建一个socket链接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
//2. 创建一个输出流
OutputStream os = socket.getOutputStream();
//3. 读取文件
FileInputStream fis = new FileInputStream("2_1.jpg");
//4. 写出文件
byte[] buffer = new byte[1024];
int len;
while ((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//通知服务器,我已经结束了
socket.shutdownOutput();//我已经传输完了!
//确定服务器接收完毕才断开连接
InputStream inputStream = socket.getInputStream();
//String byte[]
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while((len2=inputStream.read(buffer2))!=-1){
baos.write(buffer2,0,len2);
}
System.out.println(baos.toString());
//5.关闭资源
fis.close();
os.close();
socket.close();
}
}
TestServerDemo02
public class TcpServerDemo02 {
public static void main(String[] args) throws IOException {
//1.创建服务(端口
ServerSocket serverSocket = new ServerSocket(9000);
//2.监听客户端链接
Socket socket = serverSocket.accept();//阻塞式监听,会一直等待客户端链接
//3.获取输入流
InputStream is = socket.getInputStream();
//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("receive.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//通知客户端我接收完毕了
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我接收完毕了,你可以断开了".getBytes());
//关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
在TestClientDemo02中,需要在中间加入socket.shutdownOutput();
因为上面的代码在往服务器端进行输出,而服务器端并不知道有没有接受完输入,所以不会告诉客户端接收完毕,而客户端又在等待服务器端告诉客户端已经接收完毕,所以代码不会继续运行。而Demo01里双方都是直接关闭流和链接,所以不用shutdown也会结束。