Java网络编程总结

简介:

 Java对于网络通讯有着非常强大的支持。不仅可以获取网络资源,传递参数到远程服务器,还可以通过Socket对象实现TCP协议,通过DatagramSocket对象实现UDP协议。同时,对于多点广播以及代理服务器也有着非常强大的支持。以下是本人在学习过程中的总结和归纳。


1. Java的基本网络支持


1.1 InetAddress
    Java中的InetAddress是一个代表IP地址的对象。IP地址可以由字节数组和字符串来分别表示,InetAddress将IP地址以对象的形式进行封装,可以更方便的操作和获取其属性。InetAddress没有构造方法,可以通过两个静态方法获得它的对象。代码如下:

                // 根据主机名来获取对应的InetAddress实例
		InetAddress ip = InetAddress.getByName("www.oneedu.cn");
		// 判断是否可达
		System.out.println("oneedu是否可达:" + ip.isReachable(2000));
		// 获取该InetAddress实例的IP字符串
		System.out.println(ip.getHostAddress());
		// 根据原始IP地址(字节数组形式)来获取对应的InetAddress实例
		InetAddress local = InetAddress
				.getByAddress(new byte[] { 127, 0, 0, 1 });
		System.out.println("本机是否可达:" + local.isReachable(5000));
		// 获取该InetAddress实例对应的全限定域名
		System.out.println(local.getCanonicalHostName());

1.2 URLDecoder和URLEncoder

    这两个类可以别用于将application/x-www-form-urlencoded MIME类型的字符串转换为普通字符串,将普通字符串转换为这类特殊型的字符串。使用URLDecoder类的静态方法decode()用于解码,URLEncoder类的静态方法encode()用于编码。具体使用方法如下。

                // 将application/x-www-form-urlencoded字符串
		// 转换成普通字符串
		String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8");
		System.out.println(keyWord);
		// 将普通字符串转换成
		// application/x-www-form-urlencoded字符串
		String urlStr = URLEncoder.encode("ROR敏捷开发最佳指南", "GBK");
		System.out.println(urlStr);

1.3 URL和URLConnection
    URL可以被认为是指向互联网资源的“指针”,通过URL可以获得互联网资源相关信息,包括获得URL的InputStream对象获取资源的信息,以及一个到URL所引用远程对象的连接URLConnection。
    URLConnection对象可以向所代表的URL发送请求和读取URL的资源。通常,创建一个和URL的连接,需要如下几个步骤:
    a. 创建URL对象,并通过调用openConnection方法获得URLConnection对象;
    b. 设置URLConnection参数和普通请求属性;
    c. 向远程资源发送请求;
    d. 远程资源变为可用,程序可以访问远程资源的头字段和通过输入流来读取远程资源返回的信息。
    这里需要重点讨论一下第三步:如果只是发送GET方式请求,使用connect方法建立和远程资源的连接即可;如果是需要发送POST方式的请求,则需要获取URLConnection对象所对应的输出流来发送请求。这里需要注意的是,由于GET方法的参数传递方式是将参数显式追加在地址后面,那么在构造URL对象时的参数就应当是包含了参数的完整URL地址,而在获得了URLConnection对象之后,就直接调用connect方法即可发送请求。

而POST方法传递参数时仅仅需要页面URL,而参数通过需要通过输出流来传递。另外还需要设置头字段。以下是两种方式的代码。

                // 1. 向指定URL发送GET方法的请求
		String urlName = url + "?" + param;
		URL realUrl = new URL(urlName);
		// 打开和URL之间的连接
		URLConnection conn = realUrl.openConnection();
		// 设置通用的请求属性
		conn.setRequestProperty("accept", "*/*");
		conn.setRequestProperty("connection", "Keep-Alive");
		conn.setRequestProperty("user-agent",
				"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
		// 建立实际的连接
		conn.connect();

		// 2. 向指定URL发送POST方法的请求
		URL realUrl = new URL(url);
		// 打开和URL之间的连接
		URLConnection conn = realUrl.openConnection();
		// 设置通用的请求属性
		conn.setRequestProperty("accept", "*/*");
		conn.setRequestProperty("connection", "Keep-Alive");
		conn.setRequestProperty("user-agent",
				"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
		// 发送POST请求必须设置如下两行
		conn.setDoOutput(true);
		conn.setDoInput(true);
		// 获取URLConnection对象对应的输出流
		out = new PrintWriter(conn.getOutputStream());
		// 发送请求参数
		out.print(param);

   另外需要注意的是,如果既需要读取又需要发送,一定要先使用输出流,再使用输入流。因为远程资源不会主动向本地发送请求,必须要先请求资源。


2. 基于TCP协议的网络编程
    TCP协议是一种可靠的通络协议,通信两端的Socket使得它们之间形成网络虚拟链路,两端的程序可以通过虚拟链路进行通讯。Java使用socket对象代表两端的通信端口,并通过socket产生的IO流来进行网络通信。

2.1 ServerSocket
    在两个通信端没有建立虚拟链路之前,必须有一个通信实体首先主动监听来自另一端的请求。ServerSocket对象使用accept()方法用于监听来自客户端的Socket连接,如果收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket对象。如果没有连接,它将一直处于等待状态。通常情况下,服务器不应只接受一个客户端请求,而应该通过循环调用accept()不断接受来自客户端的所有请求。
    这里需要注意的是,对于多次接收客户端数据的情况来说,一方面可以每次都在客户端建立一个新的Socket对象然后通过输入输出通讯,这样对于服务器端来说,每次循环所接收的内容也不一样,被认为是不同的客户端。另外,也可以只建立一次,然后在这个虚拟链路上通信,这样在服务器端一次循环的内容就是通信的全过程。
    服务器端的示例代码:

                // 创建一个ServerSocket,用于监听客户端Socket的连接请求
		ServerSocket ss = new ServerSocket(30000);
		// 采用循环不断接受来自客户端的请求
		while (true) {
			// 每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
			Socket s = ss.accept();
			// 将Socket对应的输出流包装成PrintStream
			PrintStream ps = new PrintStream(s.getOutputStream());
			// 进行普通IO操作
			ps.println("您好,您收到了服务器的新年祝福!");
			// 关闭输出流,关闭Socket
			ps.close();
			s.close();
		}

2.2 Socket

    使用Socket可以主动连接到服务器端,使用服务器的IP地址和端口号初始化之后,服务器端的accept便可以解除阻塞继续向下执行,这样就建立了一对互相连接的Socket。
    客户端示例代码:

                Socket socket = new Socket("127.0.0.1", 30000);
		// 将Socket对应的输入流包装成BufferedReader
		BufferedReader br = new BufferedReader(new InputStreamReader(
				socket.getInputStream()));
		// 进行普通IO操作
		String line = br.readLine();
		System.out.println("来自服务器的数据:" + line);
		// 关闭输入流、socket
		br.close();
		socket.close();


2.3 使用多线程

    在复杂的通讯中,使用多线程非常必要。对于服务器来说,它需要接收来自多个客户端的连接请求,处理多个客户端通讯需要并发执行,那么就需要对每一个传过来的Socket在不同的线程中进行处理,每条线程需要负责与一个客户端进行通信。以防止其中一个客户端的处理阻塞会影响到其他的线程。对于客户端来说,一方面要读取来自服务器端的数据,另一方面又要向服务器端输出数据,它们同样也需要在不同的线程中分别处理。

具体代码如下,服务器端:

public class MyServer {
        // 定义保存所有Socket的ArrayList
         public static ArrayList<Socket> socketList = new ArrayList<Socket>();
         public static void main(String[] args) throws IOException {
               ServerSocket ss = new ServerSocket(30000);
               while (true) {
                  // 此行代码会阻塞,将一直等待别人的连接
                  Socket s = ss.accept();
                  socketList.add(s);
                 // 每当客户端连接后启动一条ServerThread线程为该客户端服务
                 new Thread(new ServerThread(s)).start();
              }
         }
}

客户端:

public class MyClient {
	public static void main(String[] args) throws IOException {
		Socket s = s = new Socket("127.0.0.1", 30000);
		// 客户端启动ClientThread线程不断读取来自服务器的数据
		new Thread(new ClientThread(s)).start();
		// 获取该Socket对应的输出流
		PrintStream ps = new PrintStream(s.getOutputStream());
		String line = null;
		// 不断读取键盘输入
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		while ((line = br.readLine()) != null) {
			// 将用户的键盘输入内容写入Socket对应的输出流
			ps.println(line);
		}
	}
}


2.4 使用协议字符
    协议字符用于标识一些字段的特定功能,用于说明传输内容的特性。它可以由用户自定义。一般情况下,可以定义一个存放这些协议字符的接口。如下:

   

public interface YeekuProtocol {
	// 定义协议字符串的长度
	int PROTOCOL_LEN = 2;
	// 下面是一些协议字符串,服务器和客户端交换的信息
	// 都应该在前、后添加这种特殊字符串。
	String MSG_ROUND = "§γ";
	String USER_ROUND = "∏∑";
	String LOGIN_SUCCESS = "1";
	String NAME_REP = "-1";
	String PRIVATE_ROUND = "【";
	String SPLIT_SIGN = "※";
}

在字段时可以加上这些字符,如下代码:

while (true) {
   String userName = JOptionPane.showInputDialog(tip + "输入用户名");
   // 将用户输入的用户名的前后增加协议字符串后发送
   ps.println(YeekuProtocol.USER_ROUND + userName+ YeekuProtocol.USER_ROUND);
   // 读取服务器的响应
   String result = brServer.readLine();
   // 如果用户重复,开始下次循环
   if (result.equals(YeekuProtocol.NAME_REP)) {
   tip = "用户名重复!请重新";
   continue;
   }
   // 如果服务器返回登陆成功,结束循环
   if (result.equals(YeekuProtocol.LOGIN_SUCCESS)) {
          break;
   }
}

收到发送来的字段时候,也再次拆分成所需要的部分,如下代码:

if (line.startsWith(YeekuProtocol.PRIVATE_ROUND)
				&& line.endsWith(YeekuProtocol.PRIVATE_ROUND)) {
			// 得到真实消息
			String userAndMsg = getRealMsg(line);
			// 以SPLIT_SIGN来分割字符串,前面部分是私聊用户,后面部分是聊天信息
			String user = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[0];
			String msg = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[1];
			// 获取私聊用户对应的输出流,并发送私聊信息
			Server.clients.get(user).println(
					Server.clients.getKeyByValue(ps) + "悄悄地对你说:" + msg);
		}

3. UDP协议的网络编程

    UDP协议是一种不可靠的网络协议,它在通讯实例的两端个建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送和接受数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送和接收的数据报。


3.1 使用DatagramSocket发送、接收数据
    DatagramSocket本身并不负责维护状态和产生IO流。它仅仅负责接收和发送数据报。使用receive(DatagramPacket p)方法接收,使用send(DatagramPacket p)方法发送。
    这里需要首先明确的是,DatagramPacket对象的构造。DatagramPacket的内部实际上采用了一个字节型数组来保存数据,它的初始化方法如下:

                //接收端的DatagaramSocket内部包含一个空的数组,接收传递过来的数据报中的数组信息。可以通过DatagaramSocket对象的getData()方法返回的数组来获取其中的包含的数组。  
		Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length);  
		//发送端的DatagaramSocket内部包含一个将要传递的数组,同时需要包含目标IP和端口。如果初始化时传递的数组参数是空,可以通过调用DatagaramSocket对象的setData()方法设置内容。  
		Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length,IP,PORT);  
		udpSocket。setData(outBuf); 

    作为这两个方法的参数,作用和构造不同的。作为接收方法中的参数,DatagramPacket中的数组一个空的数组,用来存放接收到的DatagramPacket对象中的数组;而作为发送方法参数,DatagramPacket本身含有了目的端的IP和端口,以及存储了要发送内容的指定了长度的字节型数组。
    另外,DatagramPacket对象还提供了setData(Byte[] b)和Byte[] b= getData()方法,用于设置DatagramPacket中包含的数组内容和获得其中包含数组的内容。
    使用TCP和UDP通讯的编码区别:
    a. 在TCP中,目标IP和端口由Socket指定包含;UDP中,目标IP由DatagramPacket包含指定,DatagramSocket只负责发送和接受。
    b. 在TCP中,通讯是通过Socket获得的IO流来实现;在UDP中,则通过DatagramSocket的send和receive方法。
 

3.2 使用MulticastSocket实现多点广播
    MulticastSocket是DatagramSocket的子类,可以将数据报以广播形式发送到数量不等的多个客户端。实现策略就是定义一个广播地址,使得每个MulticastSocket都加入到这个地址中。从而每次使用MulticastSocket发送数据报(包含的广播地址)时,所有加入了这个广播地址的MulticastSocket对象都可以收到信息。
    MulticastSocket的初始化需要传递端口号作为参数,特别对于需要接受信息的端来说,它的端口号需要与发送端数据报中包含的端口号一致。具体代码如下:

                // 创建用于发送、接收数据的MulticastSocket对象
		// 因为该MulticastSocket对象需要接收,所以有指定端口
		socket = new MulticastSocket(BROADCAST_PORT);
		broadcastAddress = InetAddress.getByName(BROADCAST_IP);
		// 将该socket加入指定的多点广播地址
		socket.joinGroup(broadcastAddress);
		// 设置本MulticastSocket发送的数据报被回送到自身
		socket.setLoopbackMode(false);
		// 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
		outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress,
				BROADCAST_PORT);

4. 使用代理服务器

    Java中可以使用Proxy直接创建连接代理服务器,具体使用方法如下:

public class ProxyTest {
	Proxy proxy;
	URL url;
	URLConnection conn;
	// 从网络通过代理读数据
	Scanner scan;
	PrintStream ps;
	// 下面是代理服务器的地址和端口,
	// 换成实际有效的代理服务器的地址和端口
	String proxyAddress = "202.128.23.32";
	int proxyPort;
	// 下面是你试图打开的网站地址
	String urlStr = "http://www.oneedu.cn";

	public void init() {
		try {
			url = new URL(urlStr);
			// 创建一个代理服务器对象
			proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
					proxyAddress, proxyPort));
			// 使用指定的代理服务器打开连接
			conn = url.openConnection(proxy);
			// 设置超时时长。
			conn.setConnectTimeout(5000);
			scan = new Scanner(conn.getInputStream());
			// 初始化输出流
			ps = new PrintStream("Index.htm");
			while (scan.hasNextLine()) {
				String line = scan.nextLine();
				// 在控制台输出网页资源内容
				System.out.println(line);
				// 将网页资源内容输出到指定输出流
				ps.println(line);
			}
		} catch (MalformedURLException ex) {
			System.out.println(urlStr + "不是有效的网站地址!");
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		// 关闭资源
		finally {
			if (ps != null) {
				ps.close();
			}
		}
	}
}

5. 编码中的问题总结

    a. 双方初始化套接字以后,就等于建立了链接,表示双方互相可以知晓对方的状态。服务器端可以调用接收到的客户端套接字进行输入输出流操作,客户端可以调用自身内部的套接字对象进行输入输出操作。这样可以保持输入输出的流畅性。例如,客户端向服务器端发送消息时,可以隔一段的时间输入一段信息,然后服务器端使用循环不断的读取传过来的输入流。
    b. 对于可能出现阻塞的方法,例如客户端进行循环不断读取来自服务器端的响应信息时,如果此时服务器端并没有向客户端进行输出,那么读取的方法将处于阻塞状态,直到收到信息为止才向下执行代码。那么对于这样容易产生阻塞的代码,就需要将它放在一个单独的线程中处理。
    c. 有一些流是顺承的。例如,服务器端在收到客户端的消息以后,就将消息再通过输出流向其他所有服务器发送。那么,这个来自客户端的输入流和发向客户端的输出流就是顺接的关系,不必对它们分在两个不同的线程。
    d. println()方法对应readLine()。

        e. 在JFrame类中,一般不要将自己的代码写进main方法中,可以将代码写到自定义的方法中,然后在main方法中调用。



注: 博文来自对《疯狂java讲义》的学习。

相关文章
|
26天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
49 9
|
26天前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
43 7
|
3月前
|
Java
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
57 1
|
3月前
|
XML JSON 搜索推荐
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
71 0
|
26天前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
46 11
|
26天前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
|
26天前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
48 9
|
26天前
|
JSON Java API
JAVA网络编程新纪元:URL与URLConnection的神级运用,你真的会了吗?
本文深入探讨了Java网络编程中URL和URLConnection的高级应用,通过示例代码展示了如何解析URL、发送GET请求并读取响应内容。文章挑战了传统认知,帮助读者更好地理解和运用这两个基础组件,提升网络编程能力。
45 5
|
30天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
23 1
|
1月前
|
安全 网络协议 Java
Java 网络编程详解
《Java网络编程详解》深入浅出地讲解了使用Java进行网络编程的技术和方法。从基础的网络协议介绍到核心的Socket编程,以及高级的NIO与多线程应用,帮助读者全面掌握Java网络编程技能,是Java开发者不可或缺的学习指南。
下一篇
无影云桌面