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

Java套接字Socket编程

2019年09月04日 ⁄ 综合 ⁄ 共 8557字 ⁄ 字号 评论关闭

1)概念        

       网络编程基本模型就客户端到服务器的模型,也就是我们常见的C/S模型。简单的说就是两个进程间相互通信的过程。即通信双方一方作为服务器等待客户端提出请求并给以回应,另一方作为客户端向服务器提出请求。服务器一方一般作为守护进程始终运行,监听网络端口,一旦监听到客户请求,就启动一个服务进程或线程来响应该客户端,同时继续监听端口等待其他客户端的连接。

 

2)两种传输协议 

(1) TCP  传输控制协议

       TCP是Transfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议基础上通信。服务器端和客户端通过一对socket进行连接,一旦连接成功,她们就可以进行双向数据传输了。

     Java中,这些操作被封装到两个类中:Socket、ServerSocket分别用于完成客户端和服务器端的套接字的操作。

   1)  面向连接协议,在socket之间进行数据传输之前必须建立连接,所以TCP中需要连接时间。

   2) TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按照统一的格式传输最大的数据。

   3) TCP是一个可靠的协议,确保接收方完全正确的获取发送方所发送的全部数据。

   4) 主要用于远程连接Telnet和文件传输FTP等对传输质量要求高程序中。

 

 (2) UDP 用户数据报协议

      UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址和目的地址,在网络上以任何可能的路径传输到达目的地。由于UDP是一种无连接的协议,所以数据报能否到达目的地、到达目的地的时间以及顺序都不能保证。所以UDP协议是不可靠的。

  1) 每个数据报中都给出了完整的地址信息,因此需要建立发送方和接收方的连接。

  2) UDP传输数据时是有大小限制的,每个被传输的数据报都必须限定在64KB之内。

  3) UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。

  4) 通常用于局域网高可靠性的分散系统中client/server应用程序。如:音视频会议系统,ts流传输等等。

 

3)套接字Socket

     网络上两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端为一个socket。Socket通常用来实现客户方和服务方的连接。Socket主要由一个IP地址和一个端口号来确定。在Java中Socket编程主要指基于TCP/IP协议的网络编程。

 

                                                                                  

 

 

 (1) Stream socket数据流套接字 

    客户端编程:

         1)  新建一个套接字

         2)  为套接字建立一个输入流和输出流

         3) 根据协议从套接字读入或向套接字写入

         4) 关闭套接字的输入、输出流

    服务器端程序:     

         1)  创建一个服务器型套接字和一个普通套接字

         2)  将服务器型套接字处于监听状态,并把结果返回给普通套接字

         3)  为该普通套接字创建输入和输出流

         4) 从输入和输出流读入或写入字节数据,进行相应处理

         5) 完成后关闭所有对象,包括服务器型套接字、普通套接字、输入流输出流等。

创建Socket:

      java中在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。

Socket的构造方法:  

      Socket(SocketImpl impl)
      Socket(String host, int port);
      Socket(InetAddress address, int port);
      Socket(String host, int port, InetAddress localAddr, int locolPort);
      Socket(InetAddress address, int port, InetAddress locolAddr, int locolPort);

 

     其中address、host和port分别用来表示对方的ip地址、主机号和端口号,而stream是指定socket是stream流套接字还是Datagram数据报套接字。如果创建Socket时发生错误,将产生IOException,在程序中需要对之作出处理。

      这里的InetAddress是一个表示互联网协议Ip地址的类,继承于Object,实现了Serializable接口。常用方法:

          byte[] getAddress()        // 返回这个对象的原始IP地址

          static InetAddress getByAddress(byte[] addr)    // 根据给定的IP地址,返回InetAddress类

          static InetAddress getByName(String name)     // 根据给定主机名确定IP地址类

          static InetAddress getByAddress(String host, byte[] addr)  // 根据主机名和IP地址确定InetAddress

          String getHostAddress()      // 文本表现形式返回IP地址字符串

          String getHostName()         // 返回IP地址的主机名

      介绍另外一个类InetSocketAddress,实现了IP地址+端口号来确定一个对象。它继承于SocketAddress,构造函数如下:

           InetSocketAddress(InetAddress addr, int port)

           InetSocketAddress(int port)

           InetSocketAddress(String hostname, int port)

Socket提供的常用方法:

      void bind(SocketAddress bindpoint)
      bool isBound()
      void close() 
      bool isClosed()
      viod connect(SocketAddress endpoint, int timeout)
      bool isConnected()
      InetAddress getInetAddress()
      int getPort()
      InputStream getInputStream()
      OutputStream getOutputStream()
      InetAddress getLocalAddress()
      int getLocalPort()

 

 Demo: 下面是一个客户端程序,负责向本地127.0.0.1:8889写入数据,然后再读取返回来的消息。

try {	
	Socket echoSocket = new Socket("127.0.0.1", 8889);			
	OutputStream os = echoSocket.getOutputStream();	// 得到套接字的输出流
	DataInputStream is = new DataInputStream(echoSocket.getInputStream());
	int n;
	String responseLineString;
	while((n=System.in.read())!=-1) {
		os.write((byte)n);
		if(n == '\n'){
			os.flush();
			responseLineString = is.readLine();
			System.out.println("echo:" + responseLineString);
		}
	}
	
	os.close();
	is.close();
	echoSocket.close();
	
} catch (UnknownHostException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

服务器端程序:对于客户端发送过来的数据原封不动的返回给客户端。

try {
	ServerSocket serverSocket = new ServerSocket(4000, 10);	// 4000端口	
	System.out.println("Being accept...");
	Socket clientSocket = serverSocket.accept();
		
	// 获得输入流
	DataInputStream iStream = new DataInputStream(clientSocket.getInputStream());
	// 获得输出流
	PrintStream oStream = new PrintStream(new BufferedOutputStream(clientSocket.getOutputStream(), 1024), false);
	
	String inputLine;
	while((inputLine = iStream.readLine()) != null) {
		
		for(int i = 0; i < inputLine.length(); i++) {
			oStream.write((byte)inputLine.charAt(i));	// 原封不动的返回
		}	
		oStream.write((byte)'\n');
		oStream.flush();
		System.out.println("Receive:"+inputLine);
	}	
	oStream.close();
	iStream.close();
	clientSocket.close();
	serverSocket.close();	
} catch (Exception e) {
	// TODO: handle exception
	e.printStackTrace();
}

       这里介绍的C/S程序只能实现简单的Server和一个Client之间交互,实际应用中,往往是在服务器上运行一个永久的程序,它可以接受来自多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客服端提供服务的功能,需要用到多线程机制。服务器在指定端口上监听是否有客户请求,一旦监听到客户请求,服务器会立即启动一个专门的服务线程来响应客户端,而服务器又马上进入监听状态,继续等待下一个客户的到来。

 

 (2) Datagram socket 数据报套接字

       在TCP/IP协议的传输层除了TCP协议之外还有一个UDP协议,相比而言UDP应用一般用在需要很强的实时交互性的场所,如网络游戏、视频会议等。

      在java.net包中提供了两个类DatagramSocket和DatagramPacket用来支撑数据报通信,其中DatagramSocket用于在程序之间建立传送数据报的通信连接,而DatagramPacket则用来表示一个数据报。

DatagramSocket用于创建数据报套接字,并绑定到指定的本地地址上。如DatagramSocket skt = new DatagramSocket(null); skt.bind(new InetSocketAddress(8888)) 等价于: DatagramSocket skt = new DatagramSocket(8888)

 DatagramSocket的构造方法: 

	protected DatagramSocket()	// 绑定到本地主机任何可用端口上
	protected DatagramSocket(DatagramSocketImpl impl)
	protected DatagramSocket(int port)
	protected DatagramSocket(int port, InetAddres laddr)
	protected DatagramSocket(SocketAddr bindaddr)

其中port指定socket所使用的本地端口号,如果未指定则socket随机选择一个本地可用的端口,laddr表示一个可用的本地IP地址。

 

常用的方法:

	void bind(SocketAddress addr)
	boolean isBound()
	void close()
	boolean isClosed()
	void connect(InetAddress address, int port)
	void connect(SocketAddress addr)
	void disconnect()
	SocketAddress getLocalSocketAddress()
	InetAddress getInetAddress()
	int getPort()
	InetAddress getLocalAddress()
	int getLocalPort()
	bool isConnected()
	void receive(DatagramPacket pkt)
	void send(DatagramPacket pkt)

注意用数据报方式编写C/S程序时,无论是在客户端还是服务端,首先都需要建立一个DatagramPacket对象作为传输数据的载体。

DatagramPacket 用来表示数据包,用来实现无连接的包投递服务,每一条报文仅根据改包中包含的信息从一台机器路由到另一台机器。

构造函数:

	DatagramPacket(byte[] buf, int length)	// 构造接收包
	DatagramPacket(byte[] buf, int offset, int length)
	// 下面这几个都是根据给定的地址和端口号来构造发送包
	DatagramPacket(byte[] buf, int length, InetAddress address, int port)
	DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
	DatagramPacket(byte[] buf, int offset, int length, SocketAddress addr)
	DatagramPacket(byte[] buf, int length, SocketAddress addr)

 

常用的方法:

	InetAddress getAddress()
	byte getData()
	int getLength()
	int getOffset()
	int getPort()
	SocketAddress getSocketAddress()
	void setAddress(InetAddress iaddr)	// 设置发往的那台机器IP
	void setData(byte[] buf)	// 设置包缓冲区
	void setData(byte[] buf, int offset, int length)
	void setLength(int len)
	void setPort()	
	void setSocketAddress(SocketAddress address)

 

一个简单的客户端代码:

	int port;
	InetAddress address;
	DatagramSocket socket;
	DatagramPacket packet;
	byte[] sndBuf = new byte[1024];

	socket = new DatagramSocket();
	port = Integer.parseInt(args[1]);
	address = InetAddress.getByName(arg[0]);
	packet = new DatagramPacket(sndBuf, 1024, address, port);// 目的地址和端口号
	socket.send(packet);

	packet = new DatagramPacket(sndBuf, 1024);
	socket.receive(packet);		// 阻塞直到接收到一个数据帧
	String received = new String(packet.getData(), 0);
	System.out.println("Receive:" + received);
	socket.close();

服务器端程序:

public class UdpEchoServer {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ServerThread().start();
	}
	
	class ServerThread extends Thread {
		private DatagramSocket socket = null;
		private DatagramPacket packet = null;
		private DataInputStream inputStream;
		
		public ServerThread() {
			// TODO Auto-generated constructor stub
			super("ServerThread");
			try {
				socket = new DatagramSocket();
				System.out.println("ServerThread listen on port:" + socket.getLocalPort());
				
			} catch (Exception e) {
				// TODO: handle exception
				System.out.println("Could not create datagram socket");
			}
			this.openInputFile();
		}
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			if(socket == null) { 
				return;
			}
			while(true) {
				try {
					byte[] buf = new byte[1024];
					String dateString = null;
					
					packet = new DatagramPacket(buf, 1024);
					socket.receive(packet);
					InetAddress address = packet.getAddress();
					int port = packet.getPort();
					System.out.println("Get a packet from:" + address.toString());
					if(inputStream != null) {
						dateString = getNextLine();
						dateString.getBytes(0, dateString.length(), buf, 0);
						packet = new DatagramPacket(buf, buf.length, address, port);
						socket.send(packet);
					}
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println("Exception:" + e);
					e.printStackTrace();
				}
			}
		}
		
		@Override
		protected void finalize() {
			if(socket != null) {
				socket.close();
				socket = null;
				System.out.println("Closing datagram socket");
			}
		}
		
		void openInputFile() {
			try {
				inputStream = new DataInputStream(new FileInputStream("C:\\test.txt"));
			} catch (Exception e) {
				// TODO: handle exception
				System.out.println("Open test.txt file error");
			}
		}
		
		String getNextLine() {
			String returnString = null;
			try {
				if((returnString = inputStream.readLine()) == null) {
					inputStream.close();
					this.openInputFile();
					returnString = inputStream.readLine();
				}
			} catch (IOException e) {
				// TODO: handle exception
				System.out.println("Error");
			}
			return returnString;
		}
	}
}

 

 (3) Raw socket 原始套接字

    原始套接字可以用来发送和接收IP层以上的原始数据报,如ICMP,TCP,UDP等,一般用的比较少,这里就不分析。

 

 

 

 

抱歉!评论已关闭.