Java语言程序设计 清华大学出版社 第9章 网络通信
主要内容 1、网络编程基础 2、基于TCP网络程序设计 3、基于UDP网络程序设计 4、* 基于C/S模式的分布计算
一、 Java与网络 1、网络数据通信的几个重要协议 IP协议、 TCP协议和UDP协议。
IP 数据报结构 源IP地址 目的IP地址
传输层数据报文结构:TCP
传输层数据报结构:UDP报文
2、端口号 端口号:TCP/IP协议为每种服务定义了一个端口。 服务 端口号 ftp 21 http 80 pop3 110
3、 套接字Socket Socket是网络程序中最常用的方式,用于建立两个不同程序之间通过网络进行通信的信道。
2、 套接字Socket 什么是socket ? 套接字是IP与端口的组合,可以分辨在internet上运行的程序 网络服务 IP,port Mail ftp finger 20 telnet 数据结构 I/O缓冲区 端口 21 23 客户程序 Socket IP,port 套接字是IP与端口的组合,可以分辨在internet上运行的程序
进程与进程之间建立起连接
建立套接字Socket 客户端呼叫 在客户端使用socket类,指定服务器IP和端口号,以便连到服务器上: Socket(host_IP, prot); 在服务器端使用ServerSocket类,以等待客户端的连接: ServerSocket(port); 服务器等待
Socket主要方法: 在客户端:建立socket连接后,还应该建立输入输出数据流。 getInputStream( ) 获得输入流 getOutputStream( ) 获得输出流 在服务器端: ServerSocket类的accept( )方法使服务器处于阻塞状态,等待用户请求。
二、TCP协议 Socket编程 Client Server Socket(host, port #) ServerSocket(port #) Server Socket.accept() Socket() OutputStream InputStream Close Socket Client Socket(host, port #) Attempt the connection OutputStream InputStream Close Socket
例1:演示服务器与客户之间的交互,服务器等待,客户访问,相互通一次信息。 客户端向服务器发出信息:“你好”,服务器接收到信息后,给出回应:“你好,我是服务器。”
21.5 Socket . 21.5 Socket 定义数据成员 定义数据成员 4331 创建Socket实例 创建服务器(端口号) waiting for user 127.0.0.1 4331 服务器等待 网络连接 建立socket流 读socket流 (接收并显示) 建立socket流 connetcting client... “这里是服务器" 向用户发出确认字符串 送用户名给 服务器 你好 读客户 端信息 读socket流 提示用户登录成功 关闭流
客户机端 import java.io.*; import java.net.*; class C { public static void main(String[] args) try{ Socket socket=new Socket("127.0.0.1",6565); DataOutputStream out=new DataOutputStream(socket.getOutputStream()); out.writeUTF("我是客户机"); DataInputStream in=new DataInputStream(socket.getInputStream()); String s=in.readUTF(); System.out.println("客户机收到:"+s); }catch(Exception e){} }
服务器端 import java.io.*; import java.net.*; class S { public static void main(String[] args) try{ ServerSocket s_socket=new ServerSocket(6565); Socket socket=s_socket.accept(); DataInputStream in=new DataInputStream(socket.getInputStream()); String s=in.readUTF(); System.out.println("服务器收到:"+s); DataOutputStream out=new DataOutputStream(socket.getOutputStream()); out.writeUTF("我是服务器"); }catch(Exception e){} }
21.5 Socket 显示服务器与客户机间的通信(服务器端) DataOutputStream out=null; DataInputStream in=null; ServerSocket s_socket=null; Socket c_socket=null; String s; //存放读取到的信息 定义数据成员 创建服务器(端口号) try { s_socket=new ServerSocket(4331); }catch (IOException e){ } 服务器等待 网络连接 try { c_socket=s_socket.accept(); …… }catch (IOException e){ } 用循环语句收发信息 (见下页)
DataInPutStream(clientSocket.getInputStream()); out=new 创建服务器(端口号) 定义数据成员 服务器等待 网络连接 建立socket流 等待客户登录 读客户 端信息 in=new DataInPutStream(clientSocket.getInputStream()); out=new DataOutputStream(clientSocket.getOutputStream()); While (true) { s=in.readUTF( ); if ( s!=null ) break; } out.writeUTF(“这里是服务器"); clientSocket.close();
DataOutputStream out=null; DataInputStream in=null; String string; Socket socket=null; 定义数据成员 创建Socket实例 try{ socket=new Socket("127.0.0.1",4331); 建立socket流 input=new DataInputStream(socket.getInputStream()); output=new PrintStream(socket.getOutputStream()); catch(IOException e){ System.out.println(“无法连接 ”); }
System.out.println(input.readLine()); 创建Socket实例 定义数据成员 建立socket流 Out.writeUTF(“你好”); System.out.println(input.readLine()); 读socket流 (看到提示) While (true) { s=in.readUTF( ); if ( s!=null ) break; } 从键盘上读送用户 名送给服务器端 读服务器反馈 mysocket.close(); System.out.println(“s"); 关闭流
考虑多用户 第一种解决方案: 一台计算机上一次启动多个服务器程序,只是端口号不同。 myserver <-------->myclient----f21.bat myserver2<-------->myclient2----f9.bat Server1(1111) client(2222) Server2(2222) client(1111)
多用户的第二种方案 (支持多客户) 将服务器写成多线程的,不同的线程为不同的客户服务. main()只负责循环等待 serverthread serverthread (支持多客户) client1 Server client2 将服务器写成多线程的,不同的线程为不同的客户服务. main()只负责循环等待 线程负责网络连接,接收客户输入的信息
服务器 客户1 线程1( ) 客户2 线程2( ) 线程run() 下面简单分析 教材P276 例4
客户端 socket client() getInputStream getOutputStream writer.start() 服 务 器 In.readUTF() Run( ) class reader extends Thread {server s; int index; public reader(server s, int index) { this.s=s; this.index=index;} public void run() { setPriority(MIN_PRIORITY); s.write_net_output(s.net_output[index],"WAIT"); while (s.client_counter<2) {s.write_net_output(s.net_output[index]," GO..."); while(true) { String string=s.read_net_input(s.net_input[index]); if(string==null) break; System.out.println(s.user[index]+":"+string); s.write_net_output(s.net_output[(index+1)%2],string); } System.out.println(s.user[index]+" has disconnected."); s.user[index]=null; if(--s.client_counter==0) System.exit(0); 按钮事件() Out.writeUTF()
服务器端 . 服务器一端为了能接收多个客户的信息,它的输入流,输出流都是数组型的. (4321) accept socket IP client1 IP client2 客 户 客 户 . 线程 线程 服务器一端为了能接收多个客户的信息,它的输入流,输出流都是数组型的. ServerSocket.accept()等待用户连接,一旦连接上,则调用服务程序. 服务程序的主要内容是网络的读写,多客户的原因,网络读写的功能用多线程实现,因此将此部分功能单独分离出来,构成线程类 public void service_request(Socket socket) { InputStream input; OutputStream output; try{input=socket.getInputStream(); output=socket.getOutputStream(); }catch(IOException e){System.out.println("Unable to get input/output streams");return;} if(client_counter>=2){write_net_output(output,"There are already two clients");return; } net_input[client_counter]=input; net_output[client_counter]=output; user[client_counter]=read_net_input_line(input); System.out.print(user[client_counter]+" Connected!"+socket.toString()); (new reader(this,client_counter)).start(); int c=client_counter++; } String read_net_input_line(InputStream input) { String line="";String c; c=read_net_input(input); while((byte)c.charAt(0)!=10) { line=line+c;c=read_net_input(input);} return line;
服务器端 ServerSocket server Socket client1 accept() client2 serviceRequest read_net_input getInputStream write_net_output() getOutputStream String read_net_input(InputStream input) { byte bytes[]; int number_of_bytes; try{ bytes=new byte[1]; number_of_bytes=input.read(bytes,0,1); if(number_of_bytes>0) return(new String(bytes,0,0,number_of_bytes)); else return null; }catch(IOException e){return null;} } void write_net_output(OutputStream output,String string) { byte byte_array[]; int length=string.length(); byte_array=new byte[length]; string.getBytes(0,length,byte_array,0); try{output.write(byte_array);}catch(IOException e){;} reader.start() reader.run reader.run
在Java中,网络类都在 java.net 类库中。 用来存储internet地址的类InetAddress. 其主要方法: getHostName( ) 获取InetAddress对象的主机名或域名 getHostAddress( ) 获取InetAddress对象的IP地址 getLocalHost( )获取本机的IP地址 程序如何寻找网上的特定设备,程序发送这些设备所必须使用的格式,程序如何差错以及诸如此类的事情.
例:获取本机的IP地址 import java.net.*; public class getLocalHostTest { public static void main(String args[]) { InetAddress myIP=null; try {myIP=InetAddress.getLocalHost();} catch(UnknownHostException e){} System.out.println(myIP); } 创建inetAddress类不用构造函数(不用new)
Java与网络 下面的例子演示java如何根据域名自动到DNS 上查找IP地址(与DNS服务器的连接减至一行) import java.net.*; public class getIP { public static void main(String args[]) { InetAddress pku=null; try{ pku= InetAddress.getByName(“www.pku.edu.cn”); } catch(UnknownHostException e) {} System.out.println(pku); } }
三、基于数据报Datagram TCP/IP传输层由两个并列的协议:TCP,UDP. UDP数据报的每个数据包要包含目的地址和端口号. socket Socket要求数据按照发送的次序接收,包丢失要重发,浪费网络资源,没有保持的连接和数据流,UDP的价值在于效率,如果通信子网可靠UDP大有用武之处.TCP和UDP都由端口的概念.TCP的可靠来源于确认和超时重传.要求先建立连接,传输后关闭,次序不变. UDP不需要实现连接,数据报是独立的,没有次序要求路径不同. server client Datagram数据报 server client
接收时,用 receive( )方法接收数据。 (2) DatagramPacket 类 用于打包或拆包 发送时打包: 在UDP中,要使用二个类: (1) DatagramSocket 类 发送时,用 send( )方法发送数据; 接收时,用 receive( )方法接收数据。 (2) DatagramPacket 类 用于打包或拆包 发送时打包: 包由数据、接收地址、端口号组成; 接收时拆包: 取出包中的数据、接收地址、端口号。 TCP非常复杂,因为要包容通信子网的不可靠因素 可靠并以一定比效率重要,视情况而定
Datagram 工作过程 建立数据报socket(); 建立数据报socket 建立一个报文包packet 建立一个请求包 发出请求 等待请求报文 创建接收包 获得对方地址 等待接收 构成信息包 发送出去
Datagram 一、服务器端发出报文的步骤如下: 1. 定义数据成员 DatagramSocket socket; DatagramPacket packet; InetAddress address;(用来存放接收方的地址) int port; ;(用来存放接收方的端口号) 2. 创建数据报文Socket对象 try {socket=new DatagramSocket(1111);} catch(java.net.SocketException e) {} socket 绑定到一个本地的可用端口,等待接收客户的请求.
Datagram 3.分配并填写数据缓冲区(一个字节类型的数组) byte[] Buf=new byte[256]; 存放从客户端接收的请求信息. 4.创建一个DatagramPacket packet=new DatagramPacket(Buf数组, 256长度); 用来从socket接收数据,它只有两个参数 5. 服务器阻塞 socket.receive(packet); 在客户的请求报道来之前一直等待
Datagram 6. 从到来的包中得到地址和端口号 InetAddress address=packet.getAddress(); int port=packet.getPort(); 7. 将数据送入缓冲区 或来自文件,或键盘输入 8. 建立报文包,用来从socket上发送信息 packet=new DatagramPacket (buf,buf.length, address,port); 9. 发送数据包 10.关闭socket socket.send(packet); socket.close();
Datagram 二、客户端接收包的步骤如下: 1. 定义数据成员 int port; InetAddress address; DatagramSocket socket; DatagramPacket packet; byte[] sendBuf=new byte[256]; 2. 建立socket socket=new DatagramSocket();
Datagram 3. 向服务器发出请求报文 address=InetAddress.getByName(args[0]); port=parseInt(args[1]); packet=new DatagramPacket(sendBuf,256,address,port); socket.send(packet); 这个包本身带有客户端的信息 4. 客户机等待应答 packet=new DatagramPacket(sendBuf,256); socket.receive(packet);(如果没有到就一直等待,因此实用程序要设置时间限度)
Datagram 5. 处理接收到的数据 String received=new String(packet.getData(),0); System.out.println(received); 数据报套接字首先是强调发送方和接收方的区别,同时也指出服务器和客户之间的不同: 一个客户机必须事先知道服务器的地址和端口,以便进行出事连接 一个服务器从它接收到的数据报文中读取客户端的地址和端口.
小结 实现网络功能要靠URL类, URLConection类, Socket类和DatagramSocket类 网络上的数据传送是将网络连接转换成输入输出流 DataInputStream和DataOutputStream (PrintStream)是网间流的载体. URL适用于web应用,如访问http服务器是高层服务
小结 环回地址可用于在本地机器上调试网络程序 Socket适用于面向连接的,可靠性要求高的应用 Datagram适用于效率要求高的应用 Socket是由IP和端口构成的一种网上通信链路的一端 Socket通信要分别运行服务器和客户程序 服务器程序是多线程的,可处理多个客户的请求
作业 编写一个会话程序 要求: 会话双方可以自由通话,看到对方发来“bye”则退出