Android 基于Socket的聊天室实例
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的请求,服务器端也对应产生一个socket socket s = ss.accept(); //下面就可以使用socket进行通信了 ... }
客户端通常可使用socket的构造器来连接到指定服务器
client示例:
//创建连接到服务器、30000端口的socket socket s = new socket("192.168.2.214" , 30000); //下面就可以使用socket进行通信了 ...
这样server和client就可以进行一个简单的通信了
当然,我们要做的是多客户,所以每当客户端socket连接到该serversocket之后,程序将对应socket加入clients集合中保存,并为该socket启动一条线程,该线程负责处理该socket所有的通信任务
//定义保存所有socket的arraylist public 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"?> <linearlayout xmlns: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"?> <linearlayout xmlns: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.....
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: 从零开始搭建MySQL MMM架构
推荐阅读
-
讲解Android中的Widget及AppWidget小工具的创建实例
-
Android 基于Socket的聊天室实例
-
Android 基于Socket的聊天应用实例(二)
-
Android App在ViewPager中使用Fragment的实例讲解
-
实例讲解Android中ViewPager组件的一些进阶使用技巧
-
Android系统的五种数据存储形式实例(二)
-
Android App中实现图片异步加载的实例分享
-
socket + select 完成伪并发操作的实例
-
Android中编写属性动画PropertyAnimation的进阶实例
-
Android中Property Animation属性动画编写的实例教程