网络编程的目的就是指直接或间接地经过网络协议与其余计算机进行通信。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另外一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址能够惟一地肯定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,通常不须要关心IP层是如何处理数据的。 目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通讯双方一方做为服务器等待客户提出请求并予以响应。客户则在须要服务时向服务器提出申请。服务器通常做为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时本身继续监听服务端口,使后来的客户也能及时获得服务。 1.网络的基本概念 IP地址:标识计算机等网络设备的网络地址,由四个8位的二进制数组成,中间以小数点分隔。 如:166.111.136.3 , 166.111.52.80 主机名(hostname):网络地址的助记名,按照域名进行分级管理。 如:www.baidu.com www.fanso.com 端口号(port number):网络通讯时同一机器上的不一样进程的标识。 如:80,21,23,25,其中1~1024为系统保留的端口号 服务类型(service):网络的各类服务。 http, telnet, ftp, smtp 咱们能够用如下的一幅图来描述这里咱们所提到的几个概念: html 在Internet上IP地址和主机名是一一对应的,经过域名解析能够由主机名获得机器的IP,因为机器名更接近天然语言,容易记忆,因此使用比IP地址普遍,可是对机器而言只有IP地址才是有效的标识符。 一般一台主机上老是有不少个进程须要网络资源进行网络通信。网络通信的对象准确的讲不是主机,而应该是主机中运行的进程。这时候光有主机名或IP地址来标识这么多个进程显然是不够的。端口号就是为了在一台主机上提供更多的网络资源而采起得一种手段,也是TCP层提供的一种机制。只有经过主机名或IP地址和端口号的组合才能惟一的肯定网络通信中的对象:进程。 服务类型是在TCP层上面的应用层的概念。基于TCP/IP协议能够构建出各类复杂的应用,服务类型是那些已经被标准化了的应用,通常都是网络服务器(软件)。读者能够编写本身的基于网络的服务器,但都不能被称做标准的服务类型。 2.两类传输协议 TCP UDP TCP是Tranfer Control Protocol的简称,是一种面向链接的保证可靠传输的协议。经过TCP协议传输,获得的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须创建链接,以便在TCP协议的基础上进行通讯,当一个socket(一般都是server socket)等待创建链接时,另外一个socket能够要求进行链接,一旦这两个socket链接起来,它们就能够进行双向数据传输,双方均可以进行发送或接收操做。 UDP是User Datagram Protocol的简称,是一种无链接的协议,每一个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,所以可否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。 下面咱们对这两种协议作简单比较: 使用UDP时,每一个数据报中都给出了完整的地址信息,所以无须要创建发送方和接收方的链接。对于TCP协议,因为它是一个面向链接的协议,在socket之间进行数据传输以前必然要创建链接,因此在TCP中多了一个链接创建的时间。 使用UDP传输数据时是有大小限制的,每一个被传输的数据报必须限定在64KB以内。而TCP没有这方面的限制,一旦链接创建起来,双方的socket就能够按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不必定以相同的次序到达接收方。而TCP是一个可靠的协议,它确保接收方彻底正确地获取发送方所发送的所有数据。 总之,TCP在网络通讯上有极强的生命力,例如远程链接(Telnet)和文件传输(FTP)都须要不定长度的数据被可靠地传输。相比之下UDP操做简单,并且仅须要较少的监护,所以一般用于局域网高可靠性的分散系统中client/server应用程序。 既然有了保证可靠传输的TCP协议,为何还要非可靠传输的UDP协议呢?主要的缘由有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,所以TCP传输的效率不如UDP高。二是在许多应用中并不须要保证严格的传输可靠性,好比视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就能够了,这种状况下显然使用UDP会更合理一些。 3.基于URL高层次的Java网络编程 URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址。经过URL咱们能够访问Internet上的各类网络资源,好比最多见的WWW,FTP站点。浏览器经过解析给定的URL能够在网络上查找相应的文件或其余资源。 URL是最为直观的一种网络定位方法。使用URL符合人们的语言习惯,容易记忆,因此应用十分普遍。并且在目前使用最为普遍的TCP/IP中对于URL中主机名的解析也是协议的一个标准,即所谓的域名解析服务。使用URL进行网络编程,不须要对协议自己有太多的了解,功能也比较弱,相对而言是比较简单的。 4.URL的组成 protocol://resourceName 协议名(protocol)指明获取资源所使用的传输协议,如http、ftp、gopher、file等,资源名(resourceName)则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如: http://www.sun.com/ 协议名://主机名 http://home.netscape.com/home/welcome.html 协议名://机器名+文件名 http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 协议名://机器名+端口号+文件名+内部引用 5.建立一个URL java 为了表示URL, java.net中实现了类URL。咱们能够经过下面的构造方法来初始化一个URL对象: (1) public URL (String spec); 经过一个表示URL地址的字符串能够构造一个URL对象。 URL urlBase=new URL("http://www.163.com/") (2) public URL(URL context, String spec); 经过基URL和相对URL构造一个URL对象。 URL com163=new URL ("http://www.163.com/"); URL com163=new URL(com163, "index.html") (3) public URL(String protocol, String host, String file); new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html"); (4) public URL(String protocol, String host, int port, String file); URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html"); android 注意:类URL的构造方法都声明抛弃非运行时例外(MalformedURLException),所以生成URL对象时,咱们必需要对这一例外进行处理,一般是用try-catch语句进行捕获。格式以下: 程序员 try{ URL myURL= new URL(…) }catch (MalformedURLException e){ … //exception handler code here … } 6.解析一个URL 一个URL对象生成后,其属性是不能被改变的,可是咱们能够经过类URL所提供的方法来获取这些属性: public String getProtocol() 获取该URL的协议名。 public String getHost() 获取该URL的主机名。 public int getPort() 获取该URL的端口号,若是没有设置端口,返回-1。 public String getFile() 获取该URL的文件名。 public String getRef() 获取该URL在文件中的相对位置。 public String getQuery() 获取该URL的查询信息。 public String getPath() 获取该URL的路径 public String getAuthority() 获取该URL的权限信息 public String getUserInfo() 得到使用者的信息 public String getRef() 得到该URL的锚 下面的例子中,咱们生成一个URL对象,并获取它的各个属性。 编程
- import java.net.*;
- import java.io.*;
-
- public class ParseURL{
- public static void main (String [] args) throws Exception{
-
- URL Aurl=new URL("http://java.sun.com:80/docs/books/");
- URL tuto=new URL(Aurl,"tutorial.intro.html#DOWNLOADING");
- System.out.println("protocol="+ tuto.getProtocol());
- System.out.println("host ="+ tuto.getHost());
- System.out.println("filename="+ tuto.getFile());
- System.out.println("port="+ tuto.getPort());
- System.out.println("ref="+tuto.getRef());
- System.out.println("query="+tuto.getQuery());
- System.out.println("path="+tuto.getPath());
- System.out.println("UserInfo="+tuto.getUserInfo());
- System.out.println("Authority="+tuto.getAuthority());
- }
- }
复制代码
执行结果为: protocol=http host =java.sun.com filename=/docs/books/tutorial.intro.html port=80 ref=DOWNLOADING query=null path=/docs/books/tutorial.intro.html UserInfo=null Authority=java.sun.com:80 ============================================================ 小程序 java网络编程(2) 数组
7.从URL读取WWW网络资源 浏览器 当咱们获得一个URL对象后,就能够经过它读取指定的WWW资源。这时咱们将使用URL的方法openStream(),其定义为:InputStream openStream(); 服务器 方法openSteam()与指定的URL创建链接并返回InputStream类的对象以从这一链接中读取数据。
- public class URLReader {
- public static void main(String[] args) throws Exception {
- //声明抛出全部例外
- URL tirc = new URL("http://www.eoeandroid.com/");
- //构建一URL对象
- BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
- //使用openStream获得一输入流并由此构造一个BufferedReader对象
- String inputLine;
- while ((inputLine = in.readLine()) != null)
- //从输入流不断的读数据,直到读完为止
- System.out.println(inputLine); //把读入的数据打印到屏幕上
- in.close(); //关闭输入流
- }
- }
复制代码
8.经过URLConnetction链接WWW 经过URL的方法openStream(),咱们只能从网络上读取数据,若是咱们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,咱们必须先与URL创建链接,而后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行链接的接口,有关CGI程序设计,请读者参考有关书籍。 类URLConnection也在包java.net中定义,它表示Java程序和URL在网络上的通讯链接。当与一个URL创建链接时,首先要在一个URL对象上经过方法openConnection()生成对应的URLConnection对象。例以下面的程序段首先生成一个指向地址http://edu.chinaren.com/index.shtml的对象,而后用openConnection()打开该URL对象上的一个链接,返回一个URLConnection对象。若是链接过程失败,将产生IOException.
- Try{
- URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");
- URLConnectonn tc = netchinaren.openConnection();
- }catch(MalformedURLException e){ //建立URL()对象失败
- …
- }catch (IOException e){ //openConnection()失败
- …
- }
复制代码
类URLConnection提供了不少方法来设置或获取链接参数,程序设计时最常使用的是getInputStream()和getOurputStream(),其定义为: InputSteram getInputSteram(); OutputSteram getOutputStream(); 经过返回的输入/输出流咱们能够与远程对象进行通讯。看下面的例子:
- URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");
- //建立一URL对象
- URLConnectin con=url.openConnection();
- //由URL对象获取URLConnection对象
- DataInputStream dis=new DataInputStream (con.getInputSteam());
- //由URLConnection获取输入流,并构造DataInputStream对象
- PrintStream ps=new PrintSteam(con.getOutupSteam());
- //由URLConnection获取输出流,并构造PrintStream对象
- String line=dis.readLine(); //从服务器读入一行
- ps.println("client…"); //向服务器写出字符串 "client…"
复制代码
其中backwards为服务器端的CGI程序。实际上,类URL的方法openSteam()是经过URLConnection来实现的。它等价于 openConnection().getInputStream(); 基于URL的网络编程在底层其实仍是基于下面要讲的Socket接口的。WWW,FTP等标准化的网络服务都是基于TCP协议的,因此本质上讲URL编程也是基于TCP的一种应用。 |
============================================================ java网络编程(3)
9.基于Socket(套接字)的低层次Java网络编程 Socket通信 网络上的两个程序经过一个双向的通信链接实现数据的交换,这个双向链路的一端称为一个Socket。Socket一般用来实现客户方和服务方的链接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号惟一肯定。 在传统的UNIX环境下能够操做TCP/IP协议的接口不止Socket一个,Socket所支持的协议种类也不光TCP/IP一种,所以二者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。 说Socket编程是低层次网络编程并不等于它功能不强大,偏偏相反,正由于层次低,Socket编程比基于URL的网络编程提供了更强大的功能和更灵活的控制,可是却要更复杂一些。因为Java自己的特殊性,Socket编程在Java中可能已是层次最低的网络编程接口,在Java中要直接操做协议中更低的层次,须要使用Java的本地方法调用(JNI),在这里就不予讨论了。 Socket通信的通常过程 使用Socket进行Client/Server程序设计的通常链接过程是这样的:Server端Listen(监听)某个端口是否有链接请求,Client端向Server端发出Connect(链接)请求,Server端向Client端发回Accept(接受)消息。一个链接就创建起来了。Server端和Client端均可以经过Send,Write等方法与对方通讯。 对于一个功能齐全的Socket,都要包含如下基本结构,其工做过程包含如下四个基本的步骤: (1) 建立Socket; (2) 打开链接到Socket的输入/出流; (3) 按照必定的协议对Socket进行读/写操做; (4) 关闭Socket. 第三步是程序员用来调用Socket和实现程序功能的关键步骤,其余三步在各类程序中基本相同。 以上4个步骤是针对TCP传输而言的,使用UDP进行传输时略有不一样 建立Socket java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向链接的客户端和服务端。这是两个封装得很是好的类,使用很方便。其构造方法以下: Socket(InetAddress address, int port); Socket(InetAddress address, int port, boolean stream); Socket(String host, int prot); Socket(String host, int prot, boolean stream); Socket(SocketImpl impl) Socket(String host, int port, InetAddress localAddr, int localPort) Socket(InetAddress address, int port, InetAddress localAddr, int localPort) ServerSocket(int port); ServerSocket(int port, int backlog); ServerSocket(int port, int backlog, InetAddress bindAddr) 其中address、host和port分别是双向链接中另外一方的IP地址、主机名和端口号,stream指明socket是流socket仍是数据报socket,localPort表示本地主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既能够用来建立serverSocket又能够用来建立Socket。count则表示服务端所能支持的最大链接数。例如: Socket client = new Socket("127.0.01.", 80); ServerSocket server = new ServerSocket(80); 注意,在选择端口时,必须当心。每个端口提供一种特定的服务,只有给出正确的端口,才能得到相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 因此咱们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。 在建立socket时若是发生错误,将产生IOException,在程序中必须对之做出处理。因此在建立Socket或ServerSocket是必须捕获或抛出例外。 客户端的Socket 下面是一个典型的建立客户端Socket的过程。
- try{
- Socket socket=new Socket("127.0.0.1",4700);
- //127.0.0.1是TCP/IP协议中默认的本机地址
- }catch(IOException e){
- System.out.println("Error:"+e);
- }
复制代码
这是最简单的在客户端建立一个Socket的一个小程序段,也是使用Socket进行网络通信的第一步,程序至关简单,在这里不做过多解释了。 服务器端的ServerSocket 下面是一个典型的建立Server端ServerSocket的过程。
- ServerSocket server=null;
- try {
- server=new ServerSocket(4700);
- //建立一个ServerSocket在端口4700监听客户请求
- }catch(IOException e){
- System.out.println("can not listen to :"+e);
- }
- Socket socket=null;
- try {
- socket=server.accept();
- //accept()是一个阻塞的方法,一旦有客户请求,它就会返回一个Socket对象用于同客户进行交互
- }catch(IOException e){
- System.out.println("Error:"+e);
- }
复制代码
以上的程序是Server的典型工做模式,只不过在这里Server只能接收一个请求,接受完后Server就退出了。实际的应用中老是让它不停的循环接收,一旦有客户请求,Server老是会建立一个服务线程来服务新来的客户,而本身继续监听。程序中accept()是一个阻塞函数,所谓阻塞性方法就是说该方法被调用后,将等待客户的请求,直到有一个客户启动并请求链接到相同的端口,而后accept()返回一个对应于客户的socket。这时,客户方和服务方都创建了用于通讯的socket,接下来就是由各个socket分别打开各自的输入/输出流。 打开输入/出流 类Socket提供了方法getInputStream ()和getOutStream()来获得对应的输入/输出流以进行读/写操做,这两个方法分别返回InputStream和OutputSteam类对象。为了便于读/写数据,咱们能够在返回的输入/输出流对象上创建过滤流,如DataInputStream、DataOutputStream或PrintStream类对象,对于文本方式流对象,能够采用InputStreamReader和OutputStreamWriter、PrintWirter等处理。 例如: PrintStream os=new PrintStream(new BufferedOutputStreem(socket.getOutputStream())); DataInputStream is=new DataInputStream(socket.getInputStream()); PrintWriter out=new PrintWriter(socket.getOutStream(),true); BufferedReader in=new ButfferedReader(new InputSteramReader(Socket.getInputStream())); 输入输出流是网络编程的实质性部分,具体如何构造所须要的过滤流,要根据须要而定,可否运用自如主要看读者对Java中输入输出部分掌握如何。 关闭Socket 每个Socket存在时,都将占用必定的资源,在Socket对象使用完毕时,要其关闭。关闭Socket能够调用Socket的Close()方法。在关闭Socket以前,应将与Socket相关的全部的输入/输出流所有关闭,以释放全部的资源。并且要注意关闭的顺序,与Socket相关的全部的输入/输出该首先关闭,而后再关闭Socket。 os.close(); is.close(); socket.close(); 尽管Java有自动回收机制,网络资源最终是会被释放的。可是为了有效的利用资源,建议按照合理的顺序主动释放资源。 10.简单的Client/Server程序设计 客户端程序
- import java.io.*;
- import java.net.*;
- public class TalkClient {
- public static void main(String args[]) {
- try{
- Socket socket=new Socket("127.0.0.1",4700);
- //向本机的4700端口发出客户请求
- BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
- //由系统标准输入设备构造BufferedReader对象
- PrintWriter os=new PrintWriter(socket.getOutputStream());
- //由Socket对象获得输出流,并构造PrintWriter对象
- BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
- //由Socket对象获得输入流,并构造相应的BufferedReader对象
- String readline;
- readline=sin.readLine(); //从系统标准输入读入一字符串
- while(!readline.equals("bye")){
- //若从标准输入读入的字符串为 "bye"则中止循环
- os.println(readline);
- //将从系统标准输入读入的字符串输出到Server
- os.flush();
- //刷新输出流,使Server立刻收到该字符串
- System.out.println("Client:"+readline);
- //在系统标准输出上打印读入的字符串
- System.out.println("Server:"+is.readLine());
- //从Server读入一字符串,并打印到标准输出上
- readline=sin.readLine(); //从系统标准输入读入一字符串
- } //继续循环
- os.close(); //关闭Socket输出流
- is.close(); //关闭Socket输入流
- socket.close(); //关闭Socket
- }catch(Exception e) {
- System.out.println("Error"+e); //出错,则打印出错信息
- }
- }
- }
复制代码
服务器端程序
- import java.io.*;
- import java.net.*;
- import java.applet.Applet;
- public class TalkServer{
- public static void main(String args[]) {
- try{
- ServerSocket server=null;
- try{
- server=new ServerSocket(4700);
- //建立一个ServerSocket在端口4700监听客户请求
- }catch(Exception e) {
- System.out.println("can not listen to:"+e);
- //出错,打印出错信息
- }
-
- Socket socket=null;
- try{
- socket=server.accept();
- //使用accept()阻塞等待客户请求,有客户
- //请求到来则产生一个Socket对象,并继续执行
- }catch(Exception e) {
- System.out.println("Error."+e);
- //出错,打印出错信息
- }
- String line;
- BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
- //由Socket对象获得输入流,并构造相应的BufferedReader对象
- PrintWriter os=newPrintWriter(socket.getOutputStream());
- //由Socket对象获得输出流,并构造PrintWriter对象
- BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
- //由系统标准输入设备构造BufferedReader对象
-
- System.out.println("Client:"+is.readLine());
- //在标准输出上打印从客户端读入的字符串
- line=sin.readLine();
- //从标准输入读入一字符串
- while(!line.equals("bye")){
- //若是该字符串为 "bye",则中止循环
- os.println(line);
- //向客户端输出该字符串
- os.flush();
- //刷新输出流,使Client立刻收到该字符串
- System.out.println("Server:"+line);
- //在系统标准输出上打印读入的字符串
- System.out.println("Client:"+is.readLine());
- //从Client读入一字符串,并打印到标准输出上
- line=sin.readLine();
- //从系统标准输入读入一字符串
- } //继续循环
- os.close(); //关闭Socket输出流
- is.close(); //关闭Socket输入流
- socket.close(); //关闭Socket
- server.close(); //关闭ServerSocket
- }catch(Exception e){
- System.out.println("Error:"+e);
- //出错,打印出错信息
- }
- }
- }
复制代码
从上面的两个程序中咱们能够看到,socket四个步骤的使用过程。读者能够分别将Socket使用的四个步骤的对应程序段选择出来,这样便于读者对socket的使用有进一步的了解。 最好是能在真正的网络环境下试验该程序,这样更容易分辨输出的内容和客户机,服务器的对应关系。同时也能够修改该程序,提供更为强大的功能。 |
================================================================ java网络编程(4) 11.支持多客户的client/server程序设计 客户端程序:MultiTalkClient.java
- import java.io.*;
- import java.net.*;
- public class MultiTalkClient {
- public static void main(String args[]) {
- try{
- Socket socket=new Socket("127.0.0.1",4700);
- //向本机的4700端口发出客户请求
- BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
- //由系统标准输入设备构造BufferedReader对象
- PrintWriter os=new PrintWriter(socket.getOutputStream());
- //由Socket对象获得输出流,并构造PrintWriter对象
- BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
- //由Socket对象获得输入流,并构造相应的BufferedReader对象
- String readline;
- readline=sin.readLine(); //从系统标准输入读入一字符串
- while(!readline.equals("bye")){
- //若从标准输入读入的字符串为 "bye"则中止循环
- os.println(readline);
- //将从系统标准输入读入的字符串输出到Server
- os.flush();
- //刷新输出流,使Server立刻收到该字符串
- System.out.println("Client:"+readline);
- //在系统标准输出上打印读入的字符串
- System.out.println("Server:"+is.readLine());
- //从Server读入一字符串,并打印到标准输出上
- readline=sin.readLine();
- //从系统标准输入读入一字符串
- } //继续循环
- os.close(); //关闭Socket输出流
- is.close(); //关闭Socket输入流
- socket.close(); //关闭Socket
- }catch(Exception e) {
- System.out.println("Error"+e); //出错,则打印出错信息
- }
- }
- }
复制代码
服务器端程序: MultiTalkServer.java
- import java.io.*;
- import java.net.*;
- import ServerThread;
- public class MultiTalkServer{
- static int clientnum=0; //静态成员变量,记录当前客户的个数
- public static void main(String args[]) throws IOException {
- ServerSocket serverSocket=null;
- boolean listening=true;
- try{
- serverSocket=new ServerSocket(4700);
- //建立一个ServerSocket在端口4700监听客户请求
- }catch(IOException e) {
- System.out.println("Could not listen on port:4700.");
- //出错,打印出错信息
- System.exit(-1); //退出
- }
- while(listening){ //永远循环监听
- new ServerThread(serverSocket.accept(),clientnum).start();
- //监听到客户请求,根据获得的Socket对象和
- 客户计数建立服务线程,并启动之
- clientnum++; //增长客户计数
- }
- serverSocket.close(); //关闭ServerSocket
- }
- }
复制代码
程序ServerThread.java
- import java.io.*;
- import java.net.*;
- public class ServerThread extends Thread{
- Socket socket=null; //保存与本线程相关的Socket对象
- int clientnum; //保存本进程的客户计数
- public ServerThread(Socket socket,int num) { //构造函数
- this.socket=socket; //初始化socket变量
- clientnum=num+1; //初始化clientnum变量
- }
- public void run() { //线程主体
- try{
- String line;
- BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
- //由Socket对象获得输入流,并构造相应的BufferedReader对象
- PrintWriter os=newPrintWriter(socket.getOutputStream());
- //由Socket对象获得输出流,并构造PrintWriter对象
- BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
- //由系统标准输入设备构造BufferedReader对象
- System.out.println("Client:"+ clientnum +is.readLine());
- //在标准输出上打印从客户端读入的字符串
- line=sin.readLine();
- //从标准输入读入一字符串
- while(!line.equals("bye")){
- //若是该字符串为 "bye",则中止循环
- os.println(line);
- //向客户端输出该字符串
- os.flush();
- //刷新输出流,使Client立刻收到该字符串
- System.out.println("Server:"+line);
- //在系统标准输出上打印该字符串
- System.out.println("Client:"+ clientnum +is.readLine());
- //从Client读入一字符串,并打印到标准输出上
- line=sin.readLine();
- //从系统标准输入读入一字符串
- } //继续循环
- os.close(); //关闭Socket输出流
- is.close(); //关闭Socket输入流
- socket.close(); //关闭Socket
- server.close(); //关闭ServerSocket
- }catch(Exception e){
- System.out.println("Error:"+e);
- //出错,打印出错信息
- }
- }
- }
复制代码
======================================================== java网络编程(5) 12.据报Datagram通信 前面在介绍TCP/IP协议的时候,咱们已经提到,在TCP/IP协议的传输层除了TCP协议以外还有一个UDP协议,相比而言UDP的应用不如TCP普遍,几个标准的应用层协议HTTP,FTP,SMTP…使用的都是TCP协议。可是,随着计算机网络的发展,UDP协议正愈来愈来显示出其威力,尤为是在须要很强的实时交互性的场合,如网络游戏,视频会议等,UDP更是显示出极强的威力,下面咱们就介绍一下Java环境下如何实现UDP网络传输。 13.什么是Datagram 所谓数据报(Datagram)就跟平常生活中的邮件系统同样,是不能保证可靠的寄到的,而面向连接的TCP就比如电话,双方能确定对方接受到了信息。在本章前面,咱们已经对UDP和TCP进行了比较,在这里再稍做小节: TCP,可靠,传输大小无限制,可是须要链接创建时间,差错控制开销大。 UDP,不可靠,差错控制开销较小,传输大小限制在64K如下,不须要创建链接。 总之,这两种协议各有特色,应用的场合也不一样,是彻底互补的两个协议,在TCP/IP协议中占有一样重要的地位,要学好网络编程,二者缺一不可。 14.Datagram通信的表示方法:DatagramSocket;DatagramPacket包 java.net中提供了两个类DatagramSocket和DatagramPacket用来支持数据报通讯,DatagramSocket用于在程序之间创建传送数据报的通讯链接, DatagramPacket则用来表示一个数据报。先来看一下DatagramSocket的构造方法: DatagramSocket(); DatagramSocket(int prot); DatagramSocket(int port, InetAddress laddr) 其中,port指明socket所使用的端口号,若是未指明端口号,则把socket链接到本地主机上一个可用的端口。laddr指明一个可用的本地地址。给出端口号时要保证不发生端口冲突,不然会生成SocketException类例外。注意:上述的两个构造方法都声明抛弃非运行时例外SocketException,程序中必须进行处理,或者捕获、或者声明抛弃。 用数据报方式编写client/server程序时,不管在客户方仍是服务方,首先都要创建一个DatagramSocket对象,用来接收或发送数据报,而后使用DatagramPacket类对象做为传输数据的载体。下面看一下DatagramPacket的构造方法 : DatagramPacket(byte buf[],int length); DatagramPacket(byte buf[], int length, InetAddress addr, int port); DatagramPacket(byte[] buf, int offset, int length); DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port); 其中,buf中存放数据报数据,length为数据报中数据的长度,addr和port旨明目的地址,offset指明了数据报的位移量。 在接收数据前,应该采用上面的第一种方法生成一个DatagramPacket对象,给出接收数据的缓冲区及其长度。而后调用DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。 DatagramPacket packet=new DatagramPacket(buf, 256); Socket.receive (packet); 发送数据前,也要先生成一个新的DatagramPacket对象,这时要使用上面的第二种构造方法,在给出存放发送数据的缓冲区的同时,还要给出完整的目的地址,包括IP地址和端口号。发送数据是经过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径,以传递数据报。 DatagramPacket packet=new DatagramPacket(buf, length, address, port); Socket.send(packet); 在构造数据报时,要给出InetAddress类参数。类InetAddress在包java.net中定义,用来表示一个Internet地址,咱们能够经过它提供的类方法getByName()从一个表示主机名的字符串获取该主机的IP地址,而后再获取相应的地址信息。 15.基于UDP的简单的Client/Server程序设计 客户方程序 QuoteClient.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class QuoteClient {
- public static void main(String[] args) throws IOException
- {
- if(args.length!=1) {
- //若是启动的时候没有给出Server的名字,那么出错退出
- System.out.println("Usage:java QuoteClient <hostname>");
- //打印出错信息
- return; //返回
- }
-
- DatagramSocket socket=new DatagramSocklet();
- //建立数据报套接字
-
- Byte[] buf=new byte[256]; //建立缓冲区
- InetAddress address=InetAddress.getByName(args [0]);
- //由命令行给出的第一个参数默认为Server的名字,经过它获得Server的IP信息
- DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445);
- //建立DatagramPacket对象
- socket.send(packet); //发送
- packet=new DatagramPacket(buf,buf.length);
- //建立新的DatagramPacket对象,用来接收数据报
- socket.receive(packet); //接收
- String received=new String(packet.getData());
- //根据接收到的字节数组生成相应的字符串
- System.out.println("Quote of the Moment:"+received );
- //打印生成的字符串
-
- socket.close(); //关闭套接口
- }
- }
复制代码
服务器方程序:QuoteServer.java
- public class QuoteServer{
- public static void main(String args[]) throws java.io.IOException
- {
- new QuoteServerThread().start();
- //启动一个QuoteServerThread线程
- }
- }
复制代码
程序QuoteServerThread.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- //服务器线程
- public class QuoteServerThread extends Thread
- {
- protected DatagramSocket socket=null;
- //记录和本对象相关联的DatagramSocket对象
- protected BufferedReader in=null;
- //用来读文件的一个Reader
- protected boolean moreQuotes=true;
- //标志变量,是否继续操做
-
- public QuoteServerThread() throws IOException {
- //无参数的构造函数
- this("QuoteServerThread");
- //以QuoteServerThread为默认值调用带参数的构造函数
- }
- public QuoteServerThread(String name) throws IOException {
- super(name); //调用父类的构造函数
- socket=new DatagramSocket(4445);
- //在端口4445建立数据报套接字
- try{
- in= new BufferedReader(new FileReader(" one-liners.txt"));
- //打开一个文件,构造相应的BufferReader对象
- }catch(FileNotFoundException e) { //异常处理
- System.err.println("Could not open quote file. Serving time instead.");
- //打印出错信息
- }
- }
- public void run() //线程主体
- {
- while(moreQuotes) {
- try{
- byte[] buf=new byte[256]; //建立缓冲区
- DatagramPacket packet=new DatagramPacket(buf,buf.length);
- //由缓冲区构造DatagramPacket对象
- socket.receive(packet); //接收数据报
- String dString=null;
- if(in= =null) dString=new Date().toString();
- //若是初始化的时候打开文件失败了,
- //则使用日期做为要传送的字符串
- else dString=getNextQuote();
- //不然调用成员函数从文件中读出字符串
- buf=dString.getByte();
- //把String转换成字节数组,以便传送
-
- InetAddress address=packet.getAddress();
- //从Client端传来的Packet中获得Client地址
- int port=packet.getPort(); //和端口号
- packet=new DatagramPacket(buf,buf.length,address,port);
- //根据客户端信息构建DatagramPacket
- socket.send(packet); //发送数据报
- }catch(IOException e) { //异常处理
- e.printStackTrace(); //打印错误栈
- moreQuotes=false; //标志变量置false,以结束循环
- }
- }
- socket.close(); //关闭数据报套接字
- }
-
- protected String getNextQuotes(){
- //成员函数,从文件中读数据
- String returnValue=null;
- try {
- if((returnValue=in.readLine())= =null) {
- //从文件中读一行,若是读到了文件尾
- in.close( ); //关闭输入流
- moreQuotes=false;
- //标志变量置false,以结束循环
- returnValue="No more quotes. Goodbye.";
- //置返回值
- } //不然返回字符串即为从文件读出的字符串
- }catch(IOEception e) { //异常处理
- returnValue="IOException occurred in server";
- //置异常返回值
- }
- return returnValue; //返回字符串
- }
- }
复制代码
能够看出使用UDP和使用TCP在程序上仍是有很大的区别的。一个比较明显的区别是,UDP的Socket编程是不提供监听功能的,也就是说通讯双方更为平等,面对的接口是彻底同样的。可是为了用UDP实现C/S结构,在使用UDP时可使用DatagramSocket.receive()来实现相似于监听的功能。由于receive()是阻塞的函数,当它返回时,缓冲区里已经填满了接受到的一个数据报,而且能够从该数据报获得发送方的各类信息,这一点跟accept()是很相象的,于是能够根据读入的数据报来决定下一步的动做,这就达到了跟网络监听类似的效果。 16.用数据报进行广播通信 客户方程序:MulticastClient.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class MulticastClient {
- public static void main(String args[]) throws IOException
- {
- MulticastSocket socket=new MulticastSocket(4446);
- //建立4446端口的广播套接字
- InetAddress address=InetAddress.getByName("230.0.0.1");
- //获得230.0.0.1的地址信息
- socket.joinGroup(address);
- //使用joinGroup()将广播套接字绑定到地址上
- DatagramPacket packet;
-
- for(int i=0;i<5;i++) {
- byte[] buf=new byte[256];
- //建立缓冲区
- packet=new DatagramPacket(buf,buf.length);
- //建立接收数据报
- socket.receive(packet); //接收
- String received=new String(packet.getData());
- //由接收到的数据报获得字节数组,
- //并由此构造一个String对象
- System.out.println("Quote of theMoment:"+received);
- //打印获得的字符串
- } //循环5次
- socket.leaveGroup(address);
- //把广播套接字从地址上解除绑定
- socket.close(); //关闭广播套接字
- }
- }
复制代码
服务器方程序:MulticastServer.java
- public class MulticastServer{
- public static void main(String args[]) throws java.io.IOException
- {
- new MulticastServerThread().start();
- //启动一个服务器线程
- }
- }
复制代码
程序MulticastServerThread.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class MulticastServerThread extends QuoteServerThread
- //从QuoteServerThread继承获得新的服务器线程类MulticastServerThread
- {
- Private long FIVE_SECOND=5000; //定义常量,5秒钟
- public MulticastServerThread(String name) throws IOException
- {
- super("MulticastServerThread");
- //调用父类,也就是QuoteServerThread的构造函数
- }
-
- public void run() //重写父类的线程主体
- {
- while(moreQuotes) {
- //根据标志变量判断是否继续循环
- try{
- byte[] buf=new byte[256];
- //建立缓冲区
- String dString=null;
- if(in==null) dString=new Date().toString();
- //若是初始化的时候打开文件失败了,
- //则使用日期做为要传送的字符串
- else dString=getNextQuote();
- //不然调用成员函数从文件中读出字符串
- buf=dString.getByte();
- //把String转换成字节数组,以便传送send it
- InetAddress group=InetAddress.getByName("230.0.0.1");
- //获得230.0.0.1的地址信息
- DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
- //根据缓冲区,广播地址,和端口号建立DatagramPacket对象
- socket.send(packet); //发送该Packet
- try{
- sleep((long)(Math.random()*FIVE_SECONDS));
- //随机等待一段时间,0~5秒之间
- }catch(InterruptedException e) { } //异常处理
- }catch(IOException e){ //异常处理
- e.printStackTrace( ); //打印错误栈
-
- moreQuotes=false; //置结束循环标志
- }
- }
- socket.close( ); //关闭广播套接口
- }
- }
复制代码
至此,Java网络编程这一章已经讲解完毕。读者经过学习,应该对网络编程有了一个清晰的认识,可能对某些概念还不是十分的清楚,仍是须要更多的实践来进一步掌握。编程语言的学习不一样于通常的学习,及其强调实践的重要性。读者应该对URL网络编程,Socket中的TCP,UDP编程进行大量的练习才能更好的掌握本章中所提到的一些概念,才能真正学到Java网络编程的精髓! 最后几个小节所举的例子,务必要亲自试验一下,若是遇到问题,想办法解决之。最好能根据本身的意图加以改进。这样才能更好的理解这几个程序,理解其中所包含的编程思想。 小结: 主要讲解了Java环境下的网络编程。由于TCP/IP协议是Java网络编程的基础知识,因此开篇重点介绍了TCP/IP协议中的一些概念,TCP/IP协议自己是一个十分庞大的系统,用几个小节是不可能讲清楚的。因此咱们只是联系实际,讲解了一些最基本的概念,帮助学生理解后面的相关内容。重点有一下几个概念:主机名,IP,端口,服务类型,TCP,UDP。后续的内容分为两大块,一块是以URL为主线,讲解如何经过URL类和URLConnection类访问WWW网络资源,因为使用URL十分方便直观,尽管功能不是很强,仍是值得推荐的一种网络编程方法,尤为是对于初学者特别容易接受。本质上讲,URL网络编程在传输层使用的仍是TCP协议。 另外一块是以Socket接口和C/S网络编程模型为主线,依次讲解了如何用Java实现基于TCP的C/S结构,主要用到的类有Socket,ServerSocket。以及如何用Java实现基于UDP的C/S结构,还讨论了一种特殊的传输方式,广播方式,这种方式是UDP所特有的,主要用到的类有DatagramSocket , DatagramPacket, MulticastSocket。这一块在Java网络编程中相对而言是最难的(尽管Java在网络编程这方面已经作的够"傻瓜"了,可是网络编程在其余环境下的倒是一件极为头痛的事情,再"傻瓜"仍是有必定的难度),也是功能最为强大的一部分,应该好好研究,领悟其中的思想。 最后要强调的是要学好Java网络编程,Java语言,最重要的仍是在于多多练习! 转自:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=48337 |