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

来源:互联网 发布:加拿大订酒店软件 编辑:程序博客网 时间:2024/04/25 18:48

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;// 存储客户端socketpublic HashMap<String, ClientHandlerThread> clientsMap = null;/** * 构造函数 */public ServerThread(){// 初始化 message 存储listmessagelt = new ArrayList<Message>();// 初始化客户端socketclientsMap = new HashMap<String, ClientHandlerThread>();try {// 开启服务器socketserverSocket = new ServerSocket(PORT);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 启动消息处理线程new Thread(new MessageHandlerThread(this)).start();}/** * 一旦有客户端连入,就启动客户端处理线程,并加入服务器客户端HashMap中 */@Overridepublic void run() {// TODO Auto-generated method stubwhile(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 blocke.printStackTrace();}}}/** * 关闭 ServerSocket */public void finalize(){// 关闭 ServerSockettry {serverSocket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.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 {// 客户端socketpublic Socket clientSocket = null;// socket 的输入,输出流private DataInputStream in = null;public DataOutputStream out = null;// 服务器线程ServerThreadprivate 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 blocke.printStackTrace();}}/** * 监听对应的客户端是否有消息发送 */@Overridepublic void run() {// TODO Auto-generated method stubwhile(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 blocke.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;}@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){// 线程休眠100mstry {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 blocke.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 blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return clientSocket;}public static DataInputStream getDataInputStream(){if(in == null){getSocket();try {in = new DataInputStream(clientSocket.getInputStream());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return in;}public static DataOutputStream getDataOutputStream(){if(out == null){getSocket();try {out = new DataOutputStream(clientSocket.getOutputStream());} catch (IOException e) {// TODO Auto-generated catch blocke.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;@Overrideprotected 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 blocke.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();}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubswitch(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 blocke.printStackTrace();}break;}}@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){try {message = ChatClient.getDataInputStream().readUTF();message += "\n";mHandler.sendMessage(mHandler.obtainMessage());} catch (IOException e) {// TODO Auto-generated catch blocke.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

没有积分的请留言邮箱~

1 1
原创粉丝点击