分布式系统-Socket编程.ppt
1,Socket编程,参考:Java 网络编程原理与JSP Web开发核心技术马晓敏 等编著中国铁道出版社2010年3月,网络API,API:应用程序编程接口Java网络应用程序编程接口(包)Socket接口RMI:Remote Method Invocation远程方法调用UNIXBerkeley sockets&RPC(Remote Procedure call)WindowsWinsock,各种常见的应用程序编程接口,4,TCP/IP基础,5,6,7,Socket和Socket通信基础,Socket(套接字)的定义 面向客户端/服务器结构的SocketSocket最基本的函数与函数调用流程,8,Socket的定义,进程标识一个主机的每一个网络进程使用协议端口进行标识,这样,要唯一确定网络环境的某个进程(计算机),就同时需要主机和端口号,在Internet网络环境中,就是同时采用IP地址和端口号来标识的。端口号只能取整数,范围是从065 535,其中01023的端口号一般固定分配给一些系统服务。,9,查看进程及其端口,10,11,12,多重协议标识,多重协议中需要指明是何种通信协议。一个通信进程的多重协议的标识就要用一个三元组:协议,本地主机网络地址,本地主机进程瑞口,进行全局唯一地标识。一个完整的网间网进程通信由两个通信进程组成,需要一个五元组来标识:协议,本地主机网络地址,本地主机进程瑞口,远程主机网络地址,远程主机进程端口。,13,Socket,套接字(Socket):在Internet网络环境中,协议、IP地址和端口构成了进程间通信的一个端点,抽象为网络编程的接口对象。每个Socket有一个本地唯一的Socket号,由操作系统分配.,14,Socket接口与TCP/IP协议的关系,TCP(Transmission Control Protocol):传输控制协议。UDP(User Data Protocol):用户数据报协议。IP协议(Internet Protocol):在网络层采用的网间协议。DNS(Domain Name System):域名系统,简称域名,是IP地址的字符型地址。TCP/IP(Transmission Control Protocol/Internet Protocol)协议簇:一个工业标准的协议集,简称TCP/IP协议,它是为广域网(WAN)设计的。其中IP协议和TCP协议是协议中最核心的两个协议。,15,Socket接口与TCP/IP协议,Socket是应用层与TCP/IP协议通信的中间软件抽象层,是一组接口。把复杂的TCP/IP协议隐藏在Socket接口后面,对用户来说使用非常方便,通过Socket接口应对复杂网络间通信,以符合指定的协议。,16,不同的Socket,不同的传输协议采用不同的Socket接口。(1)字节流套接字(Stream Socket)(2)数据报套接字(Datagram Socket)(3)原始数据报套接字(Raw Socket)Socket的出现只是可以更方便地使用TCP/IP协议而已,与TCP/IP协议并没有必然的联系。Socket接口在设计的时候,希望也能适应其他的网络协议。,17,面向客户端/服务器结构的套接字Socket,服务器端(Server):专门处理消息、提供服务的进程。客户端(Client):发送消息、请求处理的进程。总体过程就是客户端发送一个消息给服务器端,服务器端进程收到消息进行处理,把处理结果发送给客户端。Socket正是面向客户/服务器(C/S)结构而设计的,针对客户和服务器程序提供不同的Socket系统调用。客户端随机申请一个半关联Socket;服务器拥有公认的Socket,它事先开启,即“侦听”,任何客户都可以向它发出连接请求和信息请求,当服务器Socket“侦听”到客户端Socket便可实现连接和通信。对于客户端和服务器端Socket创建时关于端口号要事先进行约定,即共同操作同一个端口。,18,套接字Socket最基本的函数与函数调用流程,socket():对网络通信信道实现创建套接字bind():绑定网络地址和端口 recv()/send()或read()/write():接受信息和发送信息 sendto()和recvfrom()函数,connect():连接远程机器 listen():监听到达的数据 accept():接收请求 closesocket():关闭套接字或连接,Socket对通信协议进行了抽象,形成了很多灵活的函数(方法)。一些最基本的函数为:,19,1面向连接的客户端/服务器结构应用系统的关键流程,依据TCP协议,使用字节流套接字接口(Stream Socket)的网络通信为面向连接的网络通信。,20,面向连接的网络通信服务系统,socket()函数调用的关键流程,21,2面向无连接的客户端/服务器结构应用系统的关键流程,基于UDP协议,使用数据报套接字接口(Datagram Socket)的网络通信为面向无连接通信。面向无连接的网络通信服务系统,socket()函数调用的关键流程如图3-3所示。,22,面向无连接的socket()函数调用的关键流程,23,综上所述,Socket套接字在计算机中实现了一个通用的通信接口,可以通过这个接口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个Socket接口来实现。如今,Socket接口已是TCP/IP网络协议最为通用的API,也是在Internet上进行应用开发最为通用的API。,24,Windows Socket网络编程,Windows Sockets编程概述客户端和服务器端主要函数描述,Socket分为BSD UNIX Sockets和Windows Sockets,即BSD Socket和WinSock。这里仅讨论Windows Sockets编程。,25,Windows Sockets编程概述,Windows Sockets规范是一套开放的、支持多种协议,Windows下的网络编程接口。已成为Windows网络编程事实上的标准。Windows Sockets套接字编程模型的设计为服务器端和客户端结构,服务器与客户机端都必须调用Windows Sockets API函数socket()建立一个通信套接字Socket,通过套接字链接和通信。,26,客户端和服务器端主要函数描述,WindowsSockets套接字编程的主要函数分为四类,共11个。WSAStartup()和WSACleanup()函数:绑定和释放WinSock库和版本。socket()和closesocket()函数:创建和关闭Socket套接字。listen()、accept()和connect()函数:基于套接字建立监听、接收和连接函数。send()/recv()和sendto()/recvfrom()函数:发送和接收数据函数。,27,Windows Socket网络编程实例,构建编程环境TCP协议服务器和客户端编程实例UDP服务器和客户端编程实例,28,构建编程环境,在VC+中已为WinSock API网络应用开发提供了所需的所有头文件和库文件,是一个方便、易用和强大的开发平台。在VC+语言中,需要配置和使用的如下几个文件,两个主要的版本:WinSock1:头文件WINSOCK.H、库文件WSOCK32.LIB WinSock2 头文件MSWSOCK.H、库文件WS2_32.LIB、MSWSOCK.LIB,以及WINSOCK.DLL、下载Microsoft Windows Server 2003 SP1 Platform SDK,安装SDK后,设置VC+开发环境。,29,TCP协议服务器和客户端编程实例,设计一个基本的网络TCP协议服务器程序有如下几个步骤:用WSAStartup()函数初始化WinSock库和用WSACleanup()释放WinSock资源操作,已封装到initWinSock.h程序中的CInitWinSock类中,故创建CInitWinSock类对象即可。用socket()函数创建一个监听的Socket,如sListen。用TCP协议type参数为SOCK_STREAM。设置服务器地址信息,并将监听端口绑定到这个地址上。用listen()函数开始监听。用accept()函数接收客户端连接。用recv()/send()函数和客户端通信。用closesocket()关闭与客户端连接套接字sClient,关闭服务器监听套接字sListen,或者返回第步。,30,设计一个简单的TCP协议客户端程序有以下几个步骤:同上创建CInitWinSock类对象,初始化WinSock库或释放WinSock资源。用socket()函数创建一个客户端连接Socket,如sConnect。type参数为SOCK_STREAM。填写连接服务器地址信息。用connect()函数和创建的套接字与指定地址的服务器连接。用send()/recv()函数发送和接收数据,直到TCP协议会话结束。用closesocket()关闭客户端连接套接字sConnect。(1)TCP协议服务器程序:TCPServer.h(2)TCP协议客户机程序:TCPClient.h,31,UDP服务器和客户端编程实例,设计UDP服务器程序有以下几个步骤:初始化和释放WinSock资源(同上)。用socket()函数创建服务器端套接字,如sServer。使用UDP协议type参数为SOCK_DGRAM。设置本地服务器地址信息,调用bind()函数将套接字sListen与IP地址和端口绑定在一起。用recvfrom()/sendto()函数收发数据。关闭服务器套接字sListen。,32,设计UDP客户端程序有以下几个步骤:初始化和释放WinSock资源(同上)。用socket()函数创建客户端套接字,如sClient。type参数为SOCK_DGRAM。填写服务器地址信息,调用bind()函数将套接字sClient与远程服务器IP地址和端口绑定在一起。用recvfrom()/sendto()函数收发数据。关闭客户端套接字sClient。(1)UDP服务器程序:UDPServer.h(2)UDP客户机程序:UDPClient.h,33,JAVA网络编程,数据流 基于TCP协议的套接字通信 基于UDP协议的数据报和套接字,34,网络通信中基于套接字输入流和输出流的创建,客户端要实现套接字的网络通信,需四个步骤:创建套接字socket(Socket),连接成功后形成网络连接通道;完成绑定套接字通信的输入流和输出流对象的创建;用输入和输出流对象调用其对应方法的操作方式实现网络通信;网络通信结束,需要关闭输入流和输出流对象,尤其要关闭套接字对象。,35,基于套接字的字节输入流和输出流的创建-网络编程实例,import java.io.*;import.*;public class Client public static void main(String args)throws IOException/建立Socket,服务器在本机的8888端口处进行“侦听”Socket socket=new Socket(127.0.0.1,8888);/建立套接字(客户端套接字信息:+socket);try/套接字建立成功后,建立字节输入流dis和输出流dos对象 DataInputStream dis=new DataInputStream(socket.getInputStream();DataOutputStream dos=new DataOutputStream(socket.getOutputStream();/调用其对应方法进行网络通信 for(int i=0;i6;i+)dos.writeUTF(客户端测试:+i);/向服务器发数据 dos.flush();/刷新输出缓冲区,以便立即发送 System.out.println(dis.readUTF();/将从服务器接收的数据输出 dos.writeUTF(end);/向服务器发送终止标志 dos.flush();/刷新输出缓冲区,以便立即发送/关闭对象 dos.close();/关闭输出流对象dos dis.close();/关闭输入流对象disfinally(客户端结束);socket.close();/关闭套接字对象socket,36,基于套接字的具有缓冲的字符输入流和字符输出流的创建,import java.io.*;import.*;public class Client public static void main(String args)throws IOException/建立Socket,服务器在本机的8888端口处进行“侦听”Socket socket=new Socket(127.0.0.1,8888);/建立套接字 try/套接字建立成功后,通过转换器建立字符输入流br和自动刷新输出流pw BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream();PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),true);,转换器,转换器,37,/调用其对应方法进行网络通信 for(int i=0;i10;i+)/向服务器发数据,并自动刷新输出缓冲区 pw.println(测试:+i);String str=br.readLine();/从服务器接收数据 System.out.println(str);/将从服务器接收的数据输出/向服务器发送终止标志,并自动刷新输出缓冲区 pw.println(end);/关闭对象 pw.close();/关闭输出流对象pw br.close();/关闭输入流对象br finally(结束);socket.close();/关闭套接字对象socket,38,TCP套接字,一个套接字Socket就是在网络上运行的两个程序间的双向通信通道的一个端点,两个程序通过各自的套接字实现程序间的与网络拓扑结构无关的通信。客户端程序和服务器必须使用同样的通信协议,这就是TCP协议,即它们在传送的信息所使用的语言上达成一致。在Java的包中提供了两个套接字类,如客户端程序的套接字Socket和服务器端的套接字ServerSocket,它们很好地支持了TCP套接字。套接字通信是利用IP地址和端口Port进行的,是一种基础性的底层连接。一般步骤是:首先,服务器“侦听”其端口进入的连接。其次,客户端套接字与服务器的端口连接建立套接字级别的连接通道。再次,客户端与服务器分别通过绑定和套接字连接通道的输入输出流操作,实现网络通信与服务。,39,Socket类,Socket类为客户端的通信套接字。以下三个功能:由构造函数,定义指定远端IP地址、端口进行连接通信的通信套接字socket。将此socket以字节输入流和输出流的形式返回,当与数据输入流和输出流绑定,便可实现客户端的网络通信。也可通过方法获得已连接的socket的远端IP地址、端口。,1Socket定义和构造函数2返回输入流和输出流的方法3获取Socket信息方法,40,1Socket定义和构造函数,Socket构造函数均为public修饰类型,如果创建socket时发生I/O错误,均抛出IOException异常。定义和常用的构造函数如下:定义:public class Socket extends Object构造函数:Socket(InetAddress address,int port)创建一个socket并与指定的IP地址的指定的端口相连接。address为指定的IP地址。port为指定的端口。Socket(String host,int port)创建一个socket并与指定的主机的指定的端口相连接。host为指定的主机的字符串名。port为指定的端口。如果无法确定主机的IP地址还抛出UnknownHostException异常。,41,2获取输入流和输出流的方法,public InputStream getInputStream()throws IOException返回此套接字的输入流。public OutputStream getOutputStream()throws IOException返回此套接字的输出流。套接字建立成功后,建立字节输入流dis和输出流dos对象:DataInputStream dis=new DataInputStream(socket.getInputStream();DataOutputStream dos=new DataOutputStream(socket.getOutputStream();,42,3获取Socket信息方法,此类方法可获取socket所连接的远端IP地址、端口号,及所绑定的本地IP地址、端口号。另外,socket用完需要用方法关闭。方法如下:public InetAddress getInetAddress()获取socket所连接的远端IP地址。public InetAddress getLocalAddress()获取socket所绑定的本地IP地址。public int getPort()获取socket所连接的远端端口号。public int getLocalPort()获取socket所绑定到的本地端口号。public void close()throws IOException关闭socket。String toString()将此socket转换为String。,43,ServerSocket类,ServerSocket类为服务器的通信套接字。用来侦听客户端请求的连接,并为每个新连接创建一个socket对象,由此创建绑定此socket的输入流和输出流,与客户端实现网络通信。1定义与构造函数2常用成员方法,44,1ServerScket定义与构造函数,定义:public class ServerSocket extends Object构造函数:ServerSocket(int port)在所给定的用来侦听的端口上建立一个服务器套接字。如果端口号为零,则在任意的空闲的端口上创建一个服务器套接字。外来连接请求的数量默认最大为50。port参数为在服务器上所指定的用来侦听的端口。ServerSocket(int port,int backlog)在所给定的用来侦听的端口上建立一个服务器套接字。如果端口号为零,则在任意的空闲的端口上创建一个服务器套接字。外来连接请求的最大连接数量由backlog指定。,45,2常用成员方法,此类方法关键是侦听连接端口,建立与客户端的套接字连接。服务器套接字用完需要用方法关闭。方法如下:public Socket accept()throws IOException侦听并接收指向本套接字的连接,返回客户端连接套接字。本方法将造成阻塞直到连接成功。如果在等待连接时套接字发生I/O错误,则抛出异常IOException。public void close()throws IOException关闭本ServerSocket。如果在关闭本ServerSocket时发生I/O错误,则抛出异常IOException。public InetAddress getInetAddress()获取此服务器端套接字的本地IP地址。public int getLocalPort()获取此套接字侦听的端口。String toString()作为String返回此套接字的实现IP地址和实现端口。,46,建立数据输入流过程,服务器端程序。建立数据输入流dis和输出流过程类似。以创建和使用输入流dis为例。,ServerSocket s=new ServerSocket(PORT);,DataInputStream dis=:,侦听PORT端口,获得客户端套接字Socket ss=s.accept();,new DataInputStream(ss.getInputStream();,while(true)/从客户端中读数据 String str=dis.readUTF();,创建捆绑客户端套接字的数据输入流,建立服务器ServerSocket套接字,通过数据输入流操作读取数据,47,例程 服务器端程序DS_Server.java,import java.io.*;import.*;public class DS_Server public static final int PORT=8888;public static void main(String args)throws IOException ServerSocket s=new ServerSocket(PORT);/建立服务器ServerSocket套接字(服务器套接字信息:+s);try/程序阻塞,等待连接。即直到有一个客户请求到达,程序方能继续执行 Socket ss=s.accept();/侦听PORT端口,获得客户端套接字(套接字接收到的信息:+ss);/连接成功,建立相应的数据输入输出流 DataInputStream dis=new DataInputStream(ss.getInputStream();DataOutputStream dos=new DataOutputStream(ss.getOutputStream();/在死循环中,来与客户端通信 while(true)/String str=dis.readUTF();/从客户端中读数据 if(str.equals(end)break;/当读到end时,程序终止 System.out.println(str);dos.writeUTF(服务器应答:+str);/向客户端中写数据 dos.close();/关闭输出流对象dos dis.close();/关闭输入流对象dis ss.close();/关闭套接字 finally(服务器结束);s.close();/关闭服务器套接字,48,异常处理,IOException异常Socket类出现异常:在创建套接字时、在连接期间发生错误时、关闭套接字时、绑定操作失败或者已经绑定了套接字时、在创建输入流时或没有关闭套接字或没有连接套接字或关闭了套接字输入、创建输出流时或者没有连接套接字时,系统均会抛出IOException异常。ServerSocket类也会发生I/O错误,出现异常:在打开套接字时、关闭套接字时、绑定操作失败或者已经绑定了套接字、等待连接时,系统则会抛出IOException异常。这类通用的异常,采用try/catch/finally结构来抛出、捕捉和处理。,49,异常处理,BindException试图将套接字绑定到本地地址和端口时发生错误,通常是端口正在使用或无法分配所请求的本地地址。ConnectException试图将套接字连接到远程地址和端口时发生错误,通常是远程拒绝连接。NoRouteToHostException试图将套接字连接到远程地址和端口时发生错误,通常是无法到达远程主机。InterruptedIOExceptionI/O操作已中断。UnKnownHostException无法确定主机的IP地址。IllEgalArgumentException方法参数错误。,50,TCP网络通信实例,在上例中,为单线程通信。用多线程机制实现服务器能同时处理多个客户端的请求。常用的方法是在服务器程序中,accept()返回一个socket后,就用它新建一个线程,令一个线程只为它特定的客户服务。然后再调用accept(),等待下一次新的连接请求,同时产生下一个新的客户线程。,51,public class SingleThreadEchoServer public static void main(String args)throws IOException static private final int DEFAULT_PORT=8189;static private final String SIGN_OFF_TOKEN=BYE;ServerSocket serverSocket=null;Socket clientSocket=null;System.out.println(Creatin ServerSocket on+DEFAULT_PORT);serverSocket=new ServerSocket(DEFAULT_PORT);while(true)System.out.println(Looking for a client.);clientSocket=serverSocket.accept();/blocks until client connects handleRequest(clientSocket);,单线程服务器实现,52,private static void handleRequest(Socket clientSocket)throws IOException System.out.println(A client connected.);Scanner in=new Scanner(clientSocket.getInputStream();PrintWriter out=new PrintWriter(clientSocket.getOutputStream(),true);out.println(Hello!Enter BYE to exit.);,53,String line;while(true)line=in.nextLine();System.out.println(line);/Prints on server side if(line.trim().toUpperCase().startsWith(SIGN_OFF_TOKEN)out.println(Bye bye,See you later!);/client side output System.out.println(Client signed off);break;,54,public class EchoClient public static void main(String args)throws IOException Socket echoSocket=new Socket(DEFAULT_SERVER,DEFAULT_PORT);PrintWriter out=new PrintWriter(echoSocket.getOutputStream(),true);Scanner in=new Scanner(echoSocket.getInputStream();Scanner sin=new Scanner(System.in);System.out.println(Creating Socket on+DEFAULT_PORT);,55,String line=in.nextLine();System.out.println(line);String userInput=null;while(true)System.out.print(PROMPT);/Prompt String userInput=sin.nextLine();/Reading line of keyboard input if(userInput.trim().toUpperCase().startsWith(SIGNOFF_TOKEN)out.println(userInput);/Write it to Server via socket line=in.nextLine();/Read servers reply from socket System.out.println(Server reply:+line);break;else out.println(userInput);/Write it to Server via socket static private final int DEFAULT_PORT=8189;static private final String DEFAULT_SERVER=localhost;static private final String PROMPT=;static private final String SIGNOFF_TOKEN=BYE;,56,每请求每线程的服务器,public class UnboundedServer public static void main(String args)throws IOException ServerSocket serverSocket=null;System.out.println(Creatin ServerSocket on+DEFAULT_PORT);serverSocket=new ServerSocket(DEFAULT_PORT);,57,while(true)System.out.println(Looking for a client.);/blocks until client connects final Socket clientSocket=serverSocket.accept();/one request,one executor;one executor on thread.ExecutorService ec=Executors.newSingleThreadExecutor();Runnable response=new Runnable()public void run()try handleRequest(clientSocket);catch(IOException ex)System.err.println(ex.getMessage();ec.execute(response);/launch and manage the thread ec.shutdown();/shutdown the thread,58,private static void handleRequest(Socket clientSocket)throws IOException System.out.println(A client connected.);Scanner in=new Scanner(clientSocket.getInputStream();PrintWriter out=new PrintWriter(clientSocket.getOutputStream(),true);out.println(Hello!Enter BYE to exit.);String line;while(true)line=in.nextLine();System.out.println(line);/Prints on server side if(line.trim().toUpperCase().startsWith(SIGN_OFF_TOKEN)out.println(Bye bye,See you later!);/client side output System.out.println(Client signed off);break;static private final int DEFAULT_PORT=8189;static private final String SIGN_OFF_TOKEN=BYE;,59,每请求每线程特点,1main线程只负责监听客户连接请求;一旦有客户请求连接,main线程创建一个新的线程专门处理该连接;用以处理客户请求的代码(handleRequest)必须设计为线程安全的。,60,线程池服务器实现,使用线程池可以控制稳定性。线程池管理一定数目的线程,当并发线程数目达到上限后,将停止创建新的线程,阻塞创建请求直到有线程终止,61,public class PooledServer public static void main(String args)throws IOException ServerSocket serverSocket=null;System.out.println(Creatin ServerSocket on+DEFAULT_PORT);serverSocket=new ServerSocket(DEFAULT_PORT);ExecutorService ec=Executors.newFixedThreadPool(THREADS_THRESHOLD);,62,while(true)System.out.println(Looking for a client.);/blocks until client connects final Socket clientSocket=serverSocket.accept();/one request,one executor;one executor on thread.Runnable response=new Runnable()public void run()try handleRequest(clientSocket);catch(IOException ex)System.err.println(ex.getMessage();ec.execute(response);ec.shutdown();,63,private static void handleRequest(Socket clientSocket)throws IOException System.out.println(A client connected.);Scanner in=new Scanner(clientSocket.getInputStream();PrintWriter out=new PrintWriter(clientSocket.getOutputStream(),true);out.println(Hello!Enter BYE to exit.);String line;while(true)line=in.nextLine();System.out.println(line);/Prints on server side if(line.trim().toUpperCase().startsWith(SIGN_OFF_TOKEN)out.println(Bye bye,See you later!);/client side output System.out.println(Client signed off);break;,64,static private final int DEFAULT_PORT=8189;static private final String SIGN_OFF_TOKEN=BYE;private static final int THREADS_THRESHOLD=128;,65,线程池优点,线程池大小的参数防范了系统崩溃的风险,仔细地调整线程池大小的参数,也可以对性能调优,66,基于UDP协议的数据报和套接字,用户数据报协议UDP与TCP协议相反,它并不刻意追求数据报会完全发送出去,也不能担保它们抵达的顺序与它们发出时是一样的,是一种不可靠数据传输协议。比TCP协议具有开销少、传输速度快。,67,UDP概述,Java中实现UDP协议的两个类DatagramPacket数据包类:将数