项目简介:
- 技术介绍
基于局域网的聊天系统原理是android终端同时连接在一个局域网内,同属一个局域网网段,当其中一个终端向该网段发送广播(广播内容有该终端IP、用户名等信息,这些信息可以查看p2p协议文档),其他用户在收到广播后记录当前用户的信息,因此我们就可以通过广播获取指定用户的IP,再通过IP实现点对点信息交流。
- 布局介绍
上面错误是没有引入包的原因,项目本身没有错误。 界面效果
界面布局主要文件
下面是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)
{
}
} */
}