socket通信是基于底层TCP/IP协议实现的。这种服务端不需要任何的配置文件和tomcat就可以完成服务端的发布,使用纯java代码实现通信。socket是对TCP/IP的封装调用,本身并不是一种协议,我们通过socket来调用协议来跟服务端进行通信和数据的传输。socket就像客户端与服务端之间的一条信息通道,每一个不同的客户端都会建立一个独立的socket,双方都没有关闭连接的话,连接—也就是建立好的这条socket通道将一直保持,服务端要跟那一个客户端通信只需要找到对应的socket对象就可以进行数据传递。
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
一. 服务端:在客户端跟服务端通信之前,服务端必须先开启。首先来看一下服务端Socket的编写吧。服务端就是一个简单的java项目,由于聊天室可能会有多个客户端同时连接并发送消息,我们这里使用线程池来处理客户端的请求。
List<Socket> list = new ArrayList<Socket>(); ExecutorService executorService; BufferedReader br; private static final int PORT = 12345; private static final int POOL_SIZE = 5 ; public Socket_Server() throws IOException { executorService = Executors.newFixedThreadPool(POOL_SIZE); ServerSocket serverSocket = new ServerSocket(PORT); System.out.println(serverSocket.getInetAddress().getHostAddress() + ":服务端就绪。"); Socket client = null; while (true) {//为每一个连接到服务器的客户端分配一个线程进行消息的接收和发送 client = serverSocket.accept(); list.add(client); executorService.execute(new Service(client)); } }
首先我们创建了一个大小为5的固定大小线程池,并创建端口号为12345的服务端socket接收客户端请求,通过一个while循环不断轮询来自服务端的连接请求,在while循环里面调用了serverSocket.accept();是线程进入阻塞状态,也就是说在没有接收到客户端的请求时,程序将一直停留在这里,当有客户端连接服务端是,代码开始往下走,我们把接收到的客户端socket放入list里面,这样我们就把所有连接到服务端的socket保存下来了,这样就使得我们可以随时对任一客户端进行数据传递。之后就是线程池调用execute执行一个线程,把连接过来的socket作为参数传进去。接下来分析下service的内容:
class Service implements Runnable { Socket client; BufferedReader br; String msg = ""; public Service(Socket client) { this.client = client; try { br = new BufferedReader(new InputStreamReader( client.getInputStream())); msg = "用户:" + client.getInetAddress() + "加入了聊天室,当前人数:" + list.size(); sendMsg(); } catch (Exception e) { e.printStackTrace(); } } public void run() { try { while (true) { if ((msg = br.readLine()) != null) { if(msg.equals("bye")){ list.remove(this.client) ; br.close() ; msg = "用户:" + client.getInetAddress() + "离开了聊天室,当前人数:" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "说:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } } public void sendMsg() {//为每一个用户发送这个消息:msg PrintWriter pw; System.out.println(msg); for (Socket client : list) { try { pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter( client.getOutputStream()))); pw.println(msg); pw.flush() ; } catch (Exception e) { e.printStackTrace(); } } } }
在service的构造方法中使用了作为参数传进来的socket,在里面我们通过这个socket获取输入流包装成一个BufferedReader,br = new BufferedReader(new InputStreamReader(client.getInputStream()));这里我们是主要是针对聊天,所以使用的是字符流进行数据的传输,这个类里面声明了一个成员变量msg,通过这个变量来给每个客户端发送信息。下面看下sendMsg方法:
public void sendMsg() {//为每一个用户发送这个消息:msg PrintWriter pw; System.out.println(msg); for (Socket client : list) { try { pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter( client.getOutputStream()))); pw.println(msg); pw.flush() ; } catch (Exception e) { e.printStackTrace(); } } }
这里我们通过遍历list里面的每个socket获得它的的输出流并且包装成PrintWriter 向客户端发送信息, pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));向每一个客户端发送成员变量msg内容,在PrintWriter调用print之后一定要调用flush刷新输出流进行数据的传递,否则客户端无法接收到服务端发送的数据。接下来看这个类的住方法 run:
public void run() { try { while (true) { if ((msg = br.readLine()) != null) { if(msg.equals("bye")){ list.remove(this.client) ; br.close() ; msg = "用户:" + client.getInetAddress() + "离开了聊天室,当前人数:" + list.size(); sendMsg() ; client.close() ; break ; }else{ msg = client.getInetAddress() + "说:" + msg; sendMsg() ; } } } } catch (Exception e) { e.printStackTrace(); } }
与前面类似,也是通过一个while进行无限循环进行读取socket的输入流,如果内容不为空就调用sendmsg对每一个客户端进行信息发送,有个小小的处理就是如果发送过来的信息是bye的时候就断开对应socket的链接,退出聊天室。以上是对服务端的分析,接下来我们来看Android客户端。
二. 客户端:客户端基本与服务端一样,我们直接上代码吧。
//首先还是贴出成员变量 private Button send; private EditText edt_input; private TextView txt_content; private static final String SERVER_PATH = "172.16.10.18"; private static final int PORT = 12345; private Socket client; private BufferedReader br; private PrintWriter pw; private StringBuffer content = new StringBuffer(); private void initView() { send = (Button) findViewById(R.id.send); edt_input = (EditText) findViewById(R.id.input); txt_content = (TextView) findViewById(R.id.chat_content); // --------发起网络连接----- new Thread() { public void run() { try { client = new Socket(SERVER_PATH, PORT); br = new BufferedReader(new InputStreamReader( client.getInputStream())); pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream()))); } catch (Exception e) { e.printStackTrace(); } } }.start(); send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (client != null && client.isConnected() && !client.isOutputShutdown()) { String input = edt_input.getText().toString(); pw.println(input); pw.flush(); ((EditText)findViewById(R.id.input)).setText(""); } } }); new Thread(this).start(); }
首先还是传统的new一个thread来建立与服务端的连接,因为主线程不能访问网络,由于我们客户端肯定是只有当前这一个socket的,所以只有一个线程,不用跟服务端一样使用线程池了。连接一旦建立,获取socket的输入输出流来包装成对应的BufferedReader和PrintWriter:br = new BufferedReader(new InputStreamReader(client.getInputStream()));pw = new PrintWriter(new BufferedWriter( new OutputStreamWriter(client.getOutputStream())));由于这里只会使用一个socket,所以这里的相关变量都可以使用成员变量。然后走下来就是对send按钮的监听,点击发送的话,把内容发送给服务端,服务端接收到之后发送给每一个保持着链接的客户端。这个activity也是实现了runnable接口的,接下来看run方法:
public void run() { while (true) { if (client != null && client.isConnected() && !client.isInputShutdown()) { try { String response; if ((response = br.readLine()) != null) { content.append(response + "\n"); mHandler.sendEmptyMessage(UPDATE_CONTENT); } } catch (Exception e) { e.printStackTrace(); } } } }
这里跟服务端一样,通过一个while无限循环读取来自服务端的信息,一旦读取到信息之后就通过handler从子线程发送消息到主线程,主线程进行数据的更新,其实就是向显示聊天室内容的textview追加聊天内容并且setText上去:
Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_CONTENT: txt_content.append(content); break; default: break; } }; };
总体来说客户端还是比服务端容易点,没有涉及到并发,只需要做当前这个客户端对应的socket通信就行了。以上就是对socket的一个简单总结和在安卓里面的简单应用实现聊天室功能。效果图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持自由互联。