现在的位置: 首页 > 综合 > 正文

网络Socket编程及应用实例

2018年03月18日 ⁄ 综合 ⁄ 共 10498字 ⁄ 字号 评论关闭

1 TCP和UDP介绍

在介绍TCP和UDP之前,有必要先介绍下网络体系结构的各个层次。

1.1  网络体系结构

协议:控制网络中信息的发送和接收。定义了通信实体之间交换报文的格式和次序,以及在报文传输或接收或其他事件所采取的动作。

一般把网络的层次结构和每层所使用协议的集合称为网络体系结构(NetworkArchitecture)。

由国际标准化组织ISO 在1981年提出的网络分层结构,简称为OSI参考模型。(Open Systems Interconnection Reference Model)。


各层协议如下,可以看出TCP和UDP协议在传输层。


各层功能

1)链路层

链路层的功能:是把接收到的网络层数据报(也 称IP数据报)通过该层的物理接口发送到传输介质上,或从物理网络上接收数据帧,抽出IP数据报并交给IP层。

链路层通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。

2)网络层

主要功能:是可以把源主机上的分组发送到互联网中的任何一台目标主机上。

3)传输层

为运行在不同主机上的应用进程提供逻辑通信功能(主机好像是直接相连的)。

进程之间使用逻辑通信功能彼此发送报文,无需考虑具体物理链路。

传输层主要包括种协议:传输控制协议(TCP),用户数据报协议(UDP)。

4)应用层

应用层向使用网络的用户提供特定的、常用的应用程序。

表示层:通信用户之间数据格式的转换、数据压缩及加解密等。

会话层:对数据传输进行管理,包括数据交换的定界、同步,建立检查点等。

1.2 套接字

套接字是从网络向进程传递数据,或从进程向网络传递数据的门户。传输层和应用层的进程通过套接字来传递数据。

主机上的套接字可以有多个,每个套接字都有惟一的标识符(格式取决于UDP或TCP)。

当报文段到达主机时,运输层检查报文段中的目的端口号,将其定向到相应的套接字。

1.3  用户数据报协议UDP

用户数据报协议UDP(User Datagram Protocol):提供用户之间的不可靠、无连接的报文传输服。使用UDP协议的原因

1)无连接创建(减少时延)

2)简单:无连接(在UDP发送方和接收方之间无握手)

3)段首部小

4)无拥塞控制: UDP能够尽可能快地传输

经UDP的可靠传输 : 在应用层增加可靠性,实现特定的差错恢复

1.4 传输控制协议TCP

传输控制协议TCP(Transmission Control Protocol):提供用户之间可靠的、面向连接的报文传输服务。

面向连接、可靠的服务是指在进行数据交换前,初始化发送方与接收方状态,进行握手(交换控制信息)。

建立一个TCP 连接的作用:让发送方和接收方都做好准备,准备好之后就要开始进行数据传输了。三次握手建立TCP连接


三次握手的目的主要在于同步连接双方发送数据的初始序列号。

TCP 连接是一种全双工的连接,即一个TCP 连接的两个端点之间可以同时发送和接收数据,而不是每一个时刻只能有一个端点发送数据。

1.5 TCP与UDP的比较

TCP与UDP特点比较

TCP 是一种面向连接的协议,而UDP 是无连接的协议。
TCP 提供的是可靠的传输服务,而UDP 协议提供的是不可靠的服务。
TCP 提供的是面向字节流的服务。
UDP 协议的传输单位是数据块,一个数据块只能封装在一个UDP 数据包中。

解释:

1)可靠、连接

UDP不可靠、无连接:在UDP发送方和接收方之间无握手(交换控制信息)。

面向连接举例:两个人之间通过电话进行通信;

面向无连接举例:邮政服务,用户把信函放在邮件中期待邮政处理流程来传递邮政包裹。显然,不可达代表不可靠。

2)TCP面向字节流和UDP面向数据块(面向报文)

面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会使IP太小。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。
       面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。

TCP 协议与UDP协议应用的比较

TCP 协议对于有大量数据需要进行可靠传输的应用是很适合的。比如可用于文件传输协议(FTP )。
对于虽然数据量少但需要时间较长且可靠性要求高的应用TCP 也是比较适合的。 Telnet 就是这种应用的一个例子。
实时应用适合使用UDP 协议。
对于多个实体间的多播式应用无法使用TCP 进行通信 。


从程序实现的角度,TCP与UDP的过程如下


从上图也能清晰的看出,TCP通信需要服务器端侦听listen、接收客户端连接请求accept,等待客户端connect建立连接后才能进行数据包的收发(recv/send)工作。而UDP则服务器和客户端的概念不明显,服务器端即接收端需要绑定端口,等待客户端的数据的到来。后续便可以进行数据的收发(recvfrom/sendto)工作。

2 JAVA代码

在JAVA中TCP套接字由Socket类实现,UDP套接字由DatagramSocket类实现。

2.1 TCP的JAVA代码大致如下

TCP网络编程步骤
1)创建Socket
2)打开连接到socket的输入、输出流
3)按照一定的协议对socket进行读、写操作(接收消息-处理数据-发送消息)
4)关闭socket
服务端代码
	public static start(){
	    ServerSocket serverSocket=null;
	   	try {		
			int i=1;
			
			//1、创建TCP套接字
			int maxConnection="10";   //最大连接数
			serverSocket = new java.net.ServerSocket(8189,maxConnection);		
			
			//等待连接
			Socket sct=serverSocket.accept();
			
			//输入输出流
			InputStream inStream = clientSocket.getInputStream();
		    BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
		
	        OutputStream outStream = clientSocket.getOutputStream();
		    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
			
			//接收消息-处理数据-发送消息
			//接收客户端消息
		    String response="",lineStr = in.readLine();
		    while(lineStr!=null){
			  response += lineStr;
			  lineStr = in.readLine();
		   }//end while
		   
		   //TODO 处理消息等及其动作
		
		  //向客户端发送信息(output-write)
		  out.wirte("TODO"); 
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally{
		    if(serverSocket!=null)   //关闭套接字
			  serverSocket.close();
		}
	}//end start()
客户端代码与服务端代码类似,只不过服务端对客户端的请求是accept,而客户端需要连接服务端,所以使用connect方法,如下
	public static start(){
	    ServerSocket serverSocket=null;
	   	try {		
			int i=1;
			
			////打开套接字
		   Socket clientSocket = new Socket(String serverMachineIp, int port);
		   int timeout=1000;			 
		   clientSocket.connect(sa, timeout);	
			
			//输入输出流
			InputStream inStream = clientSocket.getInputStream();
		    BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
		
	        OutputStream outStream = clientSocket.getOutputStream();
		    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
			
			//接收消息-处理数据-发送消息
			/向客户端发送信息(output-write)
		    out.wirte("TODO"); 
			
			//TODO 处理消息等及其动作
			
			//返回服务端的消息
		    String response="",lineStr = in.readLine();
		    while(lineStr!=null){
			  response += lineStr;
			  lineStr = in.readLine();
		   }//end while	
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally{
		    if(serverSocket!=null)   //关闭套接字
			  serverSocket.close();
		}
	}//end start()

2.1 UDP的JAVA代码大致如下

服务端代码
public class ServerDatagramSocket 
{
	
    public static void main(String[] args) 
    {
    	try
    	{
        	//1创建套接字,监听某个端口
    		DatagramSocket ds = new DatagramSocket(10010);
    		
        	//2接收数据
            //构造缓冲数组,用于存放接收到的数据(要求该长度必须大于或等于接收到的数据长度)
            byte[] data=new byte[1024];
            //构造数据包对象
            DatagramPacket receiveDp=new DatagramPacket(data, data.length);
            //接收数据
            ds.receive(receiveDp); 
            //输出接收到的数据内容
            byte[] receiveByte=receiveDp.getData();
            String receiveContent=new String(receiveByte,0,receiveByte.length);
    		
        	//3发送数据
            //准备数据
    		//获得客户端的IP
            InetAddress clientIp=receiveDp.getAddress();
            //获得客户端的端口号
            int port=receiveDp.getPort();
            String sendContent="hello back";
            byte[] sendByte=sendContent.getBytes();
            
            DatagramPacket sendDp=new DatagramPacket(sendByte, sendByte.length, clientIp, port);
            
            ds.send(sendDp);            
		} 
    	catch (Exception e) {
			// TODO: handle exception
		}
		finally{
		    //4关闭连接
            ds.close();
		}	
	} 
}
客户端代码
public static void main(String[] args) {
		
		try 
		{
			//1创建连接
			DatagramSocket ds=new DatagramSocket();
			
			//2发送数据
			//数据准备(数据内容+地址+端口号)
			String sendContent="hello";
			String host="127.0.0.1";
			int port=10001;
			//将发送的内容转换为字节数组
			byte[] sendByte=sendContent.getBytes();
			//将服务器IP转换为InetAddress对象
            InetAddress server=InetAddress.getByName(host);
            //构造发送的数据包对象
            DatagramPacket sendDp=new DatagramPacket(sendByte, sendByte.length, server, port);
            
            //发送数据
            ds.send(sendDp);
					
			//3接收数据
            //构造缓冲数组,用于存放接收到的数据(要求该长度必须大于或等于接收到的数据长度)
            byte[] data=new byte[1024];
            //构造数据包对象
            DatagramPacket receiveDp=new DatagramPacket(data, data.length);
            //接收数据
            ds.receive(receiveDp);
            //输出数据内容
            byte[] receiveByte=receiveDp.getData();   //获得缓冲数组
            int len=receiveDp.getLength();   //获得有效数据长度
            String receiveContent=new String(receiveByte, 0, len);
            System.out.println(receiveContent);	
		} 
		catch (Exception e) 
		{
			// TODO: handle exception
		}
		finally{
			//4关闭连接
			ds.close();
		}
	}
}

3 应用(TCP和多线程的应用)

在工作中遇到如下问题:一个算法执行程序可以部署到多台机器上,一台机器上可以部署多个算法执行程序,即算法程序和机器是多对多的关系。现在要求用户只需要输入提供算法的名称和相关算法参数(如数据路径),然后返回数据处理结果。
解决方案:
服务端为多台部署算法程序的机器。客户端响应用户算法的请求,并从服务端多台机器中选择最优机器执行用户的算法处理。由于一台服务端机器可能会同时处理多个用户算法请求,而每个算法处理数据的过程(数据下载,数据处理,处理结果上传)是个比较长的时间,所以服务端程序采用多线程并行执行多个算法的请求。这个用一个共享FTP来在服务端和客户端共享处理的数据和上传处理后的数据。服务端和客户端通过TCP协议保证可靠的、面向连接的通信服务。整个系统架构如下图

实现代码的关键部分如下
服务端代码
/*
服务器端模块功能:
运行在多台服务端机器上面,监听和接收客户端机器发送的多个算法请求;
业务逻辑:
根据接收到的算法请求参数,启动一个新的线程,运行算法处理程序,完成最终的数据处理任务
 */
public class ServerSocketApplication {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		start();
	}//end main()
	
	public static start(){
	    ServerSocket serverSocket=null;
		ExecutorService threadPool=null; //用线程池管理线程
	   	try {		
			//1、创建TCP套接字
			int maxConnection="10";   //最大连接数
			serverSocket = new ServerSocket(8189,maxConnection);		
			
			while(true)  {  //while循环,不停等待算法任务的请求.服务端每接到一个算法处理请求,就开启一个线程处理该请求
				//等待连接
				Socket sct=serverSocket.accept();
				//Create a work thread to deal with a socket request
		       //创建一个线程池
		       //获得可用的处理器个数Runtime.getRuntime().availableProcessors()
               // int threadPoolSize=Runtime.getRuntime().availableProcessors() +1; 
              //  ExecutorService threadPool= Executors.newFixedThreadPool(threadPoolSize);
		       threadPool= Executors.newCachedThreadPool();
		       //将线程放入线程池中
               LogicThread logicThread = new LogicThread( sct ); 
		       Thread thd = new Thread( logicThread );
		       threadPool.execute(thd);
			}	//end while	
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally{
		    if(threadPool!=null)
			  threadPool.shutdown(); //关闭线程池
		    if(serverSocket!=null)   //关闭套接字
			  serverSocket.close();
		}
	}//end start()
}//end class ServerSocketApplication
//-----------------------------------
//LogicThread的任务是具体负责一次算法执行任务的处理,流程:
//1.解析客户端发送的算法请求参数 2. 根据请求参数中的算法名称和类型,调用具体某个算法
public class LogicThread implements Runnable {
	private Socket  clientSocket = null;
	
	public LogicThread( ){
	}
	/**
	 * Constructor
	 * @param sck
	 */
	public LogicThread( Socket clientSocket){
		this.clientSocket = clientSocket;
	}
	
	public void run(){
          clientContentHandle();
	}//end run()
	
	/**
	 * 接收客户端消息,处理客户端消息,发送消息到客户端
	 * @param clientContent 发送的消息
	 * @param serverMachineIp 目标服务器地址
	 * @param port 目标服务器监听端口
	 * @return   返回的消息
	 */
	private static void clientContentHandle() throws Exception{							
		InputStream inStream = clientSocket.getInputStream();
		BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
		
	    OutputStream outStream = clientSocket.getOutputStream();
		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
		
		try{
		  //接收客户端消息
		  String response="",lineStr = in.readLine();
		  while(lineStr!=null){
			  response += lineStr;
			  lineStr = in.readLine();
		  }//end while
		  
		  //TODO 处理消息
		  //如根据客户端发送的算法请求及其参数,调用对应的算法开始处理数据,并将处理结果上传到FTP
		
		  //向服务端发送信息(output-write)
		  out.wirte("successfull"); //处理完通知客户端
		}
	    catch (Exception e) 
         {
			e.printStackTrace();
		}
		finally{
		    //关闭输入输出流
			out.close();
			in.close();
		  	//关闭套接字
	 		s.close();
		}
		
		//处理服务端返回消息(这里直接返回)
        return response;
	}//end clientContentHandle()
}//end class LogicThread

客户端代码

/*
客户端模块功能:选择算法程序所在的最优的服务端机器,向该服务器端机器发送算法调用请求;
*/
public class ClientSocketApplication {

	public static void main(String[] args)
	{       
        //处理服务端返回消息 	
        String response=sendMsg("10.3.11.33",9090);		
		if(response==null)
		   System.out.println("Socket服务器连接失败:请检查安装的Socket服务器是否正常工作!");
		   
		processData("send algorithem name and its parameters");//此处省略掉算法的名称和其参数
	}//end main()
	
	
	/**
	 * 向指定的服务器发送信息,并接收返回结果
	 * @param sendContent 发送的消息
	 * @param serverMachineIp 目标服务器地址
	 * @param port 目标服务器监听端口
	 * @return   返回的消息
	 */
	public static String sendMsg(String sendContent, String serverMachineIp, int port) throws Exception{
		//服务端消息返回结果
		String response=null;
		
		//打开套接字
		Socket clientSocket = new Socket(String serverMachineIp, int port);
		int timeout=1000;			 
		clientSocket.connect(sa, timeout);	
		
	    OutputStream outStream = clientSocket.getOutputStream();
		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outStream));
		
		InputStream inStream = clientSocket.getInputStream();
		BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
		
		try{
		  //向服务端发送信息(output-write)
		  out.wirte(sendContent);
		
		  //接收服务端返回消息
		  String lineStr = in.readLine();
		  while(lineStr!=null){
			  response += lineStr;
			  lineStr = in.readLine();
		  }//end while
		}
	    catch (Exception e) 
         {
			e.printStackTrace();
		}
		finally{
		    //关闭输入输出流
			out.close();
			in.close();
		  	//关闭套接字
	 		s.close();
		}
		
		//处理服务端返回消息(这里直接返回)
        return response;
	}//end sendMsg()
	
	/* 
	*/
	
     /** 监听服务端处理数据过程
	客户端选择最优的服务端处理机器
	客户端将算法名称及其参数(如要处理数据的地址)发送给服务端
	客户端监听服务端发回的消息,判断数据是否处理完成,如果完成打包下载处理结果(服务端数据处理完会上传处理结果和日志文件log.xml到共享FTP上,供客户端下载)
	 * @param algName
	 *            请求算法资源名称
	 * @param reqValues
	 *            请求参数
	 * @return 算法返回结果,无结果返回<code>null</code>
	 */
	public static void processData(String algName, Map<String, String> reqValues) throws ManagerException
	{
	    //一个算法可以部署到不同的机器上,这里随机选择一台部署该算法的机器(在具体开发中应该根据资源占用情况选择等因素,采用高响应比调度算法分配资源)
	    CMachine optCMachine=getOptCMachine(algName);  
		String sendRequest=createReqStr(algName, reqValues);  // 生成算法资源请求文件
		
	    // 发送Socket连接,并等待返回结果
		try {
			String response = sendMsg(reqStr, optCMachine.getIp(),optCMachine.getPort());
			if ( !response.eques("processing") || !response.eques("successfull")) {  //如果服务端返回的消息是正在处理数据或处理成功,则正常,否则服务端出现异常
                   throw new ManagerException("向服务器发送计算请求失败: " + response);
			} 
			
			// 开始循环监视工作目录,是否已经完成处理,标识为有log.xml文件
			String monitDir = FileTool.getNodeManagerShareDir() + "/" + jobID + "/" + algName + "/output";
			File logFile = new File(monitDir + "/log.xml");
			for (;;) {
				if (logFile.exists()) {
					logger.info("Log文件已经上传,算法处理完成; 开始打包文件");
					ZipTool.zipFiles(monitDir); //打包文件
					break;
				} else {
					Thread.sleep(30000);
					logger.info("请求正在处理中...: " + algName);
				}
			}
		} catch (Exception e) {
			throw new ManagerException("算法服务器处理请求时出错,请与节点管理员联系查看原因");
		}	
	}//end processData()
}//end ClientSocket

抱歉!评论已关闭.