黑马程序员 — 网络编程

-------android培训java培训、期待与您交流! ----------


网络编程



什么是网络编程:


能够接受另一台计算机发送过来的数据或者能够向另一台计算机发送数据的程序叫做网络程序



UDP与TCP协议:


什么是协议:

为进行网络中的数据通信而建立的规则,标准或约定

UDP:

将数据及源和目的封装成数据包中,不需要建立连接

每个数据报的大小限制在64k内

因无连接,是不可靠协议

不需要建立连接,速度快


TCP:

建立连接,形成传输数据的通道

在连接中进行大数据量传输

通过三次握手完成连接,是可靠协议

必须建立连接,效率会稍低



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"));


/*

	拿到主机名称和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编程步骤:


接收端程序编写步骤:

  1. 调用DatagramGocket(int port)创建一个服务器端,并绑定到指定端口上,
  2. 调用accept,监听连接请求,如果客户端请求连接,则接受连接,并返回和客户端匹配的套接字
  3. 调用Socket类的getOutputStream()和getInputStream获取输入流和输入流,开始网络数据的发送和接收
  4. 最后关闭资源

发送端程序编写步骤:

  1. 调用Socket()创建一个客户端套接字,该套接字会自动向服务器端发送连接请求,
  2. 调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,开始网络数据的发送和接收
  3. 最后关闭资源

要注意的问题:

一旦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();
			}
		
		}
	}
}

/*
	该例子出现的问题:
		
		现象: 客户端和服务端都在莫名的等待,为什么呢?
		
			因为客户端和服务端都有阻塞式方法,这些方法没有读取结束标记,那么就一直等,而导致两端都在等待

*/



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值