android网络编程 -- Socket 通信(03) 点对点Android聊天室实现(带服务器) [附源码分析]

1-简介:

概念:在网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个socket。
组成:由一个IP地址和一个端口号唯一确定,是TCP/IP 协议的一个十分流行的编程界面。
应用:socket编程比基于URL的HTTP网络编程提供了更高的传输效率、更强大的功能和更灵活的控制,但却复杂一些。
地位:socket已经是java中层次最低的网络编程接口,在java中直接操作协议中更低的层次,需要使用java本地方法调用(JNI)。
基础:Server端监听某个端口是否有连接请求,Client端向Server端发出连接请求,Server端向Client端发回Accept消息,一个连接就建立起来了;
           Server和Client端都可以用Send和Write等方法,与对方通信,Java在包java.net中提供了两个类Socket和ServerSocket。

目前网上的资源大都无法实现点对点通信,要么用服务器广播,本实例将实现socket 指定的点点通信。

2-功能简介:

android网络编程 -- Socket 通信(03) 点对点Android聊天室实现(带服务器) [附源码分析]_第1张图片
界面设计有点挫,重点在于功能,这是安卓客户端, 上面的 spinner下拉列表用来选择在线用户,
中间的TextView用于显示接收到的信息,(这里设计略简单,只做DEMO参考级别),
最下面输入聊天内容,发送按钮进行点对点通信。

3-服务器编程:

S1.新建一个java工程充当java 服务器,命名为ChatServer:

android网络编程 -- Socket 通信(03) 点对点Android聊天室实现(带服务器) [附源码分析]_第2张图片

S2 编写服务器程序:

android网络编程 -- Socket 通信(03) 点对点Android聊天室实现(带服务器) [附源码分析]_第3张图片

服务器有三个联网类组成:

S2.1.ServerThread

服务线程,启动服务器时启动,开启服务ServerSocket用以监听端口收到的客户端信息。

package com.rxz.web;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class ServerThread implements Runnable {
	
	// ServerSocket 类对象
	private ServerSocket serverSocket = null;
	// 服务器端口设置
	private static final int PORT = 8888;
	// 存储接收到客户端的消息
	public List messagelt = null;
	// 服务器启动标志位
	private boolean isStart = true;
	// 存储客户端socket
	public HashMap clientsMap = null;
	
	/**
	 * 构造函数
	 */
	public ServerThread(){
		// 初始化 message 存储list
		messagelt = new ArrayList();
		// 初始化客户端socket
		clientsMap = new HashMap();
		
		try {
			// 开启服务器socket
			serverSocket = new ServerSocket(PORT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// 启动消息处理线程
		new Thread(new MessageHandlerThread(this)).start();
	}

	/**
	 * 一旦有客户端连入,就启动客户端处理线程,并加入服务器客户端HashMap中
	 */
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(isStart){
			try {
				// 获取一个连接客户端
				Socket socket = serverSocket.accept();
				System.out.println("连入一个客户端:" + socket.getInetAddress().getHostAddress());
				
				// 创建clientHandler线程
				ClientHandlerThread clientRunnable = new ClientHandlerThread(socket, this);
				Thread clientThread = new Thread(clientRunnable);
				clientThread.start();
				
				// 将成功获取到的客户端保存起来
				if(socket != null){
					synchronized(clientsMap){
						clientsMap.put(socket.getInetAddress().getHostAddress(), clientRunnable);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * 关闭 ServerSocket
	 */
	public void finalize(){
		
		// 关闭 ServerSocket
		try {
			serverSocket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		serverSocket = null;
	}

}

class Message{
	public String IP;
	public String info;
	public Message(String iP, String info) {
		super();
		IP = iP;
		this.info = info;
	}
}

S2.2.ClientHandlerThread

客户端处理线程,当有一个客户端连入服务器时,启动一个线程接收客户端信息。

package com.rxz.web;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class ClientHandlerThread implements Runnable {
	
	// 客户端socket
	public Socket clientSocket = null;
	// socket 的输入,输出流
	private DataInputStream in = null;
	public DataOutputStream out = null;
	// 服务器线程ServerThread
	private ServerThread serverThread = null;
	
	/**
	 * 构造函数
	 * @param socket
	 * @param serverThread
	 */
	public ClientHandlerThread(Socket socket, ServerThread serverThread){
		this.serverThread = serverThread;
		this.clientSocket = socket;
		
		// 获取对服务器操作的输入输出流
		try {
			in = new DataInputStream(clientSocket.getInputStream());
			out = new DataOutputStream(clientSocket.getOutputStream());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 监听对应的客户端是否有消息发送
	 */
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try {
				String iP = in.readUTF();
				String info = in.readUTF();
				Message message = new Message(iP, info);
				// 打印接收信息
				System.out.println("[" + clientSocket.getInetAddress().getHostAddress() + "] -> [" + message.IP + "]:" + message.info);
				// 将客户端发送的信息存储到 Message LIST中
				synchronized (serverThread.messagelt) {
					serverThread.messagelt.add(message);
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				break;
			}
		}
	}

}

S2.3.MessageHandlerThread

package com.rxz.web;

import java.io.IOException;
import java.util.Iterator;

public class MessageHandlerThread implements Runnable{

	// 客户端线程
	private ClientHandlerThread clientThread = null;
	// 服务器线程
	private ServerThread serverThread = null;
	// 处理的消息
	private Message message = null;
	
	/**
	 * 构造函数
	 * @param serverThread
	 */
	public MessageHandlerThread(ServerThread serverThread){
		this.serverThread = serverThread;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			// 线程休眠100ms
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			// 获取要处理的信息
			synchronized (serverThread.messagelt) {
				// 判断是否有未发送的信息
				if(serverThread.messagelt.isEmpty()){
					continue;
				}
				message = serverThread.messagelt.get(0);
			}
			
			// 处理信息
			synchronized (serverThread.clientsMap) {
				
				if(message.IP.equals("10.0.2.2")) message.IP = "127.0.0.1";
				
				// 获取要发送的客户端
				clientThread = serverThread.clientsMap.get(message.IP);
				try {
					// 获取所有用户
					if(message.info.equals("{GETALL}")){
						String info = "";
						Iterator iterator = serverThread.clientsMap.keySet().iterator();
						while(iterator.hasNext()) {
							info += serverThread.clientsMap.get(iterator.next()).clientSocket.getInetAddress().getHostAddress();
							info += ";";	
						}
						message.info = info;
					}
					// 发送数据
					clientThread.out.writeUTF(message.info);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				// 从server LIST中删除掉已经处理的消息
				serverThread.messagelt.remove(message);
			}
		}
	}

}

4-安卓客户端编程

S1 Socket通信模块编程

该设计按照单例模式设计,即全局不构造对象,类变量只有一个实例。

这样的设计提高了socket利用率,节省了代码量。

package com.rxz.web;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class ChatClient {
	
	private static Socket clientSocket = null;
	private static DataInputStream in = null;
	private static DataOutputStream out = null;
	
	private static String IP = "10.0.2.2";
	private static int PORT = 8888;
	
	public static Socket getSocket(){
		if(clientSocket == null){
			try {
				clientSocket = new Socket(IP, PORT);
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return clientSocket;
	}
	
	public static DataInputStream getDataInputStream(){
		if(in == null){
			getSocket();
			try {
				in = new DataInputStream(clientSocket.getInputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return in;
	}
	
	public static DataOutputStream getDataOutputStream(){
		if(out == null){
			getSocket();
			try {
				out = new DataOutputStream(clientSocket.getOutputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return out;
	}
}

S2 安卓端界面简单设计

S2.1.MainActivity

Spinner控件的设计,用于选择在线用户;

TextView控件为了显示聊天记录信息;

EditView控件为了获取用户输入的数据信息;

Button控件为了发送用户信息;

package com.rxzchatdemo;

import java.io.IOException;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import com.rxz.web.ChatClient;

public class MainActivity extends Activity implements OnClickListener, Runnable {

	private Spinner userList = null;
	private Button sendBtn = null;
	private EditText infoEdit = null;
	private String message = null;
	private String[] mUsers = null;
	private TextView infoArea = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		userList = (Spinner) super.findViewById(R.id.userList);
		infoEdit = (EditText) findViewById(R.id.infoEdit);
		sendBtn = (Button) findViewById(R.id.sendBtn);
		infoArea = (TextView) findViewById(R.id.chatArea);
		
		sendBtn.setOnClickListener(this);
		
		try {
                       //获得本机IP
                       InetAddress addr = InetAddress.getLocalHost();
                       String ip = addr.getHostAddress().toString();
                
                        ChatClient.getDataOutputStream().writeUTF(ip);
			ChatClient.getDataOutputStream().writeUTF("{GETALL}");
			String response = ChatClient.getDataInputStream().readUTF();
			mUsers = response.split(";");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
        ArrayAdapter adapter = new ArrayAdapter(this, 
                android.R.layout.simple_spinner_item, mUsers); 
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 
        
        userList.setAdapter(adapter); 
		
		new Thread(this).start();
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch(v.getId()){
		case R.id.sendBtn:
			String ip = userList.getSelectedItem().toString();
			String info = infoEdit.getText().toString();
			try {
				ChatClient.getDataOutputStream().writeUTF(ip);
				ChatClient.getDataOutputStream().writeUTF(info);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			break;
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try {
				message = ChatClient.getDataInputStream().readUTF();
				message += "\n";
				
				mHandler.sendMessage(mHandler.obtainMessage());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private Handler mHandler = new Handler(){
		/**
		 * 获取一个消息,刷新对话框
		 */
		public void handleMessage(Message msg){
			infoArea.append(message);
			super.handleMessage(msg);
		}
	};

}

S2.2.activity_main.xml



    

    

    

        
    

    

S3.增加联网权限


5-运行效果

先启动服务器,然后启动客户端,选择聊天对象,即可实现点对点通信。

android网络编程 -- Socket 通信(03) 点对点Android聊天室实现(带服务器) [附源码分析]_第4张图片


PS.几点遗憾:

1.Spinner控件没有获取到点击事件,导致无法更新在线人信息,但这个与socket通信无关,后期将会把更新版本补充上。

2.由于开启虚拟机测试,不能实现现场点对点通信(虚拟机IP相同导致的),真机测试,效果会更好。

3.之前想用传统的ObjectInputStream,然后将信息交换定义为一系列的Message类,但考虑到服务器不知用于java通信,如果这样写可能会让跨语言通信失败。


本实例精华重在一个HashMap映射思想和,MessageHandlerThread消息处理线程的设计,用以实现的点点通信机制。


附上源码:

【源码】 :http://download.csdn.net/detail/hit_rxz/7952127

没有积分的请留言邮箱~

你可能感兴趣的:(Android)