Java套接字编程上

Java套接字编程上

ID:47162203

大小:93.00 KB

页数:14页

时间:2019-08-14

上传者:qwe189537
Java套接字编程上_第1页
Java套接字编程上_第2页
Java套接字编程上_第3页
Java套接字编程上_第4页
Java套接字编程上_第5页
资源描述:

《Java套接字编程上》由会员上传分享,免费在线阅读,更多相关内容在工程资料-天天文库

Java套接字编程(上)用Java开发网络软件非常方便和强大,Java的这种力量来源于他独有的一套强大的用于网络的API,这些API是一系列的类和接口,均位于包java.net和javax.net中。在这篇文章中我们将介绍套接字(Socket)慨念,同时以实例说明如何使用NetworkAPI操纵套接字,在完成本文后,你就可以编写网络低端通讯软件。  什么是套接字(Socket)?  NetworkAPI是典型的用于基于TCP/IP网络Java程序与其他程序通讯,NetworkAPI依靠Socket进行通讯。Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。如图1  我们来分析一下图1,HostA上的程序A将一段信息写入Socket中,Socket的内容被HostA的网络管理软件访问,并将这段信息通过HostA的网络接口卡发送到HostB,HostB的网络接口卡接收到这段信息后,传送给HostB的网络管理软件,网络管理软件将这段信息保存在HostB的Socket中,然后程序B才能在Socket中阅读这段信息。  假设在图1的网络中添加第三个主机HostC,那么HostA怎么知道信息被正确传送到HostB而不是被传送到HostC中了呢?基于TCP/IP网络中的每一个主机均被赋予了一个唯一的IP地址,IP地址是一个32位的无符号整数,由于没有转变成二进制,因此通常以小数点分隔,如:198.163.227.6,正如所见IP地址均由四个部分组成,每个部分的范围都是0-255,以表示8位地址。   值得注意的是IP地址都是32位地址,这是IP协议版本4(简称Ipv4)规定的,目前由于IPv4地址已近耗尽,所以IPv6地址正逐渐代替Ipv4地址,Ipv6地址则是128位无符号整数。  假设第二个程序被加入图1的网络的HostB中,那么由HostA传来的信息如何能被正确的传给程序B而不是传给新加入的程序呢?这是因为每一个基于TCP/IP网络通讯的程序都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的短口号保留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口、端口号,这样形成的整体就可以区别每一个套接字t,下面我们就来谈谈两种套接字:流套接字和自寻址数据套接字。  流套接字(StreamSocket)  无论何时,在两个网络应用程序之间发送和接收信息时都需要建立一个可靠的连接,流套接字依靠TCP协议来保证信息正确到达目的地,实际上,IP包有可能在网络中丢失或者在传送过程中发生错误,任何一种情况发生,作为接受方的TCP将联系发送方TCP重新发送这个IP包。这就是所谓的在两个流套接字之间建立可靠的连接。  流套接字在C/S程序中扮演一个必需的角色,客户机程序(需要访问某些服务的网络应用程序)创建一个扮演服务器程序的主机的IP地址和服务器程序(为客户端应用程序提供服务的网络应用程序)的端口号的流套接字对象。  客户端流套接字的初始化代码将IP地址和端口号传递给客户端主机的网络管理软件,管理软件将IP地址和端口号通过NIC传递给服务器端主机;服务器端主机读到经过NIC传递来的数据,然后查看服务器程序是否处于监听状态,这种监听依然是通过套接字和端口来进行的;如果服务器程序处于监听状态,那么服务器端网络管理软件就向客户机网络管理软件发出一个积极的响应信号,接收到响应信号后,客户端流套接字初始化代码就给客户程序建立一个端口号,并将这个端口号传递给服务器程序的套接字(服务器程序将使用这个端口号识别传来的信息是否是属于客户程序)同时完成流套接字的初始化。  如果服务器程序没有处于监听状态,那么服务器端网络管理软件将给客户端传递一个消极信号,收到这个消极信号后,客户程序的流套接字初始化代码将抛出一个异常对象并且不建立通讯连接,也不创建流套接字对象。这种情形就像打电话一样,当有人的时候通讯建立,否则电话将被挂起。  这部分的工作包括了相关联的三个类:InetAddress,Socket,和ServerSocket。InetAddress对象描绘了32位或128位IP地址,Socket对象代表了客户程序流套接字,ServerSocket代表了服务程序流套接字,所有这三个类均位于包java.net中。  InetAddress类  InetAddress类在网络API套接字编程中扮演了一个重要角色。参数传递给流套接字类和自寻址套接字类构造器或非构造器方法。InetAddress描述了32位或64位IP地址,要完成这个功能,InetAddress类主要依靠两个支持类Inet4Address和 Inet6Address,这三个类是继承关系,InetAddrress是父类,Inet4Address和Inet6Address是子类。  由于InetAddress类只有一个构造函数,而且不能传递参数,所以不能直接创建InetAddress对象,比如下面的做法就是错误的:InetAddressia=newInetAddress();  但我们可以通过下面的5个工厂方法创建来创建一个InetAddress对象或InetAddress数组:   .getAllByName(Stringhost)方法返回一个InetAddress对象的引用,每个对象包含一个表示相应主机名的单独的IP地址,这个IP地址是通过host参数传递的,对于指定的主机如果没有IP地址存在那么这个方法将抛出一个UnknownHostException异常对象。   .getByAddress(byte[]addr)方法返回一个InetAddress对象的引用,这个对象包含了一个Ipv4地址或Ipv6地址,Ipv4地址是一个4字节数组,Ipv6地址是一个16字节地址数组,如果返回的数组既不是4字节的也不是16字节的,那么方法将会抛出一个UnknownHostException异常对象。   .getByAddress(Stringhost,byte[]addr)方法返回一个InetAddress对象的引用,这个InetAddress对象包含了一个由host和4字节的addr数组指定的IP地址,或者是host和16字节的addr数组指定的IP地址,如果这个数组既不是4字节的也不是16位字节的,那么该方法将抛出一个UnknownHostException异常对象。   .getByName(Stringhost)方法返回一个InetAddress对象,该对象包含了一个与host参数指定的主机相对应的IP地址,对于指定的主机如果没有IP地址存在,那么方法将抛出一个UnknownHostException异常对象。   .getLocalHost()方法返回一个InetAddress对象,这个对象包含了本地机的IP地址,考虑到本地主机既是客户程序主机又是服务器程序主机,为避免混乱,我们将客户程序主机称为客户主机,将服务器程序主机称为服务器主机。  上面讲到的方法均提到返回一个或多个InetAddress对象的引用,实际上每一个方法都要返回一个或多个Inet4Address/Inet6Address对象的引用,调用者不需要知道引用的子类型,相反调用者可以使用返回的引用调用InetAddress对象的非静态方法,包括子类型的多态以确保重载方法被调用。  InetAddress和它的子类型对象处理主机名到主机IPv4或IPv6地址的转换,要完成这个转换需要使用域名系统,下面的代码示范了如何通过调用getByName(Stringhost)方法获得InetAddress子类对象的方法,这个对象包含了与host参数相对应的IP地址:InetAddressia=InetAddress.getByName("www.javajeff.com"));   一但获得了InetAddress子类对象的引用就可以调用InetAddress的各种方法来获得InetAddress子类对象中的IP地址信息,比如,可以通过调用getCanonicalHostName()从域名服务中获得标准的主机名;getHostAddress()获得IP地址,getHostName()获得主机名,isLoopbackAddress()判断IP地址是否是一个loopback地址。  List1是一段示范代码:InetAddressDemo//InetAddressDemo.javaimportjava.net.*;classInetAddressDemo{ publicstaticvoidmain(String[]args)throwsUnknownHostException {  Stringhost="localhost";  if(args.length==1)   host=args[0];  InetAddressia=InetAddress.getByName(host);  System.out.println("CanonicalHostName="+        ia.getCanonicalHostName());  System.out.println("HostAddress="+        ia.getHostAddress());  System.out.println("HostName="+        ia.getHostName());  System.out.println("IsLoopbackAddress="+        ia.isLoopbackAddress()); }}  当无命令行参数时,代码输出类似下面的结果:CanonicalHostName=localhostHostAddress=127.0.0.1HostName=localhostIsLoopbackAddress=true  InetAddressDemo给了你一个指定主机名作为命令行参数的选择,如果没有主机名被指定,那么将使用localhost(客户机的),InetAddressDemo通过调用getByName(Stringhost)方法获得一个InetAddress子类对象的引用,通过这个引用获得了标准主机名,主机地址,主机名以及IP地址是否是loopback地址的输出。Socket类   当客户程序需要与服务器程序通讯的时候,客户程序在客户机创建一个socket对象,Socket类有几个构造函数。两个常用的构造函数是Socket(InetAddressaddr,intport)和Socket(Stringhost,intport),两个构造函数都创建了一个基于Socket的连接服务器端流套接字的流套接字。对于第一个InetAddress子类对象通过addr参数获得服务器主机的IP地址,对于第二个函数host参数包被分配到InetAddress对象中,如果没有IP地址与host参数相一致,那么将抛出UnknownHostException异常对象。两个函数都通过参数port获得服务器的端口号。假设已经建立连接了,网络API将在客户端基于Socket的流套接字中捆绑客户程序的IP地址和任意一个端口号,否则两个函数都会抛出一个IOException对象。  如果创建了一个Socket对象,那么它可能通过调用Socket的getInputStream()方法从服务程序获得输入流读传送来的信息,也可能通过调用Socket的getOutputStream()方法获得输出流来发送消息。在读写活动完成之后,客户程序调用close()方法关闭流和流套接字,下面的代码创建了一个服务程序主机地址为198.163.227.6,端口号为13的Socket对象,然后从这个新创建的Socket对象中读取输入流,然后再关闭流和Socket对象。Sockets=newSocket("198.163.227.6",13);InputStreamis=s.getInputStream();//Readfromthestream.is.close();s.close();  接下面我们将示范一个流套接字的客户程序,这个程序将创建一个Socket对象,Socket将访问运行在指定主机端口10000上的服务程序,如果访问成功客户程序将给服务程序发送一系列命令并打印服务程序的响应。List2使我们创建的程序SSClient的源代码:  Listing2:SSClient.java//SSClient.javaimportjava.io.*;importjava.net.*;classSSClient{ publicstaticvoidmain(String[]args) {  Stringhost="localhost";  //Ifuserspecifiesacommand-lineargument,thatargument  //representsthehostname.  if(args.length==1)   host=args[0];  BufferedReaderbr=null;  PrintWriterpw= null;  Sockets=null;  try  {   //Createasocketthatattemptstoconnecttotheserver   //programonthehostatport10000.   s=newSocket(host,10000);   //Createaninputstreamreaderthatchainstothesocket's   //byte-orientedinputstream.Theinputstreamreader   //convertsbytesreadfromthesockettocharacters.The   //conversionisbasedontheplatform'sdefaultcharacter   //set.   InputStreamReaderisr;   isr=newInputStreamReader(s.getInputStream());   //Createabufferedreaderthatchainstotheinputstream   //reader.Thebufferedreadersuppliesaconvenientmethod   //forreadingentirelinesoftext.   br=newBufferedReader(isr);   //Createaprintwriterthatchainstothesocket'sbyte-   //orientedoutputstream.Theprintwritercreatesan   //intermediateoutputstreamwriterthatconverts   //characterssenttothesockettobytes.Theconversion   //isbasedontheplatform'sdefaultcharacterset.   pw=newPrintWriter(s.getOutputStream(),true);   //SendtheDATEcommandtotheserver.   pw.println("DATE");   //Obtainandprintthecurrentdate/time.   System.out.println(br.readLine());   //SendthePAUSEcommandtotheserver.Thisallowsseveral   //clientstostartandverifiesthattheserverisspawning   //multiplethreads.   pw.println("PAUSE");    //SendtheDOWcommandtotheserver.   pw.println("DOW");   //Obtainandprintthecurrentdayofweek.   System.out.println(br.readLine());   //SendtheDOMcommandtotheserver.    pw.println("DOM");   //Obtainandprintthecurrentdayofmonth.   System.out.println(br.readLine());   //SendtheDOYcommandtotheserver.   pw.println("DOY");   //Obtainandprintthecurrentdayofyear.   System.out.println(br.readLine());  }  catch(IOExceptione)  {   System.out.println(e.toString());  }  finally  {   try   {    if(br!=null)     br.close();    if(pw!=null)     pw.close();    if(s!=null)     s.close();   }   catch(IOException e)   {    }  } }}  运行这段程序将会得到下面的结果:TueJan2918:11:51CST2002TUESDAY2929  SSClient创建了一个Socket对象与运行在主机端口10000的服务程序联系,主机的IP地址由host变量确定。SSClient将获得Socket的输入输出流,围绕BufferedReader的输入流和PrintWriter的输出流对字符串进行读写操作就变得非常容易,SSClient个服务程序发出各种date/time命令并得到响应,每个响应均被打印,一旦最后一个响应被打印,将执行Try/Catch/Finally结构的Finally子串,Finally子串将在关闭Socket之前关闭BufferedReader和PrintWriter。  在SSClient源代码编译完成后,可以输入javaSSClient来执行这段程序,如果有合适的程序运行在不同的主机上,采用主机名/IP地址为参数的输入方式,比如www.sina.com.cn是运行服务器程序的主机,那么输入方式就是javaSSClientwww.sina.com.cn。  技巧  Socket类包含了许多有用的方法。比如getLocalAddress()将返回一个包含客户程序IP地址的InetAddress子类对象的引用;getLocalPort()将返回客户程序的端口号;getInetAddress()将返回一个包含服务器IP地址的InetAddress子类对象的引用;getPort()将返回服务程序的端口号。ServerSocket类  由于SSClient使用了流套接字,所以服务程序也要使用流套接字。这就要创建一个ServerSocket对象,ServerSocket有几个构造函数,最简单的是ServerSocket(intport),当使用ServerSocket(intport)创建一个ServerSocket对象,port参数传递端口号,这个端口就是服务器监听连接请求的端口,如果在这时出现错误将抛出IOException异常对象,否则将创建ServerSocket对象并开始准备接收连接请求。  接下来服务程序进入无限循环之中,无限循环从调用ServerSocket的accept()方法开始,在调用开始后accept()方法将导致调用线程阻塞直到连接建立。在建立连接后accept()返回一个最近创建的Socket对象,该Socket对象绑定了客户程序的IP地址或端口号。   由于存在单个服务程序与多个客户程序通讯的可能,所以服务程序响应客户程序不应该花很多时间,否则客户程序在得到服务前有可能花很多时间来等待通讯的建立,然而服务程序和客户程序的会话有可能是很长的(这与电话类似),因此为加快对客户程序连接请求的响应,典型的方法是服务器主机运行一个后台线程,这个后台线程处理服务程序和客户程序的通讯。  为了示范我们在上面谈到的慨念并完成SSClient程序,下面我们创建一个SSServer程序,程序将创建一个ServerSocket对象来监听端口10000的连接请求,如果成功服务程序将等待连接输入,开始一个线程处理连接,并响应来自客户程序的命令。下面就是这段程序的代码:  Listing3:SSServer.java//SSServer.javaimportjava.io.*;importjava.net.*;importjava.util.*;classSSServer{ publicstaticvoidmain(String[]args)throwsIOException {   System.out.println("Serverstarting... ");  //Createaserversocketthatlistensforincomingconnection  //requestsonport10000.  ServerSocketserver=newServerSocket(10000);  while(true)  {   //Listenforincomingconnectionrequestsfromclient   //programs,establishaconnection,andreturnaSocket   //objectthatrepresentsthisconnection.   Sockets=server.accept();   System.out.println("AcceptingConnection... ");   //Startathreadtohandletheconnection.   newServerThread(s).start ();  } }}classServerThreadextendsThread{ privateSockets; ServerThread(Sockets) {  this.s=s; } publicvoidrun() {  BufferedReaderbr=null;  PrintWriterpw=null;  try  {   //Createaninputstreamreaderthatchainstothesocket's   //byte-orientedinputstream.Theinputstreamreader   //convertsbytesreadfromthesockettocharacters.The   //conversionisbasedontheplatform'sdefaultcharacter   //set.   InputStreamReaderisr;   isr=newInputStreamReader(s.getInputStream());   //Createabufferedreaderthatchainstotheinputstream   //reader.Thebufferedreadersuppliesaconvenientmethod   //forreadingentirelinesoftext.   br=newBufferedReader(isr);   //Createaprintwriterthatchainstothesocket'sbyte-   //orientedoutputstream.Theprintwritercreatesan   //intermediateoutputstreamwriterthatconverts   //characterssenttothesockettobytes.Theconversion   //isbasedontheplatform'sdefaultcharacterset.   pw=newPrintWriter(s.getOutputStream(),true);   //Createacalendarthatmakesitpossibletoobtaindate   //andtimeinformation.   Calendarc=Calendar.getInstance ();   //Becausetheclientprogrammaysendmultiplecommands,a   //loopisrequired.Keeploopinguntiltheclienteither   //explicitlyrequeststerminationbysendingacommand   //beginningwithlettersBYEorimplicitlyrequests   //terminationbyclosingitsoutputstream.   do   {    //Obtaintheclientprogram'snextcommand.    Stringcmd=br.readLine();    //Exitifclientprogramhascloseditsoutputstream.    if(cmd==null)     break;      //Convertcommandtouppercase,foreaseofcomparison.    cmd=cmd.toUpperCase();    //IfclientprogramsendsBYEcommand,terminate.    if(cmd.startsWith("BYE"))     break;    //IfclientprogramsendsDATEorTIMEcommand,return    //currentdate/timetotheclientprogram.    if(cmd.startsWith("DATE")||cmd.startsWith("TIME"))     pw.println(c.getTime().toString());    //IfclientprogramsendsDOM(DayOfMonth)command,    //returncurrentdayofmonthtotheclientprogram.    if(cmd.startsWith("DOM"))     pw.println(""+c.get(Calendar.DAY_OF_MONTH));    //IfclientprogramsendsDOW(DayOfWeek)command,    //returncurrentweekday(asastring)totheclient    //program.    if(cmd.startsWith("DOW"))     switch(c.get (Calendar.DAY_OF_WEEK))    {     caseCalendar.SUNDAY:pw.println("SUNDAY");      break;     caseCalendar.MONDAY:pw.println("MONDAY");      break;     caseCalendar.TUESDAY:pw.println("TUESDAY");      break;     caseCalendar.WEDNESDAY:pw.println("WEDNESDAY");      break;     caseCalendar.THURSDAY:pw.println("THURSDAY");      break;     caseCalendar.FRIDAY:pw.println("FRIDAY");      break;     caseCalendar.SATURDAY:pw.println("SATURDAY");    }    //IfclientprogramsendsDOY(DayofYear)command,    //returncurrentdayofyeartotheclientprogram.    if(cmd.startsWith("DOY"))     pw.println(""+c.get(Calendar.DAY_OF_YEAR));     //IfclientprogramsendsPAUSEcommand,sleepforthree     //seconds.     if(cmd.startsWith("PAUSE"))    try    {     Thread.sleep(3000);    }    catch(InterruptedExceptione)    {    }   }   while(true);   {   catch(IOException e)   {    System.out.println(e.toString());   }   finally   {    System.out.println("ClosingConnection... ");    try    {     if(br!=null)      br.close();      if(pw!=null)       pw.close();      if(s!=null)       s.close();    }    catch(IOExceptione)    {    }   }  }}  运行这段程序将得到下面的输出:Serverstarting...AcceptingConnection...ClosingConnection...  SSServer的源代码声明了一对类:SSServer和ServerThread;SSServer的main()方法创建了一个ServerSocket对象来监听端口10000上的连接请求,如果成功,SSServer进入一个无限循环中,交替调用ServerSocket的accept()方法来等待连接请求,同时启动后台线程处理连接(accept()返回的请求)。线程由ServerThread继承的start()方法开始,并执行ServerThread的run()方法中的代码。  一旦run()方法运行,线程将创建BufferedReader,PrintWriter和Calendar对象并进入一个循环,这个循环由读(通过BufferedReader的readLine())来自客户程序的一行文本开始,文本(命令)存储在cmd引用的string对象中,如果客户程序过早的关闭输出流,会发生什么呢?答案是:cmd将得不到赋值。  注意必须考虑到这种情况:在服务程序正在读输入流时,客户程序关闭了输出流,如果没有对这种情况进行处理,那么程序将产生异常。   一旦编译了SSServer的源代码,通过输入JavaSSServer来运行程序,在开始运行SSServer后,就可以运行一个或多个SSClient程序

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭