-------
android培训、
java培训、期待与您交流! ----------
务器(程序)。由 IP 表达的每台机器也包含了“端口”。我们设置一个客户机或者服务器的时候,
必须选择一个无论客户机还是服务器都认可连接的端口。就象我们去拜会某人时,IP 地址是他居住的房子,
而端口是他在的那个房间。
ServerSocket serverSocket=new ServerSocket(80);
如果运行时无法绑定到80端口,以上代码会抛出IOException,更确切地说,是抛出BindException,它是IOException的子类。BindException一般是由以下原因造成的:
端口已经被其他服务器进程占用;
在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到1~1023之间的端口。如果把参数port设为0,表示由操作系统来为服务器分配一个任意可用的端口。由操作系统分配的端口也称为匿名端口。对于多数服务器,会使用明确的端口,而不会使用匿名端口,因为客户程序需要事先知道服务器的端口,才能方便地访问服务器。在某些场合,匿名端口有着特殊的用途,本章3.4节会对此作介绍。
说明:
端口是用一个16位的数字来表示,它的范围是0~65535,1024以下的端口号保留给了系统的服务,例如:80端口访问网页,25端口用来邮件发送,oralce92默认的端口号是1532,tomacat默认的端口号是8080.
使用线程池的一个严重风险是线程泄漏。对于工作线程数目固定的线程池,如果工作线程在执行任务时抛出RuntimeException 或Error,并且这些异常或错误没有被捕获,那么这个工作线程就会异常终止,使得线程池永久失去了一个工作线程。如果所有的工作线程都异常终止,线程池就最终变为空,没有任何可用的工作线程来处理任务。
导致线程泄漏的另一种情形是,工作线程在执行一个任务时被阻塞,如等待用户的输入数据,但是由于用户一直不输入数据(可能是因为用户走开了),导致这个工作线程一直被阻塞。这样的工作线程名存实亡,它实际上不执行任何任务了。假如线程池中所有的工作线程都处于这样的阻塞状态,那么线程池就无法处理新加入的任务了。
当工作队列中有大量排队等候执行的任务时,这些任务本身可能会消耗太多的系统资源而引起系统资源缺乏。线程池可能会带来种种风险,为了尽可能避免它们,使用线程池时需要遵循以下原则。
(1)如果任务A在执行过程中需要同步等待任务B的执行结果,那么任务A不适合加入到线程池的工作队列中。如果把像任务A一样的需要等待其他任务执行结果的任务加入到工作队中,可能会导致线程池的死锁。
(2)如果执行某个任务时可能会阻塞,并且是长时间的阻塞,则应该设定超时时间,避免工作线程永久的阻塞下去而导致线程泄漏。在服务器程序中,当线程等待客户连接,或者等
待客户发送的数据时,都可能会阻塞。可以通过以下方式设定超时时间:
了解任务的特点,分析任务是执行经常会阻塞的I/O 操作,还是执行一直不会阻塞的运算操作。前者时断时续地占用CPU,而后者对CPU具有更高的利用率。预计完成任务大概需要多长时间?是短时间任务还是长时间任务?根据任务的特点,对任务进行分类,然后把不同类型的任务分别加入到不同线程池的工作队列中,这样可以根据任务的特点,分别调整每个线程池。
(4)调整线程池的大小。线程池的最佳大小主要取决于系统的可用CPU的数目,以及工作队列中任务的特点。假如在一个具有N 个CPU的系统上只有一个工作队列,并且其中全部是运算性质(不会阻塞)的任务,那么当线程池具有N 或N+1 个工作线程时,一般会获得最大的CPU 利用率。如果工作队列中包含会执行I/O操作并常常阻塞的任务,则要让线程池的大小超过可用CPU的数目,因为并不是所有工作线程都一直在工作。选择一个典型的任务,然后估计在执行这个任务的过程中,等待时间(WT)与实际占用CPU进行运算的时间(ST)之间的比例WT/ST。对于一个具有N个CPU的系统,需要设置大约N×(1+WT/ST)个线程来保证CPU得到充分利用。当然,CPU利用率不是调整线程池大小过程中唯一要考虑的事项。随着线程池中工作线程数目的增长,还会碰到内存或者其他系统资源的限制,如套接字、打开的文件句柄或数据库连接数目等。要保证多线程消耗的系统资源在系统的承载范围之内。
(5)避免任务过载。服务器应根据系统的承载能力,限制客户并发连接的数目。当客户并发连接的数目超过了限制值,服务器可以拒绝连接请求,并友好地告知客户:服务器正忙,
请稍后再试。
ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
接下来,服务器从Socket对象中获得输入流和输出流,就能与客户交换数据。当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException
的子类SocketException异常:java.net.SocketException: Connection reset by peer这只是服务器与单个客户通信中出现的异常,这种异常应该被捕获,使得服务器能继续与其
他客户通信。
以下程序显示了单线程服务器采用的通信流程:
与单个客户通信的代码放在一个try代码块中,如果遇到异常,该异常被catch代码块捕获。
try代码块后面还有一个finally代码块,它保证不管与客户通信正常结束还是异常结束,最
后都会关闭Socket,断开与这个客户的连接。
ServerSocket的close()方法使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行ServerSocket的close()方法,操作系统也会释放这个服务器占用的端口。因此,服务器程序并不一定要在结束之前执行ServerSocket的close()方法。在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式调用ServerSocket的close()方法。例如,以下代码用于扫描1~65535之间的端口号。如果ServerSocket成功创建,意味着该端口未被其他服务器进程绑定,否者说明该端口已经被其他进程占用:
for(int port=1;port<=65535;port++){
try{
ServerSocket serverSocket=new ServerSocket(port);
serverSocket.close(); //及时关闭ServerSocket
}catch(IOException e){
System.out.println("端口"+port+" 已经被其他服务器进程占用");
}
}
以上程序代码创建了一个ServerSocket对象后,就马上关闭它,以便及时释放它占用的端口,从而避免程序临时占用系统的大多数端口。
ServerSocket的isClosed()方法判断ServerSocket是否关闭,只有执行了ServerSocket的close()方法,isClosed()方法才返回true;否则,
即使ServerSocket还没有和特定端口绑定,isClosed()方法也会返回false。ServerSocket的isBound()方法判断ServerSocket是否已经与一个端口绑定,
只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()方法也会返回true。如果需要确定一个ServerSocket已经与特定端口绑定,并且还没有被关闭,则可以采用以
下方式:
boolean isOpen=serverSocket.isBound() && !serverSocket.isClosed();
ServerSocket的以下两个get方法可分别获得服务器绑定的IP地址,以及绑定的端口:
public InetAddress getInetAddress()
public int getLocalPort()
在构造ServerSocket时,如果把端口设为0,那么将由操作系统为服务器分配一个端口(称为匿名端口),程序只要调用getLocalPort()方法就能获知这个端口号。如例程3-3所示的RandomPort创建了一个ServerSocket,它使用的就是匿名端口。
例程3-3 RandomPort.java
public class RandomPort{
public static void main(String args[])throws IOException{
ServerSocket serverSocket=new ServerSocket(0);
System.out.println("监听的端口为:"+serverSocket.getLocalPort());
}
}
多次运行RandomPort程序,可能会得到如下运行结果:
C:\chapter03\classes>java RandomPort
监听的端口为:3000
C:\chapter03\classes>java RandomPort
监听的端口为:3004
C:\chapter03\classes>java RandomPort
监听的端口为:3005
多数服务器会监听固定的端口,这样才便于客户程序访问服务器。匿名端口一般适用于服务
器与客户之间的临时通信,通信结束,就断开连接,并且ServerSocket占用的临时端口也
被释放
1.定义码头:即定义一个DatagramSocket对象 ds
2.定义可以用来接收或发送数据的集装箱,定义DatagramPacket对象dp
3.在码头上用集装箱接收对象发送过来的数据(ds.receive(dp)),或者在码头上把集装箱中的数据发送给对方(ds.send(dp))
4.关闭码头(ds.close())
注意:
IP地址在java中是用java.net.InetAddress表示,java.net.InetaAddress 并没有构造方法,因此要把“127.0.0.1”转化为IP地址
必须写成:
InetAddress.getByName("127.0.0.1");
不能写成:
InetAddress("127.0.0.1");
DatagramPacket是个集装箱,即可以用来存放即将要发送到客户端的数据,也可以用来存放即将要接收的客户端数据
如果UDP中两个网络程序要通信的话,这两个程序都必须得定义DatagramPacket
如果定义的datagramPacket对象要用来接收客户端发送过来的数据,则不建议指定目的端口号的目的IP
如果定义的DatagramPacket对象用来存储要发送到客户端的数据,则必须的指定目的端口号的目的IP
接收端程序编写步骤:
调用DatagramGocket(int port)创建一个服务器端,并绑定到指定端口上,
调用accept,监听连接请求,如果客户端请求连接,则接受连接,并返回和客户端匹配的套接字
调用Socket类的getOutputStream()和getInputStream获取输入流和输入流,开始网络数据的发送和接收
最后关闭资源
发送端程序编写步骤:
调用Socket()创建一个客户端套接字,该套接字会自动向服务器端发送连接请求,
调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,开始网络数据的发送和接收
最后关闭资源
要注意的问题:
一旦new出的Socket对象,该对象就会自动向服务器端发送连接请求,如果连接不成功则程序立即终止
new出的ServerSocket对象并不会自动监听客户端的请求,只有调用了ServerSocket对象的accept方法时,才会监听客户端的请求
网络编程
什么是网络编程
能够接受或者发送数据给另一台终端的程序叫网络编程
UDP和TCP协议:
UDP:
将数据及源和目的封装成数据包中,不需要建立连接
每个数据报的大小限制在64k内
因无连接,是不可靠协议
不需要建
TCP:
建立连接,形成传输数据的通道
在连接中进行大数据量传输
通过三次握手完成连接,是可靠协议
必须建立连接,效率会稍低立连接,速度快
什么是IP
为了分辨来自别处的一台机器,以及为了保证自己连接的是希望的那台机器,必须有一种机制能独一
无二地标识出网络内的每台机器。早期网络只解决了如何在本地网络环境中为机器提供唯一的名字。但 Java
面向的是整个因特网,这要求用一种机制对来自世界各地的机器进行标识。为达到这个目的,我们采用了 IP
(互联网地址)的概念。
能够在网络中唯一标识一台主机的编号就是IP,网络中每台主机都必须有一个IP地址
无二地标识出网络内的每台机器。早期网络只解决了如何在本地网络环境中为机器提供唯一的名字。但 Java
面向的是整个因特网,这要求用一种机制对来自世界各地的机器进行标识。为达到这个目的,我们采用了 IP
(互联网地址)的概念。
能够在网络中唯一标识一台主机的编号就是IP,网络中每台主机都必须有一个IP地址
设定绑定的IP地址:
如果主机只有一个IP地址,那么默认情况下,服务器程序就与该IP地址绑定。ServerSock的第4个构造方法ServerSocket(int port, int backlog, InetAddress bindAddr)有一个
bindAddr参数,它显式指定服务器要绑定的IP地址,该构造方法适用于具有多个IP地址的主机。假定一个主机有两个网卡,一个网卡用于连接到Internet,IP地址为222.67.5.9
还有一个网卡用于连接到本地局域网,IP地址为192.168.3.4。如果服务器仅仅被本地局网中的客户访问,那么可以按如下方式创建ServerSocket:ServerSocket serverSocket=new ServerSocket(8000,10,InetAddress.getByName ("192.168.3.4"));
bindAddr参数,它显式指定服务器要绑定的IP地址,该构造方法适用于具有多个IP地址的主机。假定一个主机有两个网卡,一个网卡用于连接到Internet,IP地址为222.67.5.9
还有一个网卡用于连接到本地局域网,IP地址为192.168.3.4。如果服务器仅仅被本地局网中的客户访问,那么可以按如下方式创建ServerSocket:ServerSocket serverSocket=new ServerSocket(8000,10,InetAddress.getByName ("192.168.3.4"));
/*
拿到主机名称和IP地址
*/
import java.net.*;
public class IPDome
{
public static void main(String[] args)
{
try
{
InetAddress i = InetAddress.getLocalHost();
//System.out.println(i.toString());
System.out.println(i.getHostAddress()); // 拿到IP地址
System.out.println(i.getHostName()); //拿到主机名称
InetAddress ia = InetAddress.getByName("192.168.1.102");
//System.out.println(ia.getHostName());
InetAddress iaa = InetAddress.getByName("www.google.com"); // 拿到google的IP地址
//System.out.println(iaa.getHostAddress());
InetAddress[] ia2 = InetAddress.getAllByName("www.google.com"); // a拿到google所有的IP地址
for(InetAddress j : ia2)
{
System.out.println(j.getHostAddress());
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
}
}
}
IP的表示形式:
32位,4个字节,常用点分十进制的格式表示, 例如: 192.168.1.102端口:
什么是端口:
有些时候,一个 IP 地址并不足以完整标识一个服务器。这是由于在一台物理性的机器中,往往运行着多个服务器(程序)。由 IP 表达的每台机器也包含了“端口”。我们设置一个客户机或者服务器的时候,
必须选择一个无论客户机还是服务器都认可连接的端口。就象我们去拜会某人时,IP 地址是他居住的房子,
而端口是他在的那个房间。
绑定端口:
除了第一个不带参数的构造方法以外,其他构造方法都会使服务器与特定端口绑定,该端口由参数port指定。例如,以下代码创建了一个与80端口绑定的服务器:ServerSocket serverSocket=new ServerSocket(80);
如果运行时无法绑定到80端口,以上代码会抛出IOException,更确切地说,是抛出BindException,它是IOException的子类。BindException一般是由以下原因造成的:
端口已经被其他服务器进程占用;
在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到1~1023之间的端口。如果把参数port设为0,表示由操作系统来为服务器分配一个任意可用的端口。由操作系统分配的端口也称为匿名端口。对于多数服务器,会使用明确的端口,而不会使用匿名端口,因为客户程序需要事先知道服务器的端口,才能方便地访问服务器。在某些场合,匿名端口有着特殊的用途,本章3.4节会对此作介绍。
说明:
端口是用一个16位的数字来表示,它的范围是0~65535,1024以下的端口号保留给了系统的服务,例如:80端口访问网页,25端口用来邮件发送,oralce92默认的端口号是1532,tomacat默认的端口号是8080.
线程泄漏:
使用线程池的一个严重风险是线程泄漏。对于工作线程数目固定的线程池,如果工作线程在执行任务时抛出RuntimeException 或Error,并且这些异常或错误没有被捕获,那么这个工作线程就会异常终止,使得线程池永久失去了一个工作线程。如果所有的工作线程都异常终止,线程池就最终变为空,没有任何可用的工作线程来处理任务。
导致线程泄漏的另一种情形是,工作线程在执行一个任务时被阻塞,如等待用户的输入数据,但是由于用户一直不输入数据(可能是因为用户走开了),导致这个工作线程一直被阻塞。这样的工作线程名存实亡,它实际上不执行任何任务了。假如线程池中所有的工作线程都处于这样的阻塞状态,那么线程池就无法处理新加入的任务了。
任务过载:
当工作队列中有大量排队等候执行的任务时,这些任务本身可能会消耗太多的系统资源而引起系统资源缺乏。线程池可能会带来种种风险,为了尽可能避免它们,使用线程池时需要遵循以下原则。
(1)如果任务A在执行过程中需要同步等待任务B的执行结果,那么任务A不适合加入到线程池的工作队列中。如果把像任务A一样的需要等待其他任务执行结果的任务加入到工作队中,可能会导致线程池的死锁。
(2)如果执行某个任务时可能会阻塞,并且是长时间的阻塞,则应该设定超时时间,避免工作线程永久的阻塞下去而导致线程泄漏。在服务器程序中,当线程等待客户连接,或者等
待客户发送的数据时,都可能会阻塞。可以通过以下方式设定超时时间:
了解任务的特点,分析任务是执行经常会阻塞的I/O 操作,还是执行一直不会阻塞的运算操作。前者时断时续地占用CPU,而后者对CPU具有更高的利用率。预计完成任务大概需要多长时间?是短时间任务还是长时间任务?根据任务的特点,对任务进行分类,然后把不同类型的任务分别加入到不同线程池的工作队列中,这样可以根据任务的特点,分别调整每个线程池。
(4)调整线程池的大小。线程池的最佳大小主要取决于系统的可用CPU的数目,以及工作队列中任务的特点。假如在一个具有N 个CPU的系统上只有一个工作队列,并且其中全部是运算性质(不会阻塞)的任务,那么当线程池具有N 或N+1 个工作线程时,一般会获得最大的CPU 利用率。如果工作队列中包含会执行I/O操作并常常阻塞的任务,则要让线程池的大小超过可用CPU的数目,因为并不是所有工作线程都一直在工作。选择一个典型的任务,然后估计在执行这个任务的过程中,等待时间(WT)与实际占用CPU进行运算的时间(ST)之间的比例WT/ST。对于一个具有N个CPU的系统,需要设置大约N×(1+WT/ST)个线程来保证CPU得到充分利用。当然,CPU利用率不是调整线程池大小过程中唯一要考虑的事项。随着线程池中工作线程数目的增长,还会碰到内存或者其他系统资源的限制,如套接字、打开的文件句柄或数据库连接数目等。要保证多线程消耗的系统资源在系统的承载范围之内。
(5)避免任务过载。服务器应根据系统的承载能力,限制客户并发连接的数目。当客户并发连接的数目超过了限制值,服务器可以拒绝连接请求,并友好地告知客户:服务器正忙,
请稍后再试。
接收和关闭与客户的连接:
ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
接下来,服务器从Socket对象中获得输入流和输出流,就能与客户交换数据。当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException
的子类SocketException异常:java.net.SocketException: Connection reset by peer这只是服务器与单个客户通信中出现的异常,这种异常应该被捕获,使得服务器能继续与其
他客户通信。
以下程序显示了单线程服务器采用的通信流程:
public void service() {
while (true) {
Socket socket=null;
try {
socket = serverSocket.accept(); //从连接请求队列中取出一个连接
System.out.println("New connection accepted " +
socket.getInetAddress() + ":" +socket.getPort());
//接收和发送数据
…
}catch (IOException e) {
//这只是与单个客户通信时遇到的异常,可能是由于客户端过早断开连接引起的
//这种异常不应该中断整个while循环
e.printStackTrace();
}finally {
try{
if(socket!=null)socket.close(); //与一个客户通信结束后,要关闭
Socket
}catch (IOException e) {e.printStackTrace();}
}
}
}
与单个客户通信的代码放在一个try代码块中,如果遇到异常,该异常被catch代码块捕获。
try代码块后面还有一个finally代码块,它保证不管与客户通信正常结束还是异常结束,最
后都会关闭Socket,断开与这个客户的连接。
关闭ServerSocket:
ServerSocket的close()方法使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行ServerSocket的close()方法,操作系统也会释放这个服务器占用的端口。因此,服务器程序并不一定要在结束之前执行ServerSocket的close()方法。在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式调用ServerSocket的close()方法。例如,以下代码用于扫描1~65535之间的端口号。如果ServerSocket成功创建,意味着该端口未被其他服务器进程绑定,否者说明该端口已经被其他进程占用:
for(int port=1;port<=65535;port++){
try{
ServerSocket serverSocket=new ServerSocket(port);
serverSocket.close(); //及时关闭ServerSocket
}catch(IOException e){
System.out.println("端口"+port+" 已经被其他服务器进程占用");
}
}
以上程序代码创建了一个ServerSocket对象后,就马上关闭它,以便及时释放它占用的端口,从而避免程序临时占用系统的大多数端口。
ServerSocket的isClosed()方法判断ServerSocket是否关闭,只有执行了ServerSocket的close()方法,isClosed()方法才返回true;否则,
即使ServerSocket还没有和特定端口绑定,isClosed()方法也会返回false。ServerSocket的isBound()方法判断ServerSocket是否已经与一个端口绑定,
只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()方法也会返回true。如果需要确定一个ServerSocket已经与特定端口绑定,并且还没有被关闭,则可以采用以
下方式:
boolean isOpen=serverSocket.isBound() && !serverSocket.isClosed();
获取ServerSocket的信息:
ServerSocket的以下两个get方法可分别获得服务器绑定的IP地址,以及绑定的端口:
public InetAddress getInetAddress()
public int getLocalPort()
在构造ServerSocket时,如果把端口设为0,那么将由操作系统为服务器分配一个端口(称为匿名端口),程序只要调用getLocalPort()方法就能获知这个端口号。如例程3-3所示的RandomPort创建了一个ServerSocket,它使用的就是匿名端口。
例程3-3 RandomPort.java
public class RandomPort{
public static void main(String args[])throws IOException{
ServerSocket serverSocket=new ServerSocket(0);
System.out.println("监听的端口为:"+serverSocket.getLocalPort());
}
}
多次运行RandomPort程序,可能会得到如下运行结果:
C:\chapter03\classes>java RandomPort
监听的端口为:3000
C:\chapter03\classes>java RandomPort
监听的端口为:3004
C:\chapter03\classes>java RandomPort
监听的端口为:3005
多数服务器会监听固定的端口,这样才便于客户程序访问服务器。匿名端口一般适用于服务
器与客户之间的临时通信,通信结束,就断开连接,并且ServerSocket占用的临时端口也
被释放
基于UDP的socket编程步骤:
1.定义码头:即定义一个DatagramSocket对象 ds
2.定义可以用来接收或发送数据的集装箱,定义DatagramPacket对象dp
3.在码头上用集装箱接收对象发送过来的数据(ds.receive(dp)),或者在码头上把集装箱中的数据发送给对方(ds.send(dp))
4.关闭码头(ds.close())
注意:
IP地址在java中是用java.net.InetAddress表示,java.net.InetaAddress 并没有构造方法,因此要把“127.0.0.1”转化为IP地址
必须写成:
InetAddress.getByName("127.0.0.1");
不能写成:
InetAddress("127.0.0.1");
DatagramPacket是个集装箱,即可以用来存放即将要发送到客户端的数据,也可以用来存放即将要接收的客户端数据
如果UDP中两个网络程序要通信的话,这两个程序都必须得定义DatagramPacket
如果定义的datagramPacket对象要用来接收客户端发送过来的数据,则不建议指定目的端口号的目的IP
如果定义的DatagramPacket对象用来存储要发送到客户端的数据,则必须的指定目的端口号的目的IP
基于TCP的socket编程步骤:
接收端程序编写步骤:
调用DatagramGocket(int port)创建一个服务器端,并绑定到指定端口上,
调用accept,监听连接请求,如果客户端请求连接,则接受连接,并返回和客户端匹配的套接字
调用Socket类的getOutputStream()和getInputStream获取输入流和输入流,开始网络数据的发送和接收
最后关闭资源
发送端程序编写步骤:
调用Socket()创建一个客户端套接字,该套接字会自动向服务器端发送连接请求,
调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,开始网络数据的发送和接收
最后关闭资源
要注意的问题:
一旦new出的Socket对象,该对象就会自动向服务器端发送连接请求,如果连接不成功则程序立即终止
new出的ServerSocket对象并不会自动监听客户端的请求,只有调用了ServerSocket对象的accept方法时,才会监听客户端的请求
/*
需求:
建立一个文本转换器,客户端给服务端发送文本,服务端会将该文本转成大写再返回给客户端
而且客户端可以不断的进行文本转换,当客户端输入over时,转换结束
分析:
客户端:
既然是操作设备上的数据,那么就可以使用IO技术,并按照IO的操作规律来思考
源: 键盘录入
目的: 网络设备,网络输出流,而且操作的是文本数据 ,可以选择字符流
步骤:
1.建立服务
2.获取键入录入
3.将数据发给服务端
4.获取服务端返回的大写数据
5.结束,关资源
都是文本数据,可以使用字符流进行操作,可以提高效率,
*/
import java.io.*;
import java.net.*;
class Client
{
public static void main(String[] args)
{
Socket s = null;
BufferedReader bufr = null;
try
{
s = new Socket("192.168.1.102",10010);
//定义读取键盘的流对象
bufr = new BufferedReader(new InputStreamReader(System.in));
//定义目的,将数据写入到Socket输出流,发给服务端
//BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//定义一个Socket读取流,读取服务端返回的大写信息
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while ((line = bufr.readLine()) != null )
{
if ("over".equals(line))
break;
out.println(line.toUpperCase());
// bufOut.write(line);
// bufOut.newLine();
// bufOut.flush();
String str = bufIn.readLine();
System.out.println(str);
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
if (bufr != null)
bufr.close();
if (s != null)
s.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
/*
服务端:
*/
class Server
{
public static void main(String[] args)
{
ServerSocket ss = null;
Socket s = null;
try
{
ss = new ServerSocket(10010);
s = ss.accept();
//读取Socket读取流中的数据
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的,Socket输出流,将大写数据写入到Socket输出流,并发送给客户端
//BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while ((line = bufIn.readLine()) != null)
{
System.out.println(line.toUpperCase());
out.println(line.toUpperCase());
// bufOut.write(line.toUpperCase());
// bufOut.newLine();
// bufOut.flush();
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
if (s != null)
s.close();
if (ss != null)
ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
/*
该例子出现的问题:
现象: 客户端和服务端都在莫名的等待,为什么呢?
因为客户端和服务端都有阻塞式方法,这些方法没有读取结束标记,那么就一直等,而导致两端都在等待
*/