編輯:關於Android編程
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.....
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
這篇文章繼續分析Android動畫的TimeInterpolator(插值器)和TypeEvaluator(估值器)的簡單實現,以及分別去自定義插值器和估值器。一,Tim
在android上有很多跨進程的通訊方法例如aidl,messenger,ContentProvider,BroadCast,Socket等等,安卓進程間通信(IPC)
如果移動端訪問不佳或需要更好的閱讀體驗,歡迎使用 ==> Github 版使用 RadioGroup 和 ViewPager 實現更加可定制的效果。背景昨天設計圖剛
在這篇入門教程中,我們假定你已經有了PHP語言程序、MySQL數據庫、計算機網絡通訊及XML語言基礎。如果你還沒有,那麼請先學習相關知識。我們將使用微信公眾賬號方倍工作室