05
2017
10

【Android】基于Socket的即时聊天(群聊)

近来感觉秋招无望,学习Socket的时候,便做了个基于Socket的群聊工具;
先看看最终效果吧
这里写图片描述

这里写图片描述
这里写图片描述

项目GitHub通道(详细代码请自行copy)

如何利用Socket通信

socket又称为“套接字”,建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。 ——来自百度百科

socket通信步骤(TCP)
1.建立SocketServer(服务端) 和 Socket (客户端)
2.打开两个端之间的输入输出流
3.进行读写操作
4.关闭socket与流

先来看第一步
SocketServer 与 Socket都是 java.net包里的,进行TCP通信的时候需要建立客户端与服务端。SocketServer 通过绑定端口(Port)来实现监听,而Socket则是指定服务端端口(Port)与地址(IP)。
下面来看一个简单的通信例子

//Server部分
ServerSocket server = new ServerSocket(6063);//实例化(传入端口号)
Socket s = server.accept();//调用accept接收socket
BufferedReader in = 
new BufferedReader(new InputStreamReader(s.getInputStream()));//获得socket的输入流(同样的,可以通过getOutput
Stream来获取输出流)
while((msg= in.readLine())!=null){
    System.out.println(msg);
    }
in.close();
s.close();


//Client
Socket s = new Socket("192.168.1.133",6063);//实例化Socket传入指定服务端地址和端口号
            System.out.println("客户端启动...");
            BufferedReader re = new BufferedReader(new InputStreamReader(System.in));//用输入流读取键盘的输入
            PrintWriter pw = new PrintWriter(s.getOutputStream(),true);//获取socket的输出流,第二个参数表示会自动flush
            String msg2;
            while(true){
                msg2 = re.readLine();
                pw.println(msg2);//输出(自动flush)
            }

这样我们就简单了实现了socket通信
当然要实现即时聊天并非这么几行就能搞定的。
即时聊天,重点在于流的控制,需要开辟多条线程去分别做不同的事情。
下面来本次项目的PC服务端代码

PCServer端代码


public class Server implements Runnable {
    List<Socket> sockets = new ArrayList<>();
    private static final String ServerIp = "192.168.1.133";
    private static final int ServerPort = 6066;

    @Override
    public void run() {
        try{
            System.out.println("服务端启动...");
            ServerSocket server = new ServerSocket(ServerPort);
            while(true){
                Socket client = server.accept();
                sockets.add(client);
                receiveThread re = new receiveThread(client);
                re.start();
            }
        }catch(Exception e){
            System.out.println("------S:Error 1-------");
            e.printStackTrace();
        }

    }
    //接受msg线程
    public class receiveThread extends Thread{
        Socket socket;
        private BufferedReader br;
        private PrintWriter pw;
        public String msg;
        public receiveThread(Socket s){
            socket = s;
        }
        public void run(){
            try{
                br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));//不转码在Android端会乱码
                msg = "sys:##:"+"欢迎"+socket.getInetAddress()+"进入聊天室,当前人数为"+sockets.size();
                sendMsg(InetAddress.getByName("1"));
                while((msg = br.readLine())!=null){
                    if(msg.equals("EndEndClosethesocket")){
                        close(socket.getInetAddress());
                    }else{
                        msg = socket.getInetAddress()+":##:"+msg;
                        sendMsg(socket.getInetAddress());
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        public void sendMsg(InetAddress ip){
            try{
                System.out.println(msg);
                for(int i = 0;i < sockets.size();i++){
                    if(!ip.equals(sockets.get(i).getInetAddress())){
                    pw = new PrintWriter(new OutputStreamWriter(sockets.get(i).getOutputStream(),"UTF-8"),true);
                    pw.println(msg);
                    pw.flush();
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    public void close(InetAddress ip){
        for(int i = 0;i < sockets.size();i++){
            if(sockets.get(i).getInetAddress()==ip){
                sockets.remove(i);
                msg ="sys:##:"+ip+"已经离开了聊天室";
                try{
                    sendMsg(InetAddress.getByName("1"));
                }catch(Exception e){
                    e.printStackTrace();
                }
                break;
            }
        }
    }
    }
    public static void main(String args[]){
        Thread thread = new Thread(new Server());
        thread.start();
    }

}

可以看到,这里用了一个ArrayList来存储SocketServer接收的Socket
然后将接收的Socket作为参数传入自定义的线程receiveThread中,然
后在这个线程中循环读取Client端发来的消息。然后通过sendMsg方法
广播这条消息。
下面看看Android客户端的主要代码

Android端核心代码

  protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       ...//详细代码请移步GitHub
                 if(socket==null||socket.isClosed()||!socket.isConnected()){
            getIPandPort();
            creatSock();
            Log.d("aaaaaaa", "onCreate: 1111111111");
        }else {
            Log.d("aaaaaaa", "onCreate: 122222222222");
            heart();
        }
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //发送button监听
                if(sendMsg(edit.getText().toString())){
                    edit.setText("");
                }
            }
        });
  }
   //读取ip与Port
    public void getIPandPort(){
        SharedPreferences preferences = getSharedPreferences("data", Context.MODE_PRIVATE);
        ServerIp = preferences.getString("ip","192.168.1.133");
        ServerPort = preferences.getInt("port",6066);
        title.setText(ServerIp+"\n"+ServerPort);
    }
    //心跳检测
 public void heart(){
        if (socket!=null){
            try{
                socket.sendUrgentData(0xff);
            }catch (Exception e){
                e.printStackTrace();
                reconn();
            }
        }else {
            reconn();
        }
    }
    //建立连接
    public void creatSock(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
// if (socket!=null){
// clconn();
// }
                    InetAddress inetAddress = InetAddress.getByName(ServerIp);
                    socket = new Socket(inetAddress, ServerPort);
                    AcceptMsg();
                }catch (Exception e) {
                    e.printStackTrace();
                    reconn();
                    Log.d("aaaa", "run: 连接失败");
                }
            }
        }).start();
    }
    public void reconn(){
        sendMessenger(new Msg("无法连接服务器...请重设PORT或IP",2,"sys"));
        showReconn();
    }
     public  void sendMessenger(final Msg msg){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                msgs.add(msg);
                adapter.notifyDataSetChanged();
            }
        });
    }
    //发送
    private boolean sendMsg(final String msg){
        if(socket!=null&&socket.isConnected()){
            if (!msg.equals("")){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                            pw.println(msg);
                            if (!msg.equals("EndEndClosethesocket")){
                                sendMessenger(new Msg(msg,0,"Me"));
                            }
                            Log.d("XXXXXXX", "发送成功");
                        }catch (Exception e) {
                            heart();
                        }


                    }
                }).start();
                return true;
            }
        }
        return false;
    }
    //接受
    public void AcceptMsg(){
        if (socket.isConnected()&&!socket.isClosed()){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        re = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        while((socketmsg=re.readLine())!=null){
                            String[] msg = socketmsg.split(":##:");
                            Msg m;
                            if(msg[0].equals("sys")){
                                m = new Msg(msg[1],2,msg[0]);
                            }else{
                                Log.d("sxxxxx", "run: "+msg[0]);
                                String ip = msg[0].substring(msg[0].length()-3,msg[0].length());
                                m = new Msg(msg[1],1,ip);
                            }
                            sendMessenger(m);
                            Log.d("xxxxxxxxx", "接受成功"+socketmsg);
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                        heart();
                        Log.d("aaaa", "run: 接受失败");
                    }
                }
            }).start();
        }
        heart();
    }

在安卓端实例化Socket时要注意将IP地址转化为InetAddress,再将参数传入。
另外,Socket操作不能在主线程里直接操作,否则会报错,应该新建线程对其
进行操作(无论是建立连接,发送消息,还是接收消息);
本次项目开发也遇到了许多坑。
一开始想用ViewPage+fragement来实现更好的界面效果,由于fragment的
重载的大坑,导致我最终放弃直接用fragment实现聊天界面
还有就是心跳包的问题,重复发送多次会导致服务端崩溃,捣鼓了一下午也没
弄好,最后还是没用上。
这次就到这里咯,收拾收拾好心情,面对十月的秋招吧!!!!

——-来自offer颗粒无收的大四狗

上一篇:DataPicker和TimePicker 下一篇:使用 RecyclerView 实现简单时间轴