Android开发笔记(一百一十一)聊天室中的Socket通信

Socket通信

基本概念

对于程序开发来说,网络通信的基础就是Socket,但因为是基础,所以用起来不容易,今天我们就来谈谈Socket通信。计算机网络有个大名鼎鼎的TCP/IP协议,普通用户在电脑上设置本地连接的ip时,便经常看到下图的弹窗,注意红框部分已经很好地描述了TCP/IP协议的作用。



TCP/IP是个协议组,它分为三个层次:网络层、传输层和应用层:
网络层包括:IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
传输层包括:TCP协议、UDP协议。
应用层包括:HTTP、FTP、TELNET、SMTP、DNS等协议。
之前我们提到的网络编程,其实都是应用层方面的http或ftp编程;而socket属于传输层的技术,它的api实现TCP协议后即可用于http通信,实现UDP协议后即可用于ftp通信,当然也可以直接在底层进行点对点通信,比如即时通信软件(QQ、微信)这样就是。


扯远了,言归正传,java的socket编程主要使用Socket和ServerSocket两个类,下面是相关类的使用说明。


Socket

Socket是最常用的,客户端和服务端都要用到,它描述了两边对套接字(即Socket)处理的一般行为,主要方法说明如下:
connect : 连接指定ip和端口。该方法用于客户端连接服务端。
getInputStream : 获取输入流。即自己收到对方发过来的数据。
getOutputStream : 获取输入流。即自己向对方发送的数据。
getInetAddress : 获取网络地址对象。该对象是一个InetAddress实例。
isConnected : 判断socket是否连上。
isClosed : 判断socket是否关闭。
close : 关闭socket。


ServerSocket

ServerSocket仅用于服务端,它在运行时不停地侦听指定端口,主要方法说明如下:
构造函数 : 指定侦听哪个端口。
accept : 开始接收客户端的连接。有客户端连上时就返回一个Socket对象,若要持续侦听连接,得在循环中调用该函数。
getInetAddress : 获取网络地址对象。该对象是一个InetAddress实例。
isClosed : 判断socket服务器是否关闭。
close : 关闭socket服务器。


InetAddress

InetAddress是对网络地址的一个封装,主要方法说明如下:
getByName : 根据主机ip/名称获取InetAddress对象。
getHostAddress : 获取主机的ip地址。
getHostName : 获取主机的名称。
isReachable : 判断该地址是否可到达,即是否连通。


聊天室应用

实现原理

Socket在app开发中主要用于聊天/即时通信,因为涉及到客户端与服务端的交互,所以流程稍微复杂。首先要划分聊天业务的功能点,以QQ为例,我们平常看到的是三种页面:登录页面、好友列表页面、聊天页面。因此,对应的Socket功能也分为三类:
登录/注销:登录操作对应建立Socket连接,而注销操作对应断开Socket连接。
获取好友列表:与Socket有关的是获取当前在线的好友列表,客户端到服务端查询当前已建立Socket连接的好友列表。
发送消息/接收消息:发送/接收消息对应的是Socket的数据传输,发送消息操作是客户端A向服务端发送Socket数据,接收消息操作是服务端将收到的A消息向客户端B发送Socket数据。


其次在app端需要实现以下功能:
1、至少三个页面:登录页面、好友列表页面、聊天页面;
2、一个用于Socket通信的线程。由于在app运行过程中都要保持Socket连接,因此该Socket线程要放在自定义的Application类中。
3、页面向Socket线程发送消息的机制,用于登录请求、注销请求、获取好友列表请求、发送消息等等。主线程与子线程通信,我们这里采用Handler+Message机制来处理,有关该机制的使用说明参见《 Android开发笔记(四十八)Thread类实现多线程》。
4、Socket线程向页面发送消息的机制,用于返回好友列表、接收消息等等。因为返回消息会分发到不同的页面,采用Handler机制有困难,所以这里我们采用Broadcast广播来处理,在好友列表页面和聊天页面各注册一个广播接收器,用于根据服务器返回数据刷新UI。有关Broadcast及其接收器的使用说明参见《 Android开发笔记(四十二)Broadcast的生命周期》。


然后在服务端启动Socket服务器,要实现的功能有:
1、定义一个Socket连接的队列,用于保存当前连上的Socket请求;
2、循环侦听指定端口,一旦有新连接进来,则将该连接加入Socket队列,并启动新线程为该连接服务;
3、每个服务线程持续从Socket中读取客户端发过来的数据,并对不同请求做相应的处理:
a、如果是登录请求,则标识该Socket连接的用户昵称、设备编号、登录时间等信息;
b、如果是注销请求,则断开Socket连接,并从Socket队列中移除该连接;
c、如果是获取好友列表请求,则遍历Socket队列,封装好友列表数据并返回;
d、如果是发送消息请求,则根据好友的设备编号到Socket队列中查找对应的Socket连接,并向该连接返回消息内容;


最后还要定义一下服务端与客户端之间传输消息的格式,按惯例消息包分为包头与包体两块,包头用于标识操作类型、操作对象、操作时间等基本要素,而包体用于存放具体的消息内容(如好友列表、消息文本等等)。demo工程为简单起见,就不用xml或json等标准格式,直接用分隔符划分包头与包体,以及包头内部的各元素。


效果截图

博主在测试时,模拟器上开了一个app,登录名称是“在水一方”,真机上开了一个app,登录名称是“振兴中华”,两个app连的都是电脑上的Socket服务,从而模拟真实的聊天室环境。登录页面与好友列表页面比较简单,就不再截图了,截的都是聊天窗口页面。为了做得更逼真,中间消息窗口采用对方消息靠左对齐,我方消息靠右对齐的布局,并给双方消息着不同的背景色。具体截图如下,左侧图片是真机截图,右侧图片是模拟器截图。





代码示例

app端

几个注意点:
1、自定义Application类需要采用单例模式,确保Socket线程的唯一性,详细原因参见《 Android开发笔记(八十九)单例模式》。
2、Socket数据包不可直接用换行符“\n”做分隔符,因为在Socket通信中,换行符表示该数据包结束了,所以加了一个换行符,原来一个数据包就变成了两个数据包。正因如此,用户聊天的消息文本中若有换行符,则要先进行转义后才能发给Socket传输。
3、如果广播接收器在代码中动态注册,则不会收到Socket线程发出的广播消息;只有在AndroidManifest.xml中对接收器做静态注册,才能收到Socket线程发出的广播消息,具体原因不明,可能与线程有关。


下面是自定义Application类的代码:
  1. import com.example.exmsocket.thread.ClientThread;  
  2. import com.example.exmsocket.util.DateUtil;  
  3.   
  4. import android.app.Application;  
  5. import android.os.Build;  
  6. import android.os.Message;  
  7. import android.util.Log;  
  8.   
  9. public class MainApplication extends Application {  
  10.     private static final String TAG = "MainApplication";  
  11.   
  12.     private static MainApplication mApp;  
  13.     private String mNickName;  
  14.     private ClientThread mClientThread;  
  15.   
  16.     public static MainApplication getInstance() {  
  17.         return mApp;  
  18.     }  
  19.   
  20.     @Override  
  21.     public void onCreate() {  
  22.         super.onCreate();  
  23.         mApp = this;  
  24.         mClientThread = new ClientThread(mApp);  
  25.         new Thread(mClientThread).start();  
  26.     }  
  27.       
  28.     public void sendAction(String action, String otherId, String msgText) {  
  29.         String content = String.format("%s,%s,%s,%s,%s%s%s\r\n",   
  30.                 action, Build.SERIAL, getNickName(), DateUtil.getNowTime(),   
  31.                 otherId, ClientThread.SPLIT_LINE, msgText);  
  32.         Log.d(TAG, "sendAction : " + content);  
  33.         Message msg = Message.obtain();  
  34.         msg.obj = content;  
  35.         if (mClientThread==null || mClientThread.mRecvHandler==null) {  
  36.             Log.d(TAG, "mClientThread or its mRecvHandler is null");  
  37.         } else {  
  38.             mClientThread.mRecvHandler.sendMessage(msg);  
  39.         }  
  40.     }  
  41.       
  42.     public void setNickName(String nickName) {  
  43.         mApp.mNickName = nickName;  
  44.     }  
  45.       
  46.     public String getNickName() {  
  47.         return mApp.mNickName;  
  48.     }  
  49.       
  50. }  


下面是登录页面的代码:
  1. import com.example.exmsocket.thread.ClientThread;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.view.View.OnClickListener;  
  8. import android.widget.Button;  
  9. import android.widget.EditText;  
  10. import android.widget.Toast;  
  11.   
  12. public class MainActivity extends Activity implements OnClickListener {  
  13.     private static final String TAG = "MainActivity";  
  14.   
  15.     private EditText et_name;  
  16.     private Button btn_ok;  
  17.       
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.         et_name = (EditText) findViewById(R.id.et_name);  
  23.         btn_ok = (Button) findViewById(R.id.btn_ok);  
  24.         btn_ok.setOnClickListener(this);  
  25.     }  
  26.       
  27.     @Override  
  28.     public void onClick(View v) {  
  29.         if (v.getId() == R.id.btn_ok) {  
  30.             String nickName = et_name.getText().toString().trim();  
  31.             if (nickName.length() <= 0) {  
  32.                 Toast.makeText(this"请输入您的昵称", Toast.LENGTH_SHORT).show();  
  33.             } else {  
  34.                 MainApplication.getInstance().setNickName(nickName);  
  35.                 MainApplication.getInstance().sendAction(ClientThread.LOGIN, """");  
  36.                 Intent intent = new Intent(this, FriendListActivity.class);  
  37.                 startActivity(intent);  
  38.                 finish();  
  39.             }  
  40.         }  
  41.     }  
  42. }  


下面是好友列表页面的代码:
  1. import java.util.ArrayList;  
  2.   
  3. import com.example.exmsocket.adapter.Friend;  
  4. import com.example.exmsocket.adapter.FriendListAdapter;  
  5. import com.example.exmsocket.thread.ClientThread;  
  6.   
  7. import android.app.Activity;  
  8. import android.content.BroadcastReceiver;  
  9. import android.content.Context;  
  10. import android.content.Intent;  
  11. import android.os.Bundle;  
  12. import android.os.Handler;  
  13. import android.util.Log;  
  14. import android.view.View;  
  15. import android.view.View.OnClickListener;  
  16. import android.widget.Button;  
  17. import android.widget.ListView;  
  18. import android.widget.Toast;  
  19.   
  20. public class FriendListActivity extends Activity implements OnClickListener {  
  21.     private static final String TAG = "FriendListActivity";  
  22.   
  23.     private static Context mContext;  
  24.     private static ListView lv_friend;  
  25.     private Button btn_refresh;  
  26.   
  27.     @Override  
  28.     protected void onCreate(Bundle savedInstanceState) {  
  29.         super.onCreate(savedInstanceState);  
  30.         setContentView(R.layout.activity_list);  
  31.         mContext = getApplicationContext();  
  32.         lv_friend = (ListView) findViewById(R.id.lv_friend);  
  33.         btn_refresh = (Button) findViewById(R.id.btn_refresh);  
  34.         btn_refresh.setOnClickListener(this);  
  35.     }  
  36.       
  37.     @Override  
  38.     protected void onResume() {  
  39.         mHandler.postDelayed(mRefresh, 500);  
  40.         super.onResume();  
  41.     }  
  42.       
  43.     @Override  
  44.     protected void onDestroy() {  
  45.         MainApplication.getInstance().sendAction(ClientThread.LOGOUT, """");  
  46.         super.onDestroy();  
  47.     }  
  48.       
  49.     private Handler mHandler = new Handler();  
  50.     private Runnable mRefresh = new Runnable() {  
  51.         @Override  
  52.         public void run() {  
  53.             MainApplication.getInstance().sendAction(ClientThread.GETLIST, """");  
  54.         }  
  55.     };  
  56.   
  57.     @Override  
  58.     public void onClick(View v) {  
  59.         if (v.getId() == R.id.btn_refresh) {  
  60.             mHandler.post(mRefresh);  
  61.         }  
  62.     }  
  63.       
  64.     public static class GetListReceiver extends BroadcastReceiver {  
  65.   
  66.         @Override  
  67.         public void onReceive(Context context, Intent intent) {  
  68.             if (intent != null) {  
  69.                 Log.d(TAG, "onReceive");  
  70.                 String content = intent.getStringExtra(ClientThread.CONTENT);  
  71.                 if (mContext != null && content != null && content.length() > 0) {  
  72.                     int pos = content.indexOf(ClientThread.SPLIT_LINE);  
  73.                     String head = content.substring(0, pos);  
  74.                     String body = content.substring(pos + 1);  
  75.                     String[] splitArray = head.split(ClientThread.SPLIT_ITEM);  
  76.                     if (splitArray[0].equals(ClientThread.GETLIST)) {  
  77.                         String[] bodyArray = body.split("\\|");  
  78.                         ArrayList<Friend> friendList = new ArrayList<Friend>();  
  79.                         for (int i = 0; i < bodyArray.length; i++) {  
  80.                             String[] itemArray = bodyArray[i].split(ClientThread.SPLIT_ITEM);  
  81.                             if (bodyArray[i].length() > 0 && itemArray != null && itemArray.length >= 3) {  
  82.                                 friendList.add(new Friend(itemArray[0], itemArray[1], itemArray[2]));  
  83.                             }  
  84.                         }  
  85.                         if (friendList.size() > 0) {  
  86.                             FriendListAdapter adapter = new FriendListAdapter(mContext, friendList);  
  87.                             lv_friend.setAdapter(adapter);  
  88.                             lv_friend.setOnItemClickListener(adapter);  
  89.                         }  
  90.                     } else {  
  91.                         String hint = String.format("%s\n%s", splitArray[0], body);  
  92.                         Toast.makeText(mContext, hint, Toast.LENGTH_SHORT).show();  
  93.                     }  
  94.                 }  
  95.             }  
  96.         }  
  97.     }  
  98.   
  99. }  


下面是聊天页面的代码:
  1. import com.example.exmsocket.thread.ClientThread;  
  2. import com.example.exmsocket.util.DateUtil;  
  3. import com.example.exmsocket.util.MetricsUtil;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.BroadcastReceiver;  
  7. import android.content.Context;  
  8. import android.content.Intent;  
  9. import android.graphics.Color;  
  10. import android.os.Build;  
  11. import android.os.Bundle;  
  12. import android.util.Log;  
  13. import android.view.Gravity;  
  14. import android.view.View;  
  15. import android.view.View.OnClickListener;  
  16. import android.view.ViewGroup.LayoutParams;  
  17. import android.widget.Button;  
  18. import android.widget.EditText;  
  19. import android.widget.LinearLayout;  
  20. import android.widget.TextView;  
  21. import android.widget.Toast;  
  22.   
  23. public class ChatActivity extends Activity implements OnClickListener {  
  24.     private static final String TAG = "ChatActivity";  
  25.   
  26.     private static Context mContext;  
  27.     private TextView tv_other;  
  28.     private EditText et_input;  
  29.     private static TextView tv_show;  
  30.     private static LinearLayout ll_show;  
  31.     private Button btn_send;  
  32.     private String mOtherId;  
  33.     private static int dip_margin;  
  34.     
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.activity_chat);  
  39.         mContext = getApplicationContext();  
  40.         tv_other = (TextView) findViewById(R.id.tv_other);  
  41.         et_input = (EditText) findViewById(R.id.et_input);  
  42.         tv_show = (TextView) findViewById(R.id.tv_show);  
  43.         ll_show = (LinearLayout) findViewById(R.id.ll_show);  
  44.         btn_send = (Button) findViewById(R.id.btn_send);  
  45.         btn_send.setOnClickListener(this);  
  46.           
  47.         dip_margin = MetricsUtil.dip2px(mContext, 5);  
  48.         Bundle bundle = getIntent().getExtras();  
  49.         mOtherId = bundle.getString("otherId""");  
  50.         String desc = String.format("与%s聊天", bundle.getString("otherName"""));  
  51.         tv_other.setText(desc);  
  52.     }  
  53.   
  54.     @Override  
  55.     public void onClick(View v) {  
  56.         if (v.getId() == R.id.btn_send) {  
  57.             String body = et_input.getText().toString();  
  58.             String append = String.format("%s %s\n%s",  
  59.                     MainApplication.getInstance().getNickName(),  
  60.                     DateUtil.formatTime(DateUtil.getNowTime()), body);  
  61.             appendMsg(Build.SERIAL, append);  
  62.             MainApplication.getInstance().sendAction(ClientThread.SENDMSG, mOtherId, body);  
  63.             et_input.setText("");  
  64.         }  
  65.     }  
  66.       
  67.     private static void appendMsg(String deviceId, String append) {  
  68.         //tv_show.setText(tv_show.getText().toString() + append);  
  69.         int gravity = deviceId.equals(Build.SERIAL) ? Gravity.RIGHT : Gravity.LEFT;  
  70.         int bg_color = deviceId.equals(Build.SERIAL) ? 0xffccccff : 0xffffcccc;  
  71.         LinearLayout ll_append = new LinearLayout(mContext);  
  72.         LinearLayout.LayoutParams ll_params = new LinearLayout.LayoutParams(  
  73.                 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);  
  74.         ll_params.setMargins(dip_margin, dip_margin, dip_margin, dip_margin);  
  75.         ll_append.setLayoutParams(ll_params);  
  76.         ll_append.setGravity(gravity);  
  77.           
  78.         TextView tv_append = new TextView(mContext);  
  79.         tv_append.setText(tv_show.getText().toString() + append);  
  80.         tv_append.setTextColor(Color.BLACK);  
  81.         LinearLayout.LayoutParams tv_params = new LinearLayout.LayoutParams(  
  82.                 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  
  83.         tv_append.setLayoutParams(tv_params);  
  84.         tv_append.setBackgroundColor(bg_color);  
  85.         ll_append.addView(tv_append);  
  86.   
  87.         ll_show.addView(ll_append);  
  88.     }  
  89.   
  90.     public static class RecvMsgReceiver extends BroadcastReceiver {  
  91.   
  92.         @Override  
  93.         public void onReceive(Context context, Intent intent) {  
  94.             if (intent != null) {  
  95.                 Log.d(TAG, "onReceive");  
  96.                 String content = intent.getStringExtra(ClientThread.CONTENT);  
  97.                 if (mContext != null && content != null && content.length() > 0) {  
  98.                     int pos = content.indexOf(ClientThread.SPLIT_LINE);  
  99.                     String head = content.substring(0, pos);  
  100.                     String body = content.substring(pos + 1);  
  101.                     String[] splitArray = head.split(ClientThread.SPLIT_ITEM);  
  102.                     if (splitArray[0].equals(ClientThread.RECVMSG)) {  
  103.                         String append = String.format("%s %s\n%s",  
  104.                                 splitArray[2], DateUtil.formatTime(splitArray[3]), body);  
  105.                         appendMsg(splitArray[1], append);  
  106.                     } else {  
  107.                         String hint = String.format("%s\n%s", splitArray[0], body);  
  108.                         Toast.makeText(mContext, hint, Toast.LENGTH_SHORT).show();  
  109.                     }  
  110.                 }  
  111.             }  
  112.         }  
  113.     }  
  114.   
  115. }  


下面是Socket线程的代码:
  1. import java.io.BufferedReader;  
  2. import java.io.InputStreamReader;  
  3. import java.io.OutputStream;  
  4. import java.net.InetSocketAddress;  
  5. import java.net.Socket;  
  6.     
  7. import android.content.Context;  
  8. import android.content.Intent;  
  9. import android.os.Handler;  
  10. import android.os.Looper;  
  11. import android.os.Message;  
  12. import android.util.Log;  
  13.     
  14. public class ClientThread implements Runnable {  
  15.     private static final String TAG = "ClientThread";  
  16.     private static final String SOCKET_IP = "192.168.0.212";  // 模拟器使用  
  17.     //private static final String SOCKET_IP = "192.168.253.1";  // 真机使用  
  18.     private static final int SOCKET_PORT = 52000;  
  19.     public static String ACTION_RECV_MSG = "com.example.exmsocket.RECV_MSG";  
  20.     public static String ACTION_GET_LIST = "com.example.exmsocket.GET_LIST";  
  21.     public static String CONTENT = "CONTENT";  
  22.     public static String SPLIT_LINE = "|";  
  23.     public static String SPLIT_ITEM = ",";  
  24.   
  25.     public static String LOGIN = "LOGIN";  
  26.     public static String LOGOUT = "LOGOUT";  
  27.     public static String SENDMSG = "SENDMSG";  
  28.     public static String RECVMSG = "RECVMSG";  
  29.     public static String GETLIST = "GETLIST";  
  30.   
  31.     private Context mContext;  
  32.     private Socket mSocket;  
  33.     // 定义接收UI线程的Handler对象  
  34.     public Handler mRecvHandler;  
  35.     private BufferedReader mReader = null;  
  36.     private OutputStream mWriter = null;  
  37.       
  38.     public ClientThread(Context context) {  
  39.         mContext = context;  
  40.     }  
  41.     
  42.     @Override    
  43.     public void run() {  
  44.         mSocket = new Socket();  
  45.         try {  
  46.             mSocket.connect(new InetSocketAddress(SOCKET_IP, SOCKET_PORT), 3000);  
  47.             mReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));  
  48.             mWriter = mSocket.getOutputStream();  
  49.             // 启动一条子线程来读取服务器相应的数据    
  50.             new Thread() {  
  51.                 @Override    
  52.                 public void run() {  
  53.                     String content = null;  
  54.                     try {  
  55.                         while ((content = mReader.readLine()) != null) {  
  56.                             // 读取到来自服务器的数据之后,发送消息通知  
  57.                             ClientThread.this.notify(0, content);  
  58.                         }  
  59.                     } catch (Exception e) {  
  60.                         e.printStackTrace();  
  61.                         ClientThread.this.notify(97, e.getMessage());  
  62.                     }  
  63.                 }  
  64.             }.start();  
  65.             Looper.prepare();  
  66.             mRecvHandler = new Handler() {  
  67.                 @Override    
  68.                 public void handleMessage(Message msg) {  
  69.                     // 接收到UI线程的中用户输入的数据  
  70.                     try {  
  71.                         mWriter.write(msg.obj.toString().getBytes("utf8"));  
  72.                     } catch (Exception e) {  
  73.                         e.printStackTrace();  
  74.                         ClientThread.this.notify(98, e.getMessage());  
  75.                     }  
  76.                 }  
  77.             };  
  78.             Looper.loop();  
  79.         } catch (Exception e) {  
  80.             e.printStackTrace();  
  81.             notify(99, e.getMessage());  
  82.         }  
  83.     }  
  84.       
  85.     private void notify(int type, String message) {  
  86.         if (type == 99) {  
  87.             String content = String.format("%s%s%s%s""ERROR", SPLIT_ITEM, SPLIT_LINE, message);  
  88.             Intent intent1 = new Intent(ACTION_RECV_MSG);  
  89.             intent1.putExtra(CONTENT, content);  
  90.             mContext.sendBroadcast(intent1);  
  91.             Intent intent2 = new Intent(ACTION_GET_LIST);  
  92.             intent2.putExtra(CONTENT, content);  
  93.             mContext.sendBroadcast(intent2);  
  94.         } else {  
  95.             int pos = message.indexOf(SPLIT_LINE);  
  96.             String head = message.substring(0, pos-1);  
  97.             String[] splitArray = head.split(SPLIT_ITEM);  
  98.             String action = "";  
  99.             if (splitArray[0].equals(RECVMSG)) {  
  100.                 action = ACTION_RECV_MSG;  
  101.             } else if (splitArray[0].equals(GETLIST)) {  
  102.                 action = ACTION_GET_LIST;  
  103.             }  
  104.             Log.d(TAG, "action="+action+", message="+message);  
  105.             Intent intent = new Intent(action);  
  106.             intent.putExtra(CONTENT, message);  
  107.             mContext.sendBroadcast(intent);  
  108.         }  
  109.     }  
  110.       
  111. }  


服务端

下面是服务端的主程序代码:
  1. import java.net.ServerSocket;  
  2. import java.util.ArrayList;  
  3.   
  4. public class ChatServer {  
  5.     private static final int SOCKET_PORT = 52000;  
  6.     public static ArrayList<SocketBean> mSocketList = new ArrayList<SocketBean>();  
  7.   
  8.     private void initServer() {  
  9.         try {  
  10.             // 创建一个ServerSocket,用于监听客户端Socket的连接请求  
  11.             ServerSocket server = new ServerSocket(SOCKET_PORT);  
  12.             while (true) {  
  13.                 // 每当接收到客户端的Socket请求,服务器端也相应的创建一个Socket  
  14.                 SocketBean socket = new SocketBean(DateUtil.getTimeId(), server.accept());  
  15.                 mSocketList.add(socket);  
  16.                 // 每连接一个客户端,启动一个ServerThread线程为该客户端服务  
  17.                 new Thread(new ServerThread(socket)).start();  
  18.             }  
  19.         } catch (Exception e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.     }  
  23.       
  24.     public static void main(String[] args) {  
  25.         ChatServer server = new ChatServer();  
  26.         server.initServer();  
  27.     }  
  28. }  


下面是服务线程的代码:
  1. import java.io.BufferedReader;  
  2. import java.io.IOException;  
  3. import java.io.InputStreamReader;  
  4. import java.io.PrintStream;  
  5.   
  6. public class ServerThread implements Runnable {  
  7.   
  8.     private SocketBean mSocket = null;  
  9.     private BufferedReader mReader = null;  
  10.   
  11.     public ServerThread(SocketBean mSocket) throws IOException {  
  12.         this.mSocket = mSocket;  
  13.         mReader = new BufferedReader(new InputStreamReader(  
  14.                 mSocket.socket.getInputStream()));  
  15.     }  
  16.   
  17.     @Override  
  18.     public void run() {  
  19.         try {  
  20.             String content = null;  
  21.             // 循环不断地从Socket中读取客户端发送过来的数据  
  22.             while ((content = mReader.readLine()) != null) {  
  23.                 System.out.println("content="+content);  
  24.                 int pos = content.indexOf("|");  
  25.                 // 包头格式为:动作名称|设备编号|昵称|时间|对方设备编号  
  26.                 String head = content.substring(0, pos);  
  27.                 String body = content.substring(pos+1);  
  28.                 String[] splitArray = head.split(",");  
  29.                 String action = splitArray[0];  
  30.                 System.out.println("action="+action);  
  31.                 if (action.equals("LOGIN")) {  
  32.                     login(splitArray[1], splitArray[2], splitArray[3]);  
  33.                 } else if (action.equals("LOGOUT")) {  
  34.                     logout(splitArray[1]);  
  35.                     break;  
  36.                 } else if (action.equals("SENDMSG")) {  
  37.                     sendmsg(splitArray[2], splitArray[4], splitArray[1], body);  
  38.                 } else if (action.equals("GETLIST")) {  
  39.                     getlist(splitArray[1]);  
  40.                 }  
  41.             }  
  42.         } catch (Exception e) {  
  43.             e.printStackTrace();  
  44.         }  
  45.     }  
  46.       
  47.     private void login(String deviceId, String nickName, String loginTime) throws IOException {  
  48.         for (int i=0; i<ChatServer.mSocketList.size(); i++) {  
  49.             SocketBean item = ChatServer.mSocketList.get(i);  
  50.             if (item.id.equals(mSocket.id)) {  
  51.                 item.deviceId = deviceId;  
  52.                 item.nickName = nickName;  
  53.                 item.loginTime = loginTime;  
  54.                 ChatServer.mSocketList.set(i, item);  
  55.                 break;  
  56.             }  
  57.         }  
  58.     }  
  59.       
  60.     private String getFriend() {  
  61.         String friends = "GETLIST,";  
  62.         for (SocketBean item : ChatServer.mSocketList) {  
  63.             if (item.deviceId!=null && item.deviceId.length()>0) {  
  64.                 String friend = String.format("|%s,%s,%s", item.deviceId, item.nickName, item.loginTime);  
  65.                 friends += friend;  
  66.             }  
  67.         }  
  68.         return friends;  
  69.     }  
  70.       
  71.     private void getlist(String deviceId) throws IOException {  
  72.         for (int i=0; i<ChatServer.mSocketList.size(); i++) {  
  73.             SocketBean item = ChatServer.mSocketList.get(i);  
  74.             if (item.id.equals(mSocket.id) && item.deviceId.equals(deviceId)) {  
  75.                 PrintStream printStream = new PrintStream(item.socket.getOutputStream());  
  76.                 printStream.println(getFriend());  
  77.                 break;  
  78.             }  
  79.         }  
  80.     }  
  81.       
  82.     private void logout(String deviceId) throws IOException {  
  83.         for (int i=0; i<ChatServer.mSocketList.size(); i++) {  
  84.             SocketBean item = ChatServer.mSocketList.get(i);  
  85.             if (item.id.equals(mSocket.id) && item.deviceId.equals(deviceId)) {  
  86.                 PrintStream printStream = new PrintStream(item.socket.getOutputStream());  
  87.                 printStream.println("LOGOUT,|");  
  88.                 item.socket.close();  
  89.                 ChatServer.mSocketList.remove(i);  
  90.                 break;  
  91.             }  
  92.         }  
  93.     }  
  94.   
  95.     private void sendmsg(String otherName, String otherId, String selfId, String message) throws IOException {  
  96.         for (int i=0; i<ChatServer.mSocketList.size(); i++) {  
  97.             SocketBean item = ChatServer.mSocketList.get(i);  
  98.             if (item.deviceId.equals(otherId)) {  
  99.                 String content = String.format("%s,%s,%s,%s|%s",   
  100.                         "RECVMSG", selfId, otherName, DateUtil.getNowTime(), message);  
  101.                 PrintStream printStream = new PrintStream(item.socket.getOutputStream());  
  102.                 printStream.println(content);  
  103.                 break;  
  104.             }  
  105.         }  
  106.     }  
  107.   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值