JavaSE第一百零八讲:基于TCP协议的网络编程详解

  还记得我们在以前学习过的第一种设计模式:单例模式吗?如果不知道单例模式的请参考这一讲内容:JavaSE第二十五讲:单例模式详解

  在我们学习单例模式的时候有讲过单例模式有两种实现方式,其中有一种模式在多线程环境中是有可能出现错误的,现在我们就来讲解一下在多线程环境中为什么会出现错误

第一种方式:

public class Singleton {
	private static Singleton singleton = new Singleton();
	private Singleton(){
		
	}
	public static Singleton getInstance(){
		return singleton;
	}
}

【说明】:这种单例模式是没有问题的


第二种方式:

public class Singleton {
	private static Singleton singleton;
	private Singleton(){
		
	}
	public static Singleton getInstance(){
		
		if(null == singleton){
			singleton = new Singleton();
		}
		return singleton;
	}
}

【说明】: 在这种单例模式 在多线程的环境下就会产生问题的,比如有个线程,第一线程执行到 if(null == singleton)为真,进入if内部,此时还没往下执行时,CPU切换到另外第二个线程去,由于前面的线程没有执行singleton = new Singleton();语句,所以此时singlton还是为空,这个线程执行到if(null == singleton)时,也为真,然后进入if语句,生成的新的对象,返回singleton对象,此时第一个线程醒过来继续执行以下代码,也生成了一个新的对象,返回singleton对象。这样,这两个对象都生成出来,而且还是不一样的对象,这违背了单例模式的特点。

我们来用程序模拟一下这种情况。让线程睡眠

package com.ahuier.network;

public class Singleton {
	private static Singleton singleton;
	private Singleton(){
		
	}
	public static Singleton getInstance(){
		
		if(null == singleton){
			try {
				Thread.sleep((long)(Math.random()) * 4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			singleton = new Singleton();
		}
		return singleton;
	}
	
	public static void main(String[] args) {
		
		//启动两个线程,看是否run()方法中打印出的对象是否一样
		new MyThread().start(); 
		new MyThread().start();
	}
}

class MyThread extends Thread{
	
	@Override
	public void run() {
		
		System.out.println(Singleton.getInstance());
	}
}

编译执行结果:

com.ahuier.network.Singleton@1b48197
com.ahuier.network.Singleton@1a80a69

【说明】:两个对象的hashCode值不一样,所以它们不可能是同一个对象。

【总结】:对于单例模式(Singleton)来说,如果在 getInstance()方法中生成 Singleton 实例则可能 会产生同步问题,即可能会生成两个不同的对象。 


2.现在我们来讲一下网络编程中的相关内容,继续上一讲的内容

我们讲到TCP协议的时候,它是一种面向连接的可靠的协议,所以在必须要在进行连接的基础之上通过I/O流来进行双向的传递。而对于UDP来说,它是一种面向非连接的不可靠的协议,所以它在发送数据的过程中通常是以包的形式发送,而这种发送方式,在包里面就已经封装好了,目标IP地址和源IP地址,目标主机收到这个包之后进行解封获取到源IP地址同样也可以想对方进行发送消息了。

3. 注意我们学习的重点是利用Java中的JDK提供的类帮我们进行网络编程。

1) 继续练习使用UrlConnection进行网络编程。

package com.ahuier.network;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

/*
 * 访问soho网页内容
 */
public class UrlConnection3 {
	public static void main(String[] args) throws Exception {

		// 注意这边的URL目标地址必须是完整的地址
		URL url = new URL("http://www.soho.com");

		// 如果我们知道网页内容是以字符形式存在,所以我们可以用字符流来进行转换
		BufferedReader br = new BufferedReader(new InputStreamReader(
				url.openStream()));
		
		String line = null;
		while(null != (line = br.readLine())){
			System.out.println(line);
		}
		br.close();
	}
}

编译执行结果在命令行中输出soho网页的html内容,这里就不贴出来了。

4. InetAddress (在UDP编程的时候经常用到的类)

1) 无论你是在打电话、发送邮件或建立与Internet的连接,地址是基础。InetAddress 类用来封装我们前面讨论的数字式的IP地址和该地址的域名。你通过一个IP主机名与这个类发生作用,IP主机名比它的IP地址用起来更简便更容易理解。

2) InetAddress 类内部隐藏了地址数字。

3) 工厂方法(这里涉及到一种设计模式,工厂设计模式,这里先暂时不讲)

InetAddress 类没有明显的构造函数。为生成一个InetAddress对象,必须运用一个可用的工厂方法。
工厂方法(factory method)仅是一个类中静态方法返回一个该类实例的约定。

对于InetAddress,三个方法 getLocalHost( )、getByName( )以及getAllByName( )可以用来创建InetAddress的实例

LocalHost:表示机器本地主机,用来测试本地联网的应用

地址:127.0.0.1 

主机名:localhost

查看JDK 文档中的InetAddress

public class InetAddress extends Object implements Serializable
  This class represents an Internet Protocol (IP) address.

[这个类代表了一个因特网的IP地址]

查看这个类里面的方法可以发现它里面的很多方法都是 static 的。而且它里面是没有public的构造方法。

4) getLocalHost()仅返回象征本地主机的InetAddress对象。

5) getByName()方法返回一个传给它的主机名的InetAddress。

6) 如果这些方法不能解析主机名,它们引发一个UnknownHostException异常。

7) 在Internet上,用一个名称来代表多个机器是常有的事。getAllByName( )工厂方法返回代表由一个特殊名称分解的所有地址的InetAddresses类数组。在不能把名称分解成至少一个地址时,它将引发一个Unknown HostException异常。

8) 程序Demo:

package com.ahuier.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressTest {
	public static void main(String[] args) throws Exception {
		InetAddress address = InetAddress.getLocalHost();
		System.out.println(address);
	}
}

编译执行结果:

XP--20121018EFY/10.10.100.65

【说明】:InetAddress这个类的toString()方法输出内容是 "机器名字/IP地址" 这种方式

package com.ahuier.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressTest {
	public static void main(String[] args) throws Exception {
		
		InetAddress address = InetAddress.getLocalHost();
		System.out.println(address);
		
		//这是可以不用写完整地址名称,因为它可以是一个域名,可以通过域名解析获得到真实地址
		address = InetAddress.getByName("www.soho.com");
		System.out.println(address);
	}
}

编译执行结果:

XP--20121018EFY/10.10.100.65
www.soho.com/174.129.4.121

5. 使用TCP/IP的(Socked)进行通信

1) Socket的引入

(1) 为了能够方便的开发网络应用软件,由美国伯克利大学在Unix上推出了一种应用程序访问通信协议的操作系统调用socket。socket的出现,使程序员可以很方便地访问TCP/IP,从而开发各种网络应用的程序。

(2) 随着Unix的应用推广,套接字在编写网络软件中得到了极大的普及。后来,socked又被引进了Windows等操作系统中。Java语言也引入了套接字编程模型。

2) 什么是Socket?

Socket是连接运行在网络上的两个程序间的双向通讯的端点

3) 使用Socket进行网络通信的过程

(1) 服务器程序将一个 socked 绑定到一个特定的端口,并通过此socked等待和监听客户的连接请求。

    比如服务器绑定了 5000的端口号,如果客户想跟服务器通信,就必须找到服务器通过IP,并且指定端口号为5000才能进行通信。

(2) 客户程序根据服务器程序所在的主机名和端口号发出连接请求。

(3) 如果一切正常,服务器接受连接请求。并获得一个新的绑定到不同端口地址的 socked。

(4) 客户和服务器通过读、写套接字进行通讯。


4) 使用ServerSocket和Socket实现服务器端和客户端的 Socket 通信

(1) 建立Socket连接

(2) 获得输入/输出流

(3)读/写数据

(4) 关闭输入/输出流

(5) 关闭Socket

(6) 查看JDK文档中的ServerSocket类
public class ServerSocket extends Object
  This class implements server sockets. A server socket waits for requests to come in over the network. It performs some operation based on that request, and then possibly returns a result to the requester.
  The actual work of the server socket is performed by an instance of the SocketImpl class. An application can change the socket factory that creates the socket implementation to configure itself to create sockets appropriate to the local firewall.

[ 这个类实现了服务端的socket,一个服务端的socked会等待网络上请求的到来,会基于请求去执行某一些操作,并且可能会返回一个结果给请求

  实际的服务端的socket工作是由一个SocketImpl类的实例去完成的,一个应用能够改变socket的工厂模式,那就是创建socket实现来配置自身,以创建符合本地防火墙的socket(因为socket有些时候通信的时候会被防火墙拦截住,防火墙一般不会拦截80端口,80端口是用来访问网页的)]

(7) 查看ServerSocket类的构造方法和定义的方法,我们一般比较经常用的是 public ServerSocket(int port) 构造方法,它接受的是int类型的参数来指定端口号,如果接受的是0,它就会指定随意的端口号了。

这样就相当于监听这个端口号了

(8) 指定好端口号之后,就调用accet()这个方法,它返回Socket类型对象。
public Socket accept() throws IOException
  Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.

[它会监听与这个socket的连接,并且会接受它,这个方法阻塞直到一个连接被建立好](【重要】如果客户端并没有与它进行连接,则这个方法是阻塞住的,直到有一个客户端与它进行连接,它才继续执行)

注意这边得到一个Socket对象之后,就可以通过I/O流的方式进行数据的连接于读取了。

(9) 查看客户端要做的事情查看Socket类的构造方法

public Socket(String host, int port)

通过这个构造方法,指定主机名字和端口号,在new这个构造方法的时候,它就已经开始对服务端进行请求了,并不是仅仅在本地建立对象。

(10) 连接建立好之后就可以通过I/O流进行双向数据的交互了

5) 实现服务器支持多个客户



后续内容,请关注下一讲详解。









  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值