`
dpzc
  • 浏览: 12925 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

java网络编程 使用长连接

    博客分类:
  • java
阅读更多
    最近涉及了一些网络编程,在这里总结一下。
    项目是一个多域仿真系统,模型简要说明下:局域网中有l台机器,代表l个域,每个域有n个节点,每个节点有m种资源每次仿真时,随机选一个节点申请某个资源,这个资源可能好几个域上都有节点拥有它。选择信誉值最高的节点作为服务者,与其进行交互。
    对网络的要求是,可以将一个域上的东西发到另一个节点。最开始的想法是tcp开销较大且局域网丢包概率很小,因此考虑用简单快捷的UDP进行传输。UDP协议是无连接的协议,在JAVA中java.util.DatagramSocket负责接收和发送UDP数据报,Datagrampacket表示UDP数据报。DatagramSocket提供了接收和发送数据报的方法。
public void receive(DatagramPacket dst)throws IOException
public void send(DatagramPacket p)throws IOException

测试时发现会报异常,UDP规定每个包大小不能超过64K,而我们传送的数据至少几百KB。期间考虑过手工编号分组的方法,但没能成功。转而考虑TCP。
    TCP中服务器程序监听端口,接收客户程序的连接请求。
ServerSocket server=new ServerSocket(8000);

用accpet()方法等待客户连接请求,这个方法是阻塞的,直到有客户连接才返回一个Socket对象
Socket socket=server.accept();

客户端用
Socket socket=new Socket(addr,port);
建立和server的连接。
    项目采用TCP连接后可以正常传输数据了,但是由于仿真一次所需时间只要一、两秒钟时间,频繁创建socket导致程序不稳定。后经导师提醒,采用长连接的方法,即一个域初始化时,就与局域网中其他所有域建立tcp连接,这个连接建立后不关闭,因为每次仿真几乎都要传输数据。修改时碰到第一个问题是,以前accept方法是阻塞的,即需要发数据时客户端建立socket,服务端监听线程就收到一个请求,交与处理线程处理。现在socket保持连接,如何知道客户端要发送数据了呢?
    直接说解决方法了。长连接是要把socket保存下来的,新建socket的同时,取得它的输出流,因为要发送对象,用ObjectOutputStream来装饰。
sendObjectStream = new ObjectOutputStream(sendSocket.getOutputStream());

要发送时,只要得到sendObjectStream ,调用writeObject即可。由于每个节点即可能是客户端也可能是服务端,每个域可能同时进行发送和接收,这时可以使用保存的这个socket接收,我是在新建这个socket时,就开一个接收线程,把这个socket传过去用于接收。
public class Receiver implements Runnable{
 private Socket s = null;
 private ObjectInputStream ois = null;
 private static volatile boolean bConnected = false;
 private Network network;
 
 public Receiver(Socket s,Network n){
 	this.s = s;
 	this.network = n;
 	bConnected = true;
		try {
			ois = new ObjectInputStream(s.getInputStream());
		} catch (IOException e) {
			System.out.println("S关闭了");
			e.printStackTrace();
		}
 }

	public void run()
	{
		try
		{
			while(bConnected)
			{
				try
				{
					long begin = System.currentTimeMillis();
					NetParameterPacket  npp = (NetParameterPacket) ois.readObject();
					long end = System.currentTimeMillis();
					System.out.println("readObject用的时间为"+(end-begin)+"ms");
					PacketHandle ph = new PacketHandle(npp,network,s);
					new Thread(ph).start();
				}
				catch(EOFException e)
				{
					System.out.println("此时无数据");
					try
					{
						Thread.sleep(500);
					} catch (InterruptedException e1)
					{
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
				catch (IOException e)
				{
					e.printStackTrace();
					if(e instanceof SocketException)
					{
						bConnected=false;
					}
				} catch (ClassNotFoundException e)
				{
					e.printStackTrace();
				}
			}
		} 
		finally
		{
			if(ois!=null){
				try
				{
					ois.close();
				} catch (IOException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				ois = null;
			}
			if(s!= null){
				try {
					s.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				s = null;
			}
		}		
	}

	public static void setBConnected(boolean connected)
	{
		bConnected = connected;
	}
}


关于谁向谁新建TCP的问题:局域网中A域在运行时,这时B域申请加入。B域首先在局域网中广播,A域收到广播后进行响应,new一个socket向其建立tcp连接,并新建接收线程,将这个socket传过去用于接收。B域监听到这个tcp后,得到socket=server.accpet();新开一个接收线程的同时,将这个socket保存下来用于发送TCP。发送TCP代码为
public void sendTCPSingle(Object o , String address)
	{
		Domain d = getDomainByName(address);
		if(d == null)
		{
			System.out.println("没找到该domain");
			return;
		}
		try
		{
			sendLock.lock();
			ObjectOutputStream oos = d.getSendObjectStream();		
			oos.writeObject(o);
			oos.flush();	
		} catch (IOException e)
		{
			System.out.println("发送对象出错!");			
			
			//对发送异常进行处理			
			SocketAddress remoteAddr = new InetSocketAddress(address,TCP_PORT);
			try
			{
				if(d.getSendSocket().isClosed())
				{
					Socket newSocket = new Socket(address,TCP_PORT);
					System.out.println("socket已经关闭,重新向"+address+"发送建立TCP连接请求");
					d.setSendSocket(newSocket);
				}
				else
					d.getSendSocket().connect(remoteAddr);
			} 
			catch (IOException e1)
			{
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		finally
		{
			sendLock.unlock();
		}
	}

其中Domain类表示域。

用加锁的原因是,某次交互可能两个线程同时准备发送,这时发送的一方会报异常java.net.SocketException: Software caused connection abort: socket write error,接收方也跟着异常java.io.StreamCorruptedException: invalid type code: 00

网上说长连接要用心跳方法检测对方是否还活着,这里因为是局域网且数据一直要传送,就没使用。
分享到:
评论
5 楼 twocat 2009-12-11  
dpzc 写道
twocat 写道
dpzc兄,你好油菜啊,稀饭~~~

twocat姐,晚上有空吗,出来喝茶

对你什么时候都有空。。晚上教我怎么使用长链接哈
4 楼 dpzc 2009-12-10  
twocat 写道
dpzc兄,你好油菜啊,稀饭~~~

twocat姐,晚上有空吗,出来喝茶
3 楼 twocat 2009-12-10  
dpzc兄,你好油菜啊,稀饭~~~
2 楼 dpzc 2009-12-08  
twocat 写道
哈哈,来支持一下

twocat姐,你好啊
1 楼 twocat 2009-12-01  
哈哈,来支持一下

相关推荐

Global site tag (gtag.js) - Google Analytics