首页 > 系统 > Android > 正文

Android 基于Socket的聊天室实例

2019-12-12 04:24:10
字体:
来源:转载
供稿:网友

Socket是TCP/IP协议上的一种通信,在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。

Client A  发信息给 Client B ,  A的信息首先发送信息到服务器Server ,Server接受到信息后再把A的信息广播发送给所有的Clients

首先我们要在服务器建立一个ServerSocket ,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。

Socket accept():如果接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket

Server示例:

//创建一个ServerSocket,用于监听客户端Socket的连接请求ServerSocket ss = new ServerSocket(30000);//采用循环不断接受来自客户端的请求while (true){//每当接受到客户端Socket的请求,服务器端也对应产生一个SocketSocket s = ss.accept();//下面就可以使用Socket进行通信了...}

客户端通常可使用Socket的构造器来连接到指定服务器

Client示例:

//创建连接到服务器、30000端口的SocketSocket s = new Socket("192.168.2.214" , 30000);//下面就可以使用Socket进行通信了...

这样Server和Client就可以进行一个简单的通信了

当然,我们要做的是多客户,所以每当客户端Socket连接到该ServerSocket之后,程序将对应Socket加入clients集合中保存,并为该Socket启动一条线程,该线程负责处理该Socket所有的通信任务

//定义保存所有Socket的ArrayListpublic static ArrayList<Socket> clients = new ArrayList<Socket>();

当服务器线程读到客户端数据之后,程序遍历clients集合,并将该数据向clients集合中的每个Socket发送一次。这样就可以实现一个聊天室的功能了

下面来看看整个功能的demo

先建立一个Java工程,把Server.java运行起来,然后再运行手机模拟器

服务器打印信息:

程序文件结构:

1.先看看主Activity : SocketmsgActivity.java

public class SocketmsgActivity extends Activity {  /** Called when the activity is first created. */  private SQLiteDatabase db;    Thread thread = null;  Socket s = null;  private InetSocketAddress isa = null;   DataInputStream dis = null;  DataOutputStream dos = null;  private String reMsg=null;  private Boolean isContect = false;  private EditText chattxt;  private EditText chatbox;  private Button chatok;    private String chatKey="SLEEKNETGEOCK4stsjeS";  private String name=null,ip=null,port=null;  @Override  public void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.main);    chattxt = (EditText)findViewById(R.id.chattxt);    chatbox = (EditText)findViewById(R.id.chatbox);    chatok = (Button)findViewById(R.id.chatOk);    chatbox.setCursorVisible(false);    chatbox.setFocusable(false);    chatbox.setFocusableInTouchMode(false);    chatbox.setGravity(2);        //初始化,创建数据库来储存用户信息    InitDatabase();    db = SQLiteDatabase.openOrCreateDatabase(config.f, null);    try {      Cursor cursor = db.query("config", new String[]{"ip","name","port"},null,null, null, null, null);      while(cursor.moveToNext()){        name = cursor.getString(cursor.getColumnIndex("name"));        ip = cursor.getString(cursor.getColumnIndex("ip"));        port = cursor.getString(cursor.getColumnIndex("port"));      }      cursor.close();    } catch (Exception e) {      // TODO: handle exception      System.out.println(e.toString());    }    db.close();        //设置连接    if(ip==null || port==null){      Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);      startActivity(intent);      SocketmsgActivity.this.finish();    }    //设置名称    else if(name==null){      Intent intent = new Intent(SocketmsgActivity.this,IniuserActivity.class);      startActivity(intent);      SocketmsgActivity.this.finish();    }else{            connect();      chatok.setOnClickListener(new View.OnClickListener() {          @Override        public void onClick(View v) {            String str = chattxt.getText().toString().trim();          System.out.println(s);          try {            dos.writeUTF(chatKey+"name:"+name+"end;"+str);            chattxt.setText("");            }catch (SocketTimeoutException e) {             System.out.println("接超,服器未或IP");             Toast.makeText(SocketmsgActivity.this, "接超,服器未或IP", Toast.LENGTH_SHORT).show();             Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);            startActivity(intent);            SocketmsgActivity.this.finish();             e.printStackTrace();           } catch (IOException e) {            // TODO Auto-generated catch block             System.out.println("接超,服器未或IP");             Toast.makeText(SocketmsgActivity.this, "接超,服器未或IP", Toast.LENGTH_SHORT).show();             Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);            startActivity(intent);            SocketmsgActivity.this.finish();             e.printStackTrace();          }        }      });    }  }    private Runnable doThread = new Runnable() {    public void run() {      System.out.println("running!");      ReceiveMsg();    }  };      public void connect() {    try {      s = new Socket();      isa = new InetSocketAddress(ip,Integer.parseInt(port));       s.connect(isa,5000);       if(s.isConnected()){        dos = new DataOutputStream (s.getOutputStream());        dis = new DataInputStream (s.getInputStream());        dos.writeUTF(chatKey+"online:"+name);        /**         * 这里是关键,我在此耗时8h+         * 原因是 子线程不能直接更新UI         * 为此,我们需要通过Handler物件,通知主线程Ui Thread来更新界面。         * */        thread = new Thread(null, doThread, "Message");         thread.start();         System.out.println("connect");         isContect=true;      }     }catch (UnknownHostException e) {       System.out.println("接失");      Toast.makeText(SocketmsgActivity.this, "接失", Toast.LENGTH_SHORT).show();      Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);      startActivity(intent);      SocketmsgActivity.this.finish();       e.printStackTrace();     }catch (SocketTimeoutException e) {       System.out.println("接超,服器未或IP");       Toast.makeText(SocketmsgActivity.this, "接超,服器未或IP", Toast.LENGTH_SHORT).show();      Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);      startActivity(intent);      SocketmsgActivity.this.finish();       e.printStackTrace();     }catch (IOException e) {       System.out.println("接失");       e.printStackTrace();     }  }    public void disConnect() {    if(dos!=null){    try {              dos.writeUTF(chatKey+"offline:"+name);          } catch (IOException e1) {      // TODO Auto-generated catch block      e1.printStackTrace();    }    try {      s.close();    } catch (IOException e) {       e.printStackTrace();    }    }  }     /**   * 线程监视Server信息*/  private void ReceiveMsg() {    if (isContect) {      try {        while ((reMsg = dis.readUTF()) != null) {          System.out.println(reMsg);          if (reMsg != null) {            try {              Message msgMessage = new Message();              msgMessage.what = 0x1981;              handler.sendMessage(msgMessage);              Thread.sleep(100);            } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();            }          }        }      } catch (SocketException e) {        // TODO: handle exception        System.out.println("exit!");      } catch (IOException e) {        // TODO Auto-generated catch block        e.printStackTrace();      }    }  }   /**   * 通过handler更新UI*/  Handler handler = new Handler() {    public void handleMessage(Message msg) {      switch (msg.what) {      case 0x1981:        chatbox.setText(chatbox.getText() + reMsg + '/n');        chatbox.setSelection(chatbox.length());        break;      }    }  };    @Override  protected void onDestroy() {    // TODO Auto-generated method stub    super.onDestroy();    disConnect();    //System.exit(0);  }    @Override  public boolean onCreateOptionsMenu(Menu menu) {    // TODO Auto-generated method stub    menu.add(0, 1, 1, "初始化置");    menu.add(0, 2, 2, "退出");    return super.onCreateOptionsMenu(menu);  }  @Override  public boolean onOptionsItemSelected(MenuItem item) {    // TODO Auto-generated method stub    if(item.getItemId()==1){      Intent intent = new Intent(SocketmsgActivity.this,IniActivity.class);      startActivity(intent);      SocketmsgActivity.this.finish();    }else if(item.getItemId()==2){      disConnect();      SocketmsgActivity.this.finish();       android.os.Process.killProcess(android.os.Process.myPid());      System.exit(0);    }    return super.onOptionsItemSelected(item);  }  public void InitDatabase(){         if(!config.path.exists()){       config.path.mkdirs();        Log.i("LogDemo", "mkdir");     }      if(!config.f.exists()){         try{          config.f.createNewFile();         Log.i("LogDemo", "create a new database file");      }catch(IOException e){          Log.i("LogDemo",e.toString());      }      }     try {      if(tabIsExist("config")==false){        db = SQLiteDatabase.openOrCreateDatabase(config.f, null);         db.execSQL("create table config(_id integer primary key autoincrement," +            "ip varchar(128),port varchar(10),name varchar(32))");        Log.i("LogDemo", "create a database");        db.close();      }    } catch (Exception e) {      // TODO: handle exception      Log.i("LogDemo",e.toString());    }  }    /**   * check the database is already exist   * @param tabName   * @return*/  public boolean tabIsExist(String tabName){    boolean result = false;    if(tabName == null){        return false;    }    Cursor cursor = null;    db = SQLiteDatabase.openOrCreateDatabase(config.f, null);     try {      String sql = "select count(*) as c from sqlite_master where type ='table' " +            "and name ='"+tabName.trim()+"' ";      cursor = db.rawQuery(sql, null);      if(cursor.moveToNext()){        int count = cursor.getInt(0);        if(count>0){          result = true;        }      }            } catch (Exception e) {        // TODO: handle exception    }     cursor.close();    db.close();    return result;  }}

2.初始化IP和端口Activity, IniActivity.java

public class IniActivity extends Activity{  private EditText ip,port;  private Button nextButton;  private String getip,getport;  private ProgressDialog progressDialog;  private InetSocketAddress isa = null;   private SQLiteDatabase db;  private String ipstring=null,portString=null;  private int row=0;  @Override  protected void onCreate(Bundle savedInstanceState) {    // TODO Auto-generated method stub    super.onCreate(savedInstanceState);    setContentView(R.layout.config);        ip = (EditText)findViewById(R.id.ip);    port = (EditText)findViewById(R.id.port);    nextButton = (Button)findViewById(R.id.next);            db = SQLiteDatabase.openOrCreateDatabase(config.f, null);    try {      Cursor cursor = db.query("config", new String[]{"ip","port"},null,null, null, null, null);      while(cursor.moveToNext()){        ipstring = cursor.getString(cursor.getColumnIndex("ip"));        portString = cursor.getString(cursor.getColumnIndex("port"));        row++;      }      ip.setText(ipstring);      port.setText(portString);      cursor.close();    } catch (Exception e) {      // TODO: handle exception      System.out.println(e.toString());    }    db.close();        nextButton.setOnClickListener(new nextButtonListenner());  }    class nextButtonListenner implements OnClickListener{    @Override    public void onClick(View v) {      // TODO Auto-generated method stub      getip = ip.getText().toString().trim();      getport = port.getText().toString().trim();      if(getip=="" || getip==null || getip.equals("")){        Toast.makeText(IniActivity.this, "入IP", Toast.LENGTH_SHORT).show();        ip.setFocusable(true);      }else if(getport=="" || getport==null || getport.equals("")){        Toast.makeText(IniActivity.this, "入端口", Toast.LENGTH_SHORT).show();        port.setFocusable(true);      }else{      //progressDialog = ProgressDialog.show(IniActivity.this, "", "稍後...", true, false);//new Thread() {//@Override//public void run() {          try {            Socket s = new Socket();            isa = new InetSocketAddress(getip,Integer.parseInt(getport));             s.connect(isa,5000);             //showDialog("接成功",IniActivity.this);            try {              //生成ContentValues对象              ContentValues values = new ContentValues();              //想该对象当中插入键值对,其中键是列名,值是希望插入到这一列的值,值必须和数据库当中的数据类型一致              values.put("ip", getip);              values.put("port",getport);              db = SQLiteDatabase.openOrCreateDatabase(config.f, null);               if(row==0){                db.insert("config", null, values);              }else{                db.update("config", values ,null,null);              }              Toast.makeText(IniActivity.this, "接成功", Toast.LENGTH_SHORT);              s.close();              Intent intent = new Intent(IniActivity.this,IniuserActivity.class);              startActivity(intent);              IniActivity.this.finish();              db.close();            } catch (Exception e) {              // TODO: handle exception              showDialog("置失,不可用",IniActivity.this);            }                                  } catch (UnknownHostException e) {            // TODO Auto-generated catch block            e.printStackTrace();            showDialog("接失,IP或者端口不可用",IniActivity.this);          }catch (SocketTimeoutException e) {             System.out.println("接超,服器未或IP");             showDialog("接超,服器未或IP",IniActivity.this);             e.printStackTrace();          }          catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();            showDialog("接失,IP或者端口不可用",IniActivity.this);          }          //progressDialog.dismiss();//finish();//}//}.start();      }          }      }    /**   * define a dialog for show the message   * @param mess   * @param activity*/  public void showDialog(String mess,Activity activity){   new AlertDialog.Builder(activity).setTitle("信息")    .setMessage(mess)    .setNegativeButton("定",new DialogInterface.OnClickListener()    {     public void onClick(DialogInterface dialog, int which)     {          }    })    .show();  }}

3.初始化用户名称Activity, IniuserActivity.java

public class IniuserActivity extends Activity{  private EditText name;  private Button ok;  private SQLiteDatabase db;    private String nameString;  @Override  protected void onCreate(Bundle savedInstanceState) {    // TODO Auto-generated method stub    super.onCreate(savedInstanceState);    setContentView(R.layout.configuser);        name = (EditText)findViewById(R.id.name);    ok = (Button)findViewById(R.id.ok);    ok.setOnClickListener(new okButtonListenner());            db = SQLiteDatabase.openOrCreateDatabase(config.f, null);    try {      Cursor cursor = db.query("config", new String[]{"name"},null,null, null, null, null);      while(cursor.moveToNext()){        nameString = cursor.getString(cursor.getColumnIndex("name"));      }      name.setText(nameString);      cursor.close();    } catch (Exception e) {      // TODO: handle exception      System.out.println(e.toString());    }    db.close();  }    class okButtonListenner implements OnClickListener{    @Override    public void onClick(View v) {      // TODO Auto-generated method stub      String getname = name.getText().toString().trim();      if(getname==""){        Toast.makeText(IniuserActivity.this, "入您的呢", Toast.LENGTH_SHORT).show();        name.setFocusable(true);      }else{              try {          //生成ContentValues对象          ContentValues values = new ContentValues();          //想该对象当中插入键值对,其中键是列名,值是希望插入到这一列的值,值必须和数据库当中的数据类型一致          values.put("name", getname);          db = SQLiteDatabase.openOrCreateDatabase(config.f, null);           db.update("config",values,null,null);          Toast.makeText(IniuserActivity.this, "置完成", Toast.LENGTH_SHORT).show();          Intent intent = new Intent(IniuserActivity.this,SocketmsgActivity.class);          startActivity(intent);          IniuserActivity.this.finish();          db.close();        } catch (Exception e) {          // TODO: handle exception          showDialog("置失,不可用",IniuserActivity.this);        }      }    }      }    /**   * define a dialog for show the message   * @param mess   * @param activity*/  public void showDialog(String mess,Activity activity){   new AlertDialog.Builder(activity).setTitle("信息")    .setMessage(mess)    .setNegativeButton("定",new DialogInterface.OnClickListener()    {     public void onClick(DialogInterface dialog, int which)     {          }    })    .show();  }}

4.config.java

public class config{  public static String SDCARD = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();  public static File path = new File(SDCARD+"/RunChatDatabase/"); //数据库文件目录    public static File f = new File(SDCARD+"/RunChatDatabase/config.db"); //数据库文件 }

布局文件:

1.main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:orientation="vertical" android:layout_width="fill_parent"  android:layout_height="fill_parent">  <EditText android:id="@+id/chatbox" android:layout_width="fill_parent"    android:layout_height="fill_parent" android:layout_weight="1">  </EditText>  <EditText android:id="@+id/chattxt" android:layout_width="fill_parent"    android:layout_height="wrap_content" android:gravity="top"    android:hint="你想和对方说点什么?">  </EditText>  <Button android:id="@+id/chatOk" android:layout_width="fill_parent"    android:layout_height="wrap_content" android:text="Send"     android:textSize="@dimen/btn1">  </Button></LinearLayout>

2.config.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">  <TextView android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="初始化置"    android:textSize="@dimen/h2"/>  <TextView android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="服器IP"    android:textSize="@dimen/h3"/>  <EditText android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:hint="192.168.2.214"    android:id="@+id/ip"    android:textSize="@dimen/et1"/>      <TextView android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="端口"    android:textSize="@dimen/h3"/>  <EditText android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:hint="8888"    android:id="@+id/port"    android:textSize="@dimen/et1"/>      <Button android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="下一步"    android:id="@+id/next"    android:textSize="@dimen/btn1"/>    </LinearLayout>

3.configuer.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">  <TextView android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="初始化置"    android:textSize="@dimen/h2"/>  <TextView android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="您的呢"    android:textSize="@dimen/h3"/>  <EditText android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:hint="仔"    android:id="@+id/name"    android:maxLength="20"    android:textSize="@dimen/et1"/>      <Button android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="完成"    android:id="@+id/ok"    android:textSize="@dimen/btn1"/>  </LinearLayout>style文件:dimens.xml<?xml version="1.0" encoding="utf-8"?><resources>     <dimen name="h3">30dip</dimen>   <dimen name="h2">40dip</dimen>   <dimen name="btn1">30dip</dimen>  <dimen name="et1">25dip</dimen></resources>

最后是服务器文件:Server.java

import java.io.*;import java.net.*;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.*;import javax.sound.sampled.Port;import javax.swing.JOptionPane;public class Server {    ServerSocket ss = null;  private String getnameString=null;  boolean started = false;  List<Client> clients = new ArrayList<Client>();  List<Info> infos = new ArrayList<Info>();  public static void main(String[] args) {    String inputport = JOptionPane.showInputDialog("入服器使用的端口:");    int port = Integer.parseInt(inputport);    new Server().start(port);  }   public void start(int port) {    try {      ss = new ServerSocket(port);      System.out.println("服器");      started = true;    } catch (BindException e) {       System.out.println(" 端口已经被占用");       System.exit(0);      }     catch (IOException e) {       e.printStackTrace();     }   try {     while (started) {       Socket s = ss.accept();       Client c = new Client (s);       System.out.println("a client is connected");       new Thread(c).start();       clients.add(c);                   }   } catch (IOException e) {      e.printStackTrace();     }     finally {      try {        ss.close();      } catch (IOException e) {         e.printStackTrace();        }     }  }  public List<Client> getClient(){    return clients;  } class Client implements Runnable {   private String chatKey="SLEEKNETGEOCK4stsjeS";   private Socket s = null;   private DataInputStream dis = null;   private DataOutputStream dos = null;   private boolean bConnected = false;   private String sendmsg=null;   Client (Socket s) {    this.s = s;    try {     dis = new DataInputStream (s.getInputStream());     dos = new DataOutputStream (s.getOutputStream());     bConnected = true;    } catch(IOException e) {       e.printStackTrace();      }   }      public void send (String str) {          try {       //System.out.println(s);       dos.writeUTF(str+"");       dos.flush();     } catch(IOException e) {       clients.remove(this);       System.out.println("对方已经退出了");     }   }   public void run() {     try {      while (bConnected) {        String str = dis.readUTF();        DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        String date = " ["+df.format(new Date())+"]";        if(str.startsWith(chatKey+"online:")){          Info info = new Info();          getnameString = str.substring(27);                    info.setName(getnameString);          infos.add(info);          for (int i=0; i<clients.size(); i++) {           Client c = clients.get(i);           c.send(getnameString+" on line."+date);          }          System.out.println(getnameString+" on line."+date);        }else if(str.startsWith(chatKey+"offline:")){          getnameString = str.substring(28);          clients.remove(this);          for (int i=0; i<clients.size(); i++) {             Client c = clients.get(i);             c.send(getnameString+" off line."+date);            }          System.out.println(getnameString+" off line."+date);        }        else{          int charend = str.indexOf("end;");          String chatString = str.substring(charend+4);          String chatName = str.substring(25, charend);                    sendmsg=chatName+date+"/n"+chatString;           for (int i=0; i<clients.size(); i++) {            Client c = clients.get(i);            c.send(sendmsg);           }          System.out.println(sendmsg);        }       }     } catch (SocketException e) {       System.out.println("client is closed!");       clients.remove(this);     } catch (EOFException e) {        System.out.println("client is closed!");        clients.remove(this);      }      catch (IOException e) {        e.printStackTrace();      }      finally {       try {        if (dis != null) dis.close();        if (dos != null) dos.close();        if (s != null) s.close();       } catch (IOException e) {          e.printStackTrace();        }      }   } }  class Info{   private String info_name = null;   public Info(){        }   public void setName(String name){     info_name = name;   }   public String getName(){     return info_name;   } }}

以上只是一个粗略的聊天室功能,如果要实现私聊,还需要保存该Socket关联的客户信息。一个客户端可以将信息发送另一个指定客户端。实际上,我们知道所有客户端只与服务器连接,客户端之间并没有互相连接。这个功能等我以后有时间再写个demo.....

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表