您的位置:首页 > 理论基础 > 计算机网络

黑马程序员--10.网络编程--07.【C_S编程中服务器端和客户端的读写流】【C_S常见的“双卡”现象和解决--TCP文本转换器示例】【TCP文本转换器客户端和服务器端循环结束分析】【网络流简化】

2013-08-29 18:39 771 查看

网络编程--7

C/S编程中服务器端和客户端的读写流

C/S常见的“双卡”现象和解决--TCP文本转换器示例

 TCP文本转换器客户端和服务器端循环情况结束分析

网络输出流部分的简化

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

1.    C/S编程中服务器端和客户端的读写流

1). 可能存在的所有的读写流

(1). 网络通信中客户端和服务器端的最全面的通信过程图示



[1]. 网络IO流

简称网络流,用于两台主机之间App进行通信

[2]. 本地IO流

简称本地流,用于本地主机App读写本地设备或者硬盘文件

注意】网络通信中网络流必须的,但是本地流未必必须

(2). 两台主机通信全过程 (全面)

一般是客户端服务端发起请求,所以客户端先做动作。

[1]. 客户端主机从自己的硬盘文件读取数据 -----本地输入 (客户端)【可选

[2]. 客户端主机将读取数据写入网络输出流上 -----网络输出 (客户端)
必须

-------------------------------------------------经过网络传输------------------------------------------------

[3]. 服务器端主机从网络输入流读取数据-----网络输入 (服务器端)
必须

[4]. 服务器端主机将读取到的数据写入硬盘文件 -----本地输出(服务器端)
可选

===从客户端的角度[1], [2], [3]和[4]共同构成了客户端向服务器端上传文件的过程===

[5]. 服务器端主机从本地硬盘读取硬盘文件 -----本地输入 (服务器端)
可选

[6]. 服务器端主机将读取数据写入网络输出流中 -----网络输出 (服务器端)
必须

-------------------------------------------------经过网络传输------------------------------------------------

[7]. 客户端主机从网络输入流读取发送来的数据-----网络输入 (客户端)
必须

[8]. 客户端主机将读取出来的数据写入硬盘文件 -----本地输出 (客户端)【可选

===从客户端的角度看[5], [6], [7]和[8]共同构成了客户端从服务器端下载文件的过程===

(3). 总结 (有来回的通信)

[1]. 网络通信中,客户端需要流的数量2~4个

{1}. 客户端最少需要2个网络流 (网络输入、输出流)

{2}. 客户端最多需要2个网络流 (网络输入、输出流) +2个本地流
(本地输入、输出流)

[2]. 网络通信中,服务器端需要流的数量2~4个

{1}. 服务器端最少需要2个网络流 (网络输入、输出流)

{2}. 服务器端最多需要2个网络流 (网络输入、输出流) +2个本地流
(本地输入、输出流)

2.    C/S常见的“双卡”现象和解决--TCP文本转换器示例

1). 需求和常规做法

(1). 需求

建立一个文本转换器客户端服务器端发送文本服务器会将文本转换成大写文本返回给客户端。当客户端输入“over”这个单词之后,客户端停止服务器发送文本

(2). 客户端程序分析

[1]. 从键盘输入数据(本地输入流)+两个网络流 ----->三个流对象

[2]. 分析各个流对象的写法

{1}. 本地输入流 -----

    文本?Y -->高效?Y --->设备:键盘(System.in)



BufferedReaderbufr =new BufferedReader(new InputStreamReader(System.in));


{2}. 网络输出流 -----目的

    文本?Y -->高效?Y --->设备:网络输出流(socket.getOutputStream())

BufferedWriterbufOut = new BufferedWriter(newOutputStreamWriter(socket.getOutputStream()));


{3}. 网络输入流 -----

    文本?Y -->高效?Y --->设备:网络输入流(socket.getInputStream())

BufferedReaderbufIn = new BufferedReader(newInputStreamReader(socket.getInputStream()));


[3]. 文本转换器客户端示例代码

class TransClientI{
public static void main(String[] args) throws Exception{
Socketsocket =new Socket("127.0.0.1", 10005);
//读入键盘数据
BufferedReaderbufr =new BufferedReader(new InputStreamReader(System.in));
//读入服务器发送回来数据
BufferedReaderbufIn =new BufferedReader(newInputStreamReader(socket.getInputStream()));
//向服务器发送数据
BufferedWriterbufOut =new BufferedWriter(newOutputStreamWriter(socket.getOutputStream()));

StringkeyBoardLine =null;
StringreceivedTransStr =null;

while((keyBoardLine =bufr.readLine()) !=null){
if("over".equals(keyBoardLine))
break;

//发送到服务器
bufOut.write(keyBoardLine);

//接收服务器转换回来的子串
receivedTransStr=bufIn.readLine();
System.out.println("Server response: "+ receivedTransStr);
}

bufr.close();
socket.close();
}
}


注意】由于本地流Socket无关,所以要单独进行关闭网络流随着Socket的关闭而关闭。因此,有bufr.close()和socket.close()这两个语句

(3). 服务器端程序分析

[1]. 两个网络流 -----> 两个流对象

[2]. 分析各个流对象的写法

{1}. 网络输入流 -----

    文本?Y -->高效?Y --->设备:网络输入流(socket.getInputStream())

BufferedReaderbufIn = new BufferedReader(newInputStreamReader(socket.getInputStream()));


{2}. 网络输出流 -----目的

    文本?Y -->高效?Y --->设备:网络输出流(socket.getOutputStream())

BufferedWriterbufOut = new BufferedWriter(newOutputStreamWriter(socket.getOutputStream()));


[3]. 文本转换器服务器端示例代码

class TransServerI{
public static void main(String[] args) throws Exception{
ServerSocketserverSocket =new ServerSocket(10005);
SocketclientSocket =serverSocket.accept();

Stringip = clientSocket.getInetAddress().getHostAddress();
System.out.println(ip +"...connected!");

BufferedReaderbufIn =new BufferedReader(newInputStreamReader(clientSocket.getInputStream()));

BufferedWriterbufOut =new BufferedWriter(newOutputStreamWriter(clientSocket.getOutputStream()));

//接受客户端的数据
StringreceivedFromClientStr =null;
while((receivedFromClientStr=bufIn.readLine()) !=null){
System.out.println("The message from the clientis :"+receivedFromClientStr);
//转换成大写
bufOut.write(receivedFromClientStr.toUpperCase());
}

clientSocket.close();
serverSocket.close();
}
}


(4). 运行上述程序

[1]. 效果



双方都卡在那里,都阻塞不动了。

[2]. 问题描述

{1}. 在客户端输入Benjamin之后一回车之后,服务器没有收到数据。

{2}. 想再次在客户端输入其他字符,客户端无法继续输入。

2). 出现的问题、原因和修正I

(1). 分析I

[1]. 通过程序分析问题



[2]. 客户端阻塞情况

{1}. 由于用户端先输入了文本“Benjamin”并敲入了回车(windows是\r\n),输入的文本是有效的行文本。因此这个readLine方法的阻塞状态被解除客户端的程序会一直向下运行到第二个阻塞点进行等待

{2}. 这次解除第二个阻塞点的条件就是必须服务器端收到null或者有效的行文本

[3]. 服务器端阻塞情况

{1}. 由于已经打印出客户端连接到服务器端,因此服务器端的第一个阻塞点accept()方法的阻塞情况被解除。

{2}. 因此服务器端的主线程向下运行第二个阻塞点readLine()。由于客户端发送来的信息没有打印出来,因此断定此时服务器端的程序阻塞在第二个阻塞点上。要想解除这个端点的阻塞,就需要客户端有行文本数据发送过来或者网络输入流达到了结尾(返回了null)

{3}. 现在的情况就是客户端发送的文本“Benjamin”数据没有发送到服务器端。所以此时服务器端的阻塞情况就不能被解决。客户端数据没有发送到服务器端的原因就是客户端使用的是缓冲区,但是没有使用flush方法数据缓冲区发送到网络输出流,所以数据可能因为这个没有发送到服务器端。

(2). 修正I

为服务器端和客户端向网络输出流中写入数据之后,共同都调用flush方法,以确保双方都能将数据从缓冲区写入到网络输出流中。

[1]. 客户端程序修正

while((keyBoardLine =bufr.readLine()) !=null){
if("over".equals(keyBoardLine))
break;

//发送到服务器
bufOut.write(keyBoardLine);
bufOut.flush();

//接收服务器转换回来的子串
receivedTransStr=bufIn.readLine();
System.out.println("Server response: "+ receivedTransStr);
}


[2]. 服务器端程序修正

while((receivedFromClientStr=bufIn.readLine()) !=null){
System.out.println("The message from the clientis :"+receivedFromClientStr);
//转换成大写
bufOut.write(receivedFromClientStr.toUpperCase());
bufOut.flush();
}

(3). 运行修正代码I



出现的效果仍然和上一次是一样的。客户端和服务器端全部卡在那不动

3). 出现的问题、原因和修正II

(1). 分析II

[1]. 通过程序分析问题


[2].服务器端阻塞情况

{1}. 客户端发送的Benjamin\r\n字符串被客户端的第一个readLine读取到之后,发现了有效的行结束符。此时客户端的readLine可以运行结束,并返回没有行结束符的字符串Benjamin。此时,客户端就把这个Benjamin字符串发送到了服务器端。

{2}.但是这个字符串没有行结束符。服务器端的readLine接收到了这个Benjamin之后,由于不是有效的行文本数据,服务器端的readLine方法就会一直等待行结束符的到来,但是这个行结束符被客户端的readLine方法去掉了。所以服务器端的readLine方法永远等不到有效的行结束符被传送过来。

{3}. 因此虽然flush方法将客户端的数据发送过来,但是由于不是有效的行本文数据,服务器端的readLine方法不能解除阻塞。

因此最后仍然是客户端和服务器端的程序被卡在那里,都不能动。

(2). 修正II

为服务器端和客户端向网络输出流中写入数据之后并在调用flush方法之前都向网络流中写入换行符以确保双方的readLine方法能够读到有效的行数据而正确解除阻塞状态。

[1]. 客户端程序修正

while((keyBoardLine =bufr.readLine()) !=null){
if("over".equals(keyBoardLine))
break;

//发送到服务器
bufOut.write(keyBoardLine);
bufOut.newLine();
bufOut.flush();

//接收服务器转换回来的子串
receivedTransStr=bufIn.readLine();
System.out.println("Server response: "+ receivedTransStr);
}


[2]. 服务器端程序修正

while((receivedFromClientStr=bufIn.readLine()) !=null){
System.out.println("The message from the clientis :"+receivedFromClientStr);
//转换成大写
bufOut.write(receivedFromClientStr.toUpperCase());
bufOut.newLine();
bufOut.flush();
}


(3). 运行修正代码II


程序成功运行。

3.    TCP文本转换器客户端和服务器端循环情况结束分析

1). 客户端while循环结束的分析

(1). 从键盘读取数据的循环判定条件存在的问题

{1}. 判断条件是:while((keyBoardLine=bufr.readLine()) !=null)

{2}. 注意这个readLine是读取用户从键盘上输入的数据。但是用户不能输入null这个值【注意字符串"null"不是null】。这样,因此实际上这个循环的自身判定条件没有办法自行结束的。所以实际上在while内部没有其他跳出循环的措施,这就是一个死循环

(2). 防止客户端的键盘输入程序死循环

自定义结束命令,当用户输入这个自定义结束字符串的时候,就结束循环。这个例子中就是使用"over"作为自定义结束符来结束客户端的键盘循环程序。

2). 服务器端while循环结束的分析

(1). 客户端的while正常结束是服务器端while循环结束的前提

[1]. 当用户输入自定义结束标记的时候,客户端的while循环顺利结束。之后客户端的本地流被关闭。之后客户端的Socket被关闭

[2]. 客户端的Socket被关闭,注意此时和客户端有关的网络流也被关闭!!!这是重点。这样客户端的输入流中就被注入了流的结束标记

(2). 服务器端while循环结束分析

[1]. 服务器端while循环判断条件

while((receivedFromClientStr =bufIn.readLine()) !=null)

[2]. 由于服务器的bufIn获取数据的源是网络流。所以只要保证客户端的程序能正确关闭Socket使得流的结束标记被注入网络流来标记网络流已经达到末尾,服务器端的bufIn.readLine()是调用了read方法,这样readLine内部的read方法返回-1,此时readLine就会映射地返回null。因此这个服务器端的循环是可以正常结束的

4.    网络输出流部分的简化

1). 使用PrintWriter替代BufferedWriter

(1). PrintWriter优势

[1]. PrintWriter既能接收字符数据,又能接收字节数据

[2]. PrintWriter可以在创建对象的时候传入一个boolean的参数保证每次写入数据之后自动flush

[3]. PrintWriter有著名的println方法,可以自动在发送的数据之后加上换行符。

(2). 在服务端和客户端中定义BufferedWriter的地方换成PrintWriter

将BufferedWriter替换成PrintWriter

BufferedWriter bufOut =new BufferedWriter(newOutputStreamWriter(socket.getOutputStream())); 


PrintWriter bufOut =newPrintWriter(socket.getOutputStream(), true);


(3). 代码的简化

[1]. 客户端



[2]. 服务器端



(4). 简化后代码示例

[1]. 文本转换器最终客户端代码

/*
需求:建立一个文本转换服务器
客户端给Server端发送文本,服务器会将文本转换成大写文本并返回客户端
而且服务器可以不断地进行文本的转换。当客户端发送over时,转换就结束
*/

import java.net.*;
import java.io.*;

class TransClient{
public static void main(String[] args) throws Exception{
Socketsocket =new Socket("127.0.0.1", 10005);
//读入键盘数据
BufferedReaderbufr =new BufferedReader(new InputStreamReader(System.in));
//读入服务器发送回来数据
BufferedReaderbufIn =new BufferedReader(newInputStreamReader(socket.getInputStream()));
//向服务器发送数据
PrintWriterbufOut =newPrintWriter(socket.getOutputStream(), true);

StringkeyBoardLine =null;
StringreceivedTransStr =null;

while((keyBoardLine =bufr.readLine()) !=null){
if("over".equals(keyBoardLine))
break;

//发送到服务器
//PrintWriter的println方法简化出来 仅仅使用一条语句来替代
bufOut.println(keyBoardLine);

//接收服务器转换回来的子串
receivedTransStr=bufIn.readLine();
System.out.println("Server response: "+ receivedTransStr);
}

bufr.close();
socket.close();
}
}


[2]. 文本转换器最终服务端代码
class TransServer{
public static void main(String[] args) throws Exception{
ServerSocketserverSocket =new ServerSocket(10005);
SocketclientSocket =serverSocket.accept();

Stringip = clientSocket.getInetAddress().getHostAddress();
System.out.println(ip +"...connected!");

BufferedReaderbufIn =new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

//使用PrintWriter来简化代码
PrintWriterbufOut =newPrintWriter(clientSocket.getOutputStream(), true);

//接受客户端的数据
StringreceivedFromClientStr =null;
while((receivedFromClientStr=bufIn.readLine()) !=null){
System.out.println("The message from the clientis :"+receivedFromClientStr);
//转换成大写
//简化代码
bufOut.println(receivedFromClientStr.toUpperCase());
}

clientSocket.close();
serverSocket.close();
}
}


[3]. 运行结果



注意这种客户端服务器相互通信的程序主要研究

{1}. 阻塞式方法何时解除阻塞

{2}. 如果遇见循环,要分析循环能不能自动执行完成。如果执行完成,什么时候是执行完成的时刻。

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------
 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐