基于局域网的聊天系统

项目简介:

  1. 技术介绍
    基于局域网的聊天系统原理是android终端同时连接在一个局域网内,同属一个局域网网段,当其中一个终端向该网段发送广播(广播内容有该终端IP、用户名等信息,这些信息可以查看p2p协议文档),其他用户在收到广播后记录当前用户的信息,因此我们就可以通过广播获取指定用户的IP,再通过IP实现点对点信息交流。
Created with Raphaël 2.1.0 https://www.zybuluo.com 开始 https://www.zybuluo.com 向局域网发送广播 其他终端返回信息 是否收到广播? End 编辑重新发送广播 yes no
  1. 布局介绍
    上面错误是没有引入包的原因,项目本身没有错误。
  2. 界面效果

  3. 界面布局主要文件
    下面是main.xml的布局文件

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <RelativeLayout
        android:id="@+id/top"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/mainbodytop"
       >
           <ImageView
            android:id="@+id/main_self_head"
            android:src="@drawable/headpicture2"
            android:layout_width="60dip"
            android:layout_height="60dip"
            android:layout_marginLeft="2dip"/>
            <TextView
            android:id="@+id/myname"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="14dip" 
            android:text="张三"
            android:layout_toRightOf="@+id/main_self_head"
            />
            <TextView
            android:id="@+id/mymood"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="127.0.0.1"
            android:textColor="@color/black"
            android:layout_below="@+id/myname"
            android:layout_marginLeft="80dip"
            android:textSize="12dip"
            />
            <TextView 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/personalAutograph"
                android:textSize="12dip"
                android:layout_toLeftOf="@+id/edtpersonalAutograph"
                android:id="@+id/personalAutograph"
                android:layout_marginLeft="10dip"
                android:layout_below="@+id/main_self_head"
                />
             <EditText 
                android:layout_below="@+id/mymood"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:hint="@string/personalAuto"
                android:textSize="14dip"
                android:textColor="@color/gray_blue"
                android:layout_centerHorizontal="true"
                android:maxLength="50"
                android:singleLine="true"
                android:id="@+id/edtpersonalAutograph"
                />
             <!--  
            <TextView
            android:id="@+id/totalUser"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="14dip" 
            android:text="@string/info" 
            android:layout_alignParentRight="true"
            />
            <Button android:id="@+id/btnExit"
            android:layout_width="70dp"
            android:layout_height="60dp"
            android:background="@drawable/chat_quit_nor"
            android:textColor="@color/white"
            android:layout_alignParentRight="true"
               />-->

       </RelativeLayout>

        <TabWidget 
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0.0"
            android:visibility="gone"/>
        <RadioGroup
            android:id="@+id/main_tab"
            android:background="@drawable/maintab_toolbar_bg"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:layout_gravity="top">
            <RadioButton 
                android:layout_marginTop="2.0dip"
                android:text="聊天记录"
                android:drawableTop="@drawable/icon_1_n"
                android:id="@+id/radio_button1"
                style="@style/main_tab_bottom"/>
            <RadioButton 
                android:layout_marginTop="2.0dip"
                android:text="聊天"
                android:drawableTop="@drawable/icon_2_n"
                android:id="@+id/radio_button2"
                style="@style/main_tab_bottom"/>
            <RadioButton 
                android:layout_marginTop="2.0dip"
                android:text="更多"
                android:drawableTop="@drawable/icon_3_n"
                android:id="@+id/radio_button3"
                style="@style/main_tab_bottom"/>
        </RadioGroup>
         <FrameLayout 
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0.0dip"
            android:layout_weight="1.0"/>
    </LinearLayout>
</TabHost>
main_chat.xml 的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/gray" >
    <ExpandableListView 
        android:id="@+id/userlist"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:drawSelectorOnTop="false"
        android:groupIndicator="@null"
        />
</RelativeLayout>
下面是chat.xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#CAE1FF"
    android:orientation="vertical" >

    <LinearLayout android:id="@+id/chat_top"
        android:layout_width="fill_parent"
        android:layout_height="50dip"
        android:layout_weight="0"
        android:background="@color/deepskyblue"
        android:gravity="center_vertical"
        >
        <RelativeLayout 
            android:id="@+id/re_item_head"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dip"
            android:layout_weight="0"
            android:background="@drawable/headimage_background"
            >
            <ImageView 
                android:id="@+id/chat_item_head"
                android:src="@drawable/ic_launcher"
                android:layout_width="36dip"
                android:layout_height="36dip"
                android:layout_centerInParent="true"
                />        
        </RelativeLayout>        
        <LinearLayout 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            >          
            <TextView android:id="@+id/chat_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/black"
                />         
            <TextView android:id="@+id/chat_mood"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/darkgray" 
                />
        </LinearLayout>  
        <Button android:id="@+id/chat_quit"
            android:layout_width="70dip"
            android:layout_height="60dip"
            android:layout_marginRight="15dip"
            android:layout_weight="0"
            android:background="@drawable/chat_quit_nor"
            android:onClick="onClick"
            android:text="返回"
            />
    </LinearLayout>
    <ScrollView 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scrollbars="vertical"
        android:id="@+id/chart_msg_scroll"
        android:layout_weight="1">
        <LinearLayout android:layout_weight="1"
            android:id="@+id/chart_msg_panel"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"/>
    </ScrollView>

  <!-- 底部按钮 -->
    <LinearLayout android:id="@+id/body"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <LinearLayout 
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:background="@color/gray_blue"
            android:gravity="center_vertical"
            >
            <Button 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/imgExpression"
                android:background="@drawable/button_expression"
                />
            <EditText 
                android:id="@+id/chat_input"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginLeft="5dip"
                android:maxLines="4"
                />

            <Button android:id="@+id/chat_send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="0"
                android:background="@drawable/button_sumbit"
                android:layout_marginLeft="4dip"
                android:layout_marginRight="4dip"
                android:onClick="onClick"
                android:textColor="@color/white"
                android:textSize="18dip"
                />

        </LinearLayout>
    </LinearLayout>

</LinearLayout>
下面是chat_item.xml的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" 
    android:padding="6dip"  >

    <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

        <TextView android:id="@+id/show_name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textColor="@color/chat_myself"
            />

        <TextView android:id="@+id/show_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:textColor="@color/darkgray"
            />

    </LinearLayout>

    <TextView android:id="@+id/message"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        />

</LinearLayout>
下面是children.xml的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" 
    android:gravity="center_vertical">


    <RelativeLayout 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/headimage_background"
        android:layout_marginLeft="25dip"

        >
        <ImageView android:id="@+id/user_img"
        android:layout_width="36dip"
        android:layout_height="36dip"
        android:layout_centerInParent="true"        
        />
    </RelativeLayout>

    <LinearLayout 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dip"
        android:orientation="vertical"
        >

        <TextView
            android:id="@+id/child_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="15dip" 
            />

        <TextView
            android:id="@+id/child_ip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/darkgray" 
            android:text="127.0.0.1"
            />
    </LinearLayout>

    <LinearLayout 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        >

        <TextView android:id="@+id/child_infos"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dip"
            android:text="0"
        />
    </LinearLayout>

</LinearLayout>

4.具体实现文件

TCP接收文件类
package cqut.edu.cn.net;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;

import android.os.Message;
import android.util.Log;

import cqut.edu.cn.application.ApplicationOwener;
import cqut.edu.cn.p2p.*;
import cqut.edu.cn.utils.IpMessageConst;
import cqut.edu.cn.utils.IpMessageProtocol;
import cqut.edu.cn.utils.UsedConst;

/**
 * Tcp接收文件线程类
 * @author wxq
 *
 * 2012/2/28
 */
public class NetTcpFileReceiveThread implements Runnable {
    private final static String TAG = "NetTcpFileReceiveThread";

    private String[] fileInfos; //文件信息字符数组
    private String senderIp;
    private long packetNo;  //包编号
    private String savePath;    //文件保存路径

    private String selfName;
    private String selfGroup;

    private Socket socket;
    private BufferedInputStream bis;    
    private BufferedOutputStream bos;
    BufferedOutputStream fbos;
    private byte[] readBuffer = new byte[512];

    public NetTcpFileReceiveThread(String packetNo,String senderIp, String[] fileInfos){
        this.packetNo = Long.valueOf(packetNo);
        this.fileInfos = fileInfos;
        this.senderIp = senderIp;

        selfName = ApplicationOwener.Owener.getUserName();
        selfGroup = "P2P";
        savePath= "/mnt/sdcard/P2P/";

        //判断接收文件的文件夹是否存在,若不存在,则创建
        File fileDir = new File(savePath);
        if( !fileDir.exists()){ //若不存在
            fileDir.mkdir();
        }

    }
    public void run() {
        // TODO Auto-generated method stub
        for(int i = 0; i < fileInfos.length; i++){  //循环接受每个文件
            //注意,这里暂时未处理文件名包含冒号的情况,飞鸽协议规定中若文件名包含冒号,则用双冒号替代。需做处理,这里暂时没做
            String[] fileInfo = fileInfos[i].split(":");    //使用:分隔得到文件信息数组
            //先发送一个指定获取文件的包
            IpMessageProtocol ipmsgPro = new IpMessageProtocol();
            ipmsgPro.setVersion(String.valueOf(IpMessageConst.VERSION));
            ipmsgPro.setCommandNo(IpMessageConst.IPMSG_GETFILEDATA);
            ipmsgPro.setSenderName(selfName);
            ipmsgPro.setSenderHost(selfGroup);
            String additionStr = Long.toHexString(packetNo) + ":" + i + ":" + "0:";
            ipmsgPro.setAdditionalSection(additionStr);


            try {
                socket = new Socket(senderIp, IpMessageConst.PORT);
                Log.d(TAG, "已连接上发送端");
                bos = new BufferedOutputStream(socket.getOutputStream());

                //发送收取文件飞鸽命令
                byte[] sendBytes = ipmsgPro.getProtocolString().getBytes("gbk");
                bos.write(sendBytes, 0, sendBytes.length);
                bos.flush();

                Log.d(TAG, "通过TCP发送接收指定文件命令。命令内容是:" + ipmsgPro.getProtocolString());

                //接收文件
                File receiveFile = new File(savePath + fileInfo[1]);
                if(receiveFile.exists()){   //若对应文件名的文件已存在,则删除原来的文件
                    receiveFile.delete();
                }
                fbos = new BufferedOutputStream(new FileOutputStream(receiveFile));
                Log.d(TAG, "准备开始接收文件....");
                bis = new BufferedInputStream(socket.getInputStream());
                int len = 0;
                long sended = 0;    //已接收文件大小
                long total = Long.parseLong(fileInfo[2], 16);   //文件总大小
                int temp = 0;
                while((len = bis.read(readBuffer)) != -1){
                    fbos.write(readBuffer, 0, len);
                    fbos.flush();

                    sended += len;  //已接收文件大小
                    int sendedPer = (int) (sended * 100 / total);   //接收百分比
                    if(temp != sendedPer){  //每增加一个百分比,发送一个message
                        int[] msgObj = {i, sendedPer};
                        Message msg = new Message();
                        msg.what = UsedConst.FILERECEIVEINFO;
                        msg.obj = msgObj;
                        P2PBaseActivity.sendMessage(msg);
                        temp = sendedPer;
                    }
                    if(len < readBuffer.length) break;
                }

                Log.i(TAG, "第" + (i+1) + "个文件接收成功,文件名为"  + fileInfo[1]);
                int[] success = {i+1, fileInfos.length};
                Message msg4success = new Message();
                msg4success.what = UsedConst.FILERECEIVESUCCESS;
                msg4success.obj = success;
                P2PBaseActivity.sendMessage(msg4success);

            }catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "....系统不支持GBK编码");
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "远程IP地址错误");
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "文件创建失败");
            }catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "发生IO错误");
            }finally{   //处理

                if(bos != null){    
                    try {
                        bos.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    bos = null;
                }

                if(fbos != null){
                    try {
                        fbos.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    fbos = null;
                }

                if(bis != null){
                    try {
                        bis.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    bis = null;
                }

                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    socket = null;
                }

            }



        }

    }

}
TCP 发送文件类
package cqut.edu.cn.net;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;

import android.util.Log;

import cqut.edu.cn.p2p.P2PBaseActivity;
import cqut.edu.cn.utils.IpMessageConst;
import cqut.edu.cn.utils.IpMessageProtocol;
import cqut.edu.cn.utils.UsedConst;

/**
 * Tcp发送文件线程
 * @author ccf
 * 
 * 2012/2/28
 */
public class NetTcpFileSendThread implements Runnable{
    private final static String TAG = "NetTcpFileSendThread";
    private String[] filePathArray; //保存发送文件路径的数组

    public static ServerSocket server;  
    private Socket socket;  
    private byte[] readBuffer = new byte[1024];

    public NetTcpFileSendThread(String[] filePathArray){
        this.filePathArray = filePathArray;

        try 
        {
            server = new ServerSocket(IpMessageConst.PORT);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e(TAG, "监听tcp端口失败");
        }
    }


    public void run() {
        // TODO Auto-generated method stub
        for(int i = 0; i < filePathArray.length; i ++){
            try {
                socket = server.accept();
                Log.i(TAG, "与IP为" + socket.getInetAddress().getHostAddress() + "的用户建立TCP连接");
                BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
                BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
                int mlen = bis.read(readBuffer);
                String ipmsgStr = new String(readBuffer,0,mlen,"gbk");


                Log.d(TAG, "收到的TCP数据信息内容是:" + ipmsgStr);

                IpMessageProtocol ipmsgPro = new IpMessageProtocol(ipmsgStr);
                String fileNoStr = ipmsgPro.getAdditionalSection();
                String[] fileNoArray = fileNoStr.split(":");
                int sendFileNo = Integer.valueOf(fileNoArray[1]);

                Log.d(TAG, "本次发送的文件具体路径为" + filePathArray[sendFileNo]);
                File sendFile = new File(filePathArray[sendFileNo]);    //要发送的文件
                BufferedInputStream fbis = new BufferedInputStream(new FileInputStream(sendFile));

                int rlen = 0;
                while((rlen = fbis.read(readBuffer)) != -1){
                    bos.write(readBuffer, 0, rlen);
                }
                bos.flush();
                Log.i(TAG, "文件发送成功");

                if(bis != null){
                    bis.close();
                    bis = null;
                }

                if(fbis != null){
                    fbis.close();
                    fbis = null;
                }

                if(bos != null){
                    bos.close();
                    bos = null;
                }

                if(i == (filePathArray.length -1)){
                    P2PBaseActivity.sendEmptyMessage(UsedConst.FILESENDSUCCESS);    //文件发送成功
                }

            }catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "接收数据时,系统不支持GBK编码");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "发生IO错误");
                break;
            } finally{
                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    socket = null;
                }
            }

        }

        if(server != null){
            try {
                server.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            server = null;
        }       
    }
}
聊天信息接收类
package cqut.edu.cn.net;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import android.os.Message;
import android.util.Log;
import cqut.edu.cn.p2p.MainChat;
import cqut.edu.cn.p2p.P2PBaseActivity;
import cqut.edu.cn.application.ApplicationOwener;
import cqut.edu.cn.data.ChatMessage;
import cqut.edu.cn.data.User;
import cqut.edu.cn.interfaces.ReceiveMsgListener;
import cqut.edu.cn.utils.IpMessageConst;
import cqut.edu.cn.utils.IpMessageProtocol;

/**
 * 实现UDP通信以及UDP端口监听
 * 端口监听采用多线程方式
 * 
 * 单例模式
 * @author ccf
 * 
 *
 */

public class NetThreadHelper implements Runnable{
    public static final String TAG = "NetThreadHelper";

    private static NetThreadHelper instance;

    private static final int BUFFERLENGTH = 1024; //缓冲大小
    private boolean onWork = false; //线程工作标识
    private String selfName;
    private String selfGroup;

    private Thread udpThread = null;    //接收UDP数据线程
    //private Thread threadInfoRefresh = null;//刷新界面线程

    //public MyCount mc = new MyCount(10000, 1000);

    private DatagramSocket udpSocket = null;    //用于接收和发送udp数据的socket
    private DatagramPacket udpSendPacket = null;    //用于发送的udp数据包
    private DatagramPacket udpResPacket = null; //用于接收的udp数据包
    private byte[] resBuffer = new byte[BUFFERLENGTH];  //接收数据的缓存
    private byte[] sendBuffer = null;

    private Map<String,User> users; //当前所有用户的集合,以IP为KEY
    private int userCount = 0; //用户个数。注意,此项值只有在调用getSimpleExpandableListAdapter()才会更新,目的是与adapter中用户个数保持一致

    private Queue<ChatMessage> receiveMsgQueue; //消息队列,在没有聊天窗口时将接收的消息放到这个队列中
    private Vector<ReceiveMsgListener> listeners;   //ReceiveMsgListener容器,当一个聊天窗口打开时,将其加入。一定要记得适时将其移除

    private NetThreadHelper(){
        users = new HashMap<String, User>();
        receiveMsgQueue = new ConcurrentLinkedQueue<ChatMessage>();
        listeners = new Vector<ReceiveMsgListener>();

        selfName = ApplicationOwener.Owener.getUserName();
        selfGroup = "P2P";
    }

    public static NetThreadHelper newInstance(){
        if(instance == null)
            instance = new NetThreadHelper();
        return instance;
    }

    public Map<String, User> getUsers(){
        return users;
    }

    public int getUserCount(){
        return userCount;
    }

    public Queue<ChatMessage> getReceiveMsgQueue(){
        return receiveMsgQueue;
    }

    //添加listener到容器中
    public void addReceiveMsgListener(ReceiveMsgListener listener){
        if(!listeners.contains(listener)){
            listeners.add(listener);
        }
    }

    //从容器中移除相应listener
    public void removeReceiveMsgListener(ReceiveMsgListener listener){
        if(listeners.contains(listener)){
            listeners.remove(listener);
        }
    }

    /**
     * 
     * 此方法用来判断是否有处于前台的聊天窗口对应的activity来接收收到的数据。
     */
    private boolean receiveMsg(ChatMessage msg){
        for(int i = 0; i < listeners.size(); i++){
            ReceiveMsgListener listener = listeners.get(i);
            if(listener.receive(msg)){
                return true;
            }
        }
        return false;
    }


    public void noticeOnline(){ // 发送上线广播
        IpMessageProtocol ipmsgSend = new IpMessageProtocol();
        ipmsgSend.setVersion(String.valueOf(IpMessageConst.VERSION));
        ipmsgSend.setSenderName(selfName);
        ipmsgSend.setSenderHost(selfGroup);
        ipmsgSend.setCommandNo(IpMessageConst.IPMSG_BR_ENTRY);  //上线命令
        ipmsgSend.setAdditionalSection(selfName + "\0" );   //附加信息里加入用户名和分组信息

        InetAddress broadcastAddr;
        try {
            broadcastAddr = InetAddress.getByName("255.255.255.255");   //广播地址
            sendUdpData(ipmsgSend.getProtocolString()+"\0", broadcastAddr, IpMessageConst.PORT);    //发送数据
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e(TAG, "noticeOnline()....广播地址有误");
        }

    }

    public void noticeOffline(){    //发送下线广播
        IpMessageProtocol ipmsgSend = new IpMessageProtocol();
        ipmsgSend.setVersion(String.valueOf(IpMessageConst.VERSION));
        ipmsgSend.setSenderName(selfName);
        ipmsgSend.setSenderHost(selfGroup);
        ipmsgSend.setCommandNo(IpMessageConst.IPMSG_BR_EXIT);   //下线命令
        ipmsgSend.setAdditionalSection(selfName + "\0" + selfGroup);    //附加信息里加入用户名和分组信息

        InetAddress broadcastAddr;
        try {
            broadcastAddr = InetAddress.getByName("255.255.255.255");   //广播地址
            sendUdpData(ipmsgSend.getProtocolString() + "\0", broadcastAddr, IpMessageConst.PORT);  //发送数据
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e(TAG, "noticeOnline()....广播地址有误");
        }

    }

    public void refreshUsers(){ //刷新在线用户
        users.clear();  //清空在线用户列表
        noticeOnline(); //发送上线通知
        P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_BR_ENTRY);
    }

    public void run() {
        // TODO Auto-generated method stub
        while(onWork)
        {

            try
            {
                udpSocket.receive(udpResPacket);
            } 
            catch (IOException e) {
                // TODO Auto-generated catch block
                onWork = false;
                if(udpResPacket != null)
                {
                    udpResPacket = null;
                }
                if(udpSocket != null)
                {
                    udpSocket.close();
                    udpSocket = null;
                }
                udpThread = null;
                Log.e(TAG, "UDP数据包接收失败!线程停止");
                break;
            } 
            if(udpResPacket.getLength() == 0)
            {
                Log.i(TAG, "无法接收UDP数据或者接收到的UDP数据为空");
                continue;
            }
            String ipmsgStr = "";
            try 
            {
                ipmsgStr = new String(resBuffer, 0, udpResPacket.getLength(),"gbk");
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                Log.e(TAG, "接收数据时,系统不支持GBK编码");
            }//截取收到的数据
            Log.i(TAG, "接收到的UDP数据内容为:" + ipmsgStr);
            IpMessageProtocol ipmsgPro = new IpMessageProtocol(ipmsgStr);   //
            int commandNo = ipmsgPro.getCommandNo();
            int commandNo2 = 0x000000FF & commandNo;    //获取命令字
            switch(commandNo2)
            {
            case IpMessageConst.IPMSG_BR_ENTRY: 
            {   //收到上线数据包,添加用户,并回送IPMSG_ANSENTRY应答。
                addUser(ipmsgPro);  //添加用户
                P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_BR_ENTRY);

                //下面构造回送报文内容
                IpMessageProtocol ipmsgSend = new IpMessageProtocol();
                ipmsgSend.setVersion(String.valueOf(IpMessageConst.VERSION));
                ipmsgSend.setSenderName(selfName);
                ipmsgSend.setSenderHost(selfGroup);
                ipmsgSend.setCommandNo(IpMessageConst.IPMSG_ANSENTRY);  //回送报文命令
                ipmsgSend.setAdditionalSection(selfName + "\0" );   //附加信息里加入用户名和分组信息
                sendUdpData(ipmsgSend.getProtocolString(), udpResPacket.getAddress(), udpResPacket.getPort());  //发送数据
            }   
                break;

            case IpMessageConst.IPMSG_ANSENTRY: {   //收到上线应答,更新在线用户列表
                addUser(ipmsgPro);
                P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_ANSENTRY);
            }   
                break;

            case IpMessageConst.IPMSG_BR_EXIT:{ //收到下线广播,删除users中对应的值
                String userIp = udpResPacket.getAddress().getHostAddress();
                users.remove(userIp);
                P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_BR_EXIT);

                Log.i(TAG, "根据下线报文成功删除ip为" + userIp + "的用户");
            }   
                break;

            case IpMessageConst.IPMSG_SENDMSG:{ //收到消息,处理
                if(users.containsKey(udpResPacket.getAddress().getHostAddress()) == false)
                {
                    addUser(ipmsgPro);
                    P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_BR_ENTRY);
                }
                String senderIp = udpResPacket.getAddress().getHostAddress();   //得到发送者IP
                String senderName = ipmsgPro.getSenderName();   //得到发送者的名称
                String additionStr = ipmsgPro.getAdditionalSection();   //得到附加信息
                Date time = new Date(); //收到信息的时间
                String msgTemp;     //直接收到的消息,根据加密选项判断是否是加密消息
                String msgStr;      //解密后的消息内容

                //以下是命令的附加字段的判断

                //若有命令字传送验证选项,则需回送收到消息报文
                if( (commandNo & IpMessageConst.IPMSG_SENDCHECKOPT) == IpMessageConst.IPMSG_SENDCHECKOPT){
                    //构造通报收到消息报文
                    IpMessageProtocol ipmsgSend = new IpMessageProtocol();
                    ipmsgSend.setVersion("" +IpMessageConst.VERSION);   //通报收到消息命令字
                    ipmsgSend.setCommandNo(IpMessageConst.IPMSG_RECVMSG);
                    ipmsgSend.setSenderName(selfName);
                    ipmsgSend.setSenderHost(selfGroup);
                    ipmsgSend.setAdditionalSection(ipmsgPro.getPacketNo() + "\0");  //附加信息里是确认收到的包的编号
                    for(int c = 0;c < 3;c++)
                        sendUdpData(ipmsgSend.getProtocolString(), udpResPacket.getAddress(), udpResPacket.getPort());  //发送数据
                }

                String[] splitStr = additionStr.split("\0"); //使用"\0"分割,若有附加文件信息,则会分割出来
                msgTemp = splitStr[0]; //将消息部分取出

                //是否有发送文件的选项.若有,则附加信息里截取出附带的文件信息
                if((commandNo & IpMessageConst.IPMSG_FILEATTACHOPT) == IpMessageConst.IPMSG_FILEATTACHOPT){ 
                    //下面进行发送文件相关处理

                    Message msg = new Message();
                    msg.what = (IpMessageConst.IPMSG_SENDMSG | IpMessageConst.IPMSG_FILEATTACHOPT);
                    //字符串数组,分别放了  IP,附加文件信息,发送者名称,包ID
                    String[] extraMsg = {senderIp, splitStr[1],senderName,ipmsgPro.getPacketNo()};  
                    msg.obj = extraMsg; //附加文件信息部分
                    P2PBaseActivity.sendMessage(msg);

                    break;
                }


                //是否有加密选项,暂缺
                msgStr = msgTemp;

                // 若只是发送消息,处理消息
                ChatMessage msg = new ChatMessage(senderIp, senderName, msgStr, time);
                if(!receiveMsg(msg)){   //没有聊天窗口对应的activity
                    receiveMsgQueue.add(msg);   // 添加到信息队列
                    P2PBaseActivity.playMsg();
                    //之后可以做些UI提示的处理,用sendMessage()来进行,暂缺
                    P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_SENDMSG); //更新主界面UI
                }
            }
                break;
            case IpMessageConst.IPMSG_RELEASEFILES:{ //拒绝接受文件
                P2PBaseActivity.sendEmptyMessage(IpMessageConst.IPMSG_RELEASEFILES);
            }
                break;


            }   //end of switch

            if(udpResPacket != null){   //每次接收完UDP数据后,重置长度。否则可能会导致下次收到数据包被截断。
                udpResPacket.setLength(BUFFERLENGTH);
            }

        }

        if(udpResPacket != null){
            udpResPacket = null;
        }

        if(udpSocket != null){
            udpSocket.close();
            udpSocket = null;
        }

        udpThread = null;

    }

    public boolean connectSocket(){ //监听端口,接收UDP数据
        boolean result = false;

        try {
            if(udpSocket == null){
                udpSocket = new DatagramSocket(IpMessageConst.PORT);    //绑定端口
                Log.i(TAG, "connectSocket()....绑定UDP端口" + IpMessageConst.PORT + "成功");
            }
            if(udpResPacket == null)
                udpResPacket = new DatagramPacket(resBuffer, BUFFERLENGTH);
            onWork = true;  //设置标识为线程工作
            startThread();  //启动线程接收udp数据
            result = true;
        } catch (SocketException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            disconnectSocket();
            Log.e(TAG, "connectSocket()....绑定UDP端口" + IpMessageConst.PORT + "失败");
        }

        return result;
    }

    public void disconnectSocket(){ // 停止监听UDP数据
        onWork = false; // 设置线程运行标识为不运行

        stopThread();
    }


    private void stopThread() { //停止线程
        // TODO Auto-generated method stub
        if(udpThread != null){
            udpThread.interrupt();  //若线程堵塞,则中断
        }
        Log.i(TAG, "停止监听UDP数据");
    }

    private void startThread() {    //启动线程
        // TODO Auto-generated method stub
        if(udpThread == null)
        {
            udpThread = new Thread(this);
            udpThread.start();
            Log.i(TAG, "正在监听UDP数据");
        }
        /*if(threadInfoRefresh == null)
        {
            threadInfoRefresh = new Thread(threadTime);
            threadInfoRefresh.setDaemon(true);
            threadInfoRefresh.start();
        }*/
    }
    public synchronized void sendUdpData(String sendStr, InetAddress sendto, int sendPort){ //发送UDP数据包的方法
        try {
            sendBuffer = sendStr.getBytes("gbk");
            // 构造发送的UDP数据包
            udpSendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, sendto, sendPort);
            udpSocket.send(udpSendPacket);  //发送udp数据包
            Log.i(TAG, "成功向IP为" + sendto.getHostAddress() + "发送UDP数据:" + sendStr);
            udpSendPacket = null;

        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.e(TAG, "sendUdpData(String sendStr, int port)....系统不支持GBK编码");
        } catch (IOException e) {   //发送UDP数据包出错
            // TODO Auto-generated catch block
            e.printStackTrace();
            udpSendPacket = null;
            Log.e(TAG, "sendUdpData(String sendStr, int port)....发送UDP数据包失败");
        }
    }

    private synchronized void addUser(IpMessageProtocol ipmsgPro){ //添加用户到Users的Map中
        String userIp = udpResPacket.getAddress().getHostAddress();
        User user = new User();
//      user.setUserName(ipmsgPro.getSenderName());
        user.setAlias(ipmsgPro.getSenderName());    //别名暂定发送者名称

        String extraInfo = ipmsgPro.getAdditionalSection();
        String[] userInfo = extraInfo.split("\0");  //对附加信息进行分割,得到用户名和分组名
        if(userInfo.length < 1){
            user.setUserName(ipmsgPro.getSenderName());
            if(userIp.equals(MainChat.hostIp))
                user.setGroupName("自己");
            else
                user.setGroupName("在线好友");
        }else if (userInfo.length == 1){
            user.setUserName(userInfo[0]);
            if(userIp.equals(MainChat.hostIp))
                user.setGroupName("自己");
            else
                user.setGroupName("在线好友");
        }else{
            user.setUserName(userInfo[0]);
            if(userIp.equals(MainChat.hostIp))
                user.setGroupName("自己");
            else
                user.setGroupName(userInfo[1]);
        }

        user.setIp(userIp);
        user.setHostName(ipmsgPro.getSenderHost());
        user.setMac("");    //暂时没用这个字段
        users.put(userIp, user);
        Log.i(TAG, "成功添加ip为" + userIp + "的用户");
    }
    /*Runnable threadTime = new Runnable() {

        public void run() 
        {
            mc.start();
        }
    };*/
/*  class MyCount extends Count {    

        public MyCount(long millisInFuture, long countDownInterval) {    
            super(millisInFuture, countDownInterval);    
        }    
        public void onFinish() 
        {   
            //自动提交信息
            refreshUsers();
            mc.setmMillisInFuture(10000);
            mc.start();
        }    
        public void onTick(long millisUntilFinished) 
        {    

        }    

    }    */
}

说明:

原项目可以在点击这里下载http://yunpan.cn/cLk8FP7Cj6mvG 访问密码 6de7,项目中代码有些多,建议先看协议文档
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值