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-功能简介:


界面设计有点挫,重点在于功能,这是安卓客户端, 上面的 spinner下拉列表用来选择在线用户,
中间的TextView用于显示接收到的信息,(这里设计略简单,只做DEMO参考级别),
最下面输入聊天内容,发送按钮进行点对点通信。

3-服务器编程:

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


S2 编写服务器程序:

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

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<Message> messagelt = null;
	// 服务器启动标志位
	private boolean isStart = true;
	// 存储客户端socket
	public HashMap<String, ClientHandlerThread> clientsMap = null;
	
	/**
	 * 构造函数
	 */
	public ServerThread(){
		// 初始化 message 存储list
		messagelt = new ArrayList<Message>();
		// 初始化客户端socket
		clientsMap = new HashMap<String, ClientHandlerThread>();
		
		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<String> 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<String> adapter = new ArrayAdapter<String>(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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Spinner
        android:id="@+id/userList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/chatArea"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1.05"/>

    <EditText
        android:id="@+id/infoEdit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10" >

        <requestFocus />
    </EditText>

    <Button
        android:id="@+id/sendBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="发送" />

</LinearLayout>

S3.增加联网权限

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

5-运行效果

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


PS.几点遗憾:

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

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

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


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


附上源码:

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

没有积分的请留言邮箱~

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值