• 加入多线程


    实际应用中的客户端可能需要和服务器端保持长时间通信,即服务器需要不断地读取客户端数据,并向客户端写入数据;客户端也需要不断地读取服务器数据,并向服务器写入数据。

    当使用传统BufferedReader的readLine()方法读取数据时,当该方法成功返回之前,线程被阻塞,程序无法继续执行。考虑到这个原因,服务器应该为每个Socket单独启动一条线程,每条线程负责与一个客户端进行通信。

    客户端读取服务器数据的线程同样会被阻塞,所以系统应该单独启动一条线程,该线程专门负责读取服务器数据。

    下面考虑实现一个简单的C/S聊天室应用,服务器端则应该包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据(从客户端发送过来的数据),并将读到的数据向每个Socket输出流发送一遍(将一个客户端发送的数据"广播"给其他客户端),因此需要在服务器端使用List来保存所有的Socket。

    下面是服务器端的实现代码,程序为服务器提供了两个类,一个是创建ServerSocket监听的主类,另一个是负责处理每个Socket通信的线程类。

    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;

    public class MyServer {
      //定义保存所有Socket的ArrayList
      public static ArrayList<Socket> socketList = new ArrayList<Socket>();
      public static void main(String[] args) throws IOException {
      ServerSocket ss = new ServerSocket();
      while(true){
        //此行代码会阻塞,将一直等待别人的连接
        Socket s = ss.accept();
        socketList.add(s);
        //每当客户端连接后启动一条ServerTread线程为该客户服务
        new Thread(new ServerThread(s)).start();
      }
      }
    }

    上面的程序是服务器端只负责接收客户端Socket的连接请求,每当客户端Socket连接到该ServerSocket之后,程序将对应Socket假如socketList集合中保存,并为该Socket启动一条线程,该线程负责处理该Socket所有的通信任务。

    服务器端线程类的代码如下:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.Socket;
    //负责处理每个线程通信的线程类
    public class ServerThread implements Runnable {
      //定义当前线程所处理的Socket
      Socket s = null;
      //该线程处理的Socket所对应的输入流
      BufferedReader br = null;
      public ServerThread(Socket s) throws UnsupportedEncodingException, IOException{
        this.s = s;
        //初始化该Socket对应的输入流
        br = new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));
      }

      @Override
      public void run() {
        try {
          String content = null;
          //采用循环不断从Socket中读取客户端发送过来的数据
          while((content = readFromClient()) != null){
            //遍历socketList中的每个Socket
            //将读到的内容向每个Socket发送一次
            for(Socket s: MyServer.socketList){
              OutputStream os = s.getOutputStream();
              os.write((content+" ").getBytes("utf-8"));
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      //定义读取客户端数据的方法
      private String readFromClient(){
        try {
          return br.readLine();
        }
        //如果捕捉到异常,表明该Socket对应的客户端已经关闭
        catch (IOException e) {
          // 删除该Socket
          MyServer.socketList.remove(s);
        }
        return null;
      }

    }

    上面的服务器端线程类不断读取客户端数据,程序使用readFromClient()方法来读取客户端数据,如果读取数据过程中捕获到IOException异常,则表明该Socket对应的客户端Socket出现了问题,程序就将该Socket从socketList中删除。

    当服务器线程读到客户端数据之后,程序遍历socketList集合,并将该数据向socketList集合中的每个Socket发送一次-----该服务器线程将把从Socket中读到的数据向socketList中的每个Socket转发一次,如run()线程执行体中的代码所示。

    上面的程序中的代码将网络的字节输入流转换为字符输入流时,指定了转换所用的字符串:UTF-8,这也是由于客户端写过来的数据是采用UTF-8字符集进行编码的,所以此处的服务器端也要使用UTF-8字符集进行解码。当需要编写跨平台的网络通信程序时,使用UTF-8字符集进行编码、解码是一种较好的解决方案。

    每个客户端应该包含两条线程:一条负责生成主界面,并响应用户动作,并将用户输入的数据写入Socket对应的输出流;另一条负责读取Socket对应输入流中的数据(从服务器发送过来的数据),并负责将这些数据在程序界面上显示出来。

    客户端程序的界面布局代码如下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      tools:context=".MutilTreadClient" >

      <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >
        <!-- 定义一个文本框,它用于接收用户的输入 -->
        <EditText
          android:id="@+id/input"
          android:layout_width="240px"
          android:layout_height="wrap_content"
          />

        <Button
          android:id="@+id/send"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:paddingLeft="8px"
          android:text="@string/send"
          />

      </LinearLayout>

      <!-- 定义一个文本框。它用于显示来自服务器的信息 -->
      <EditText
        android:id="@+id/show"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="top"
        android:editable="false"
        android:cursorVisible="false"
        />

     

    </LinearLayout>

    客户端的Activity负责生成程序界面,并为程序的按钮单击事件绑定事件监听器,当用户单击按钮时向服务器发送信息。客户端的Activity代码如下:

    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;

    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.app.Activity;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;

    public class MutilTreadClient extends Activity {
      //定义界面上的两个文本框
      EditText input ;
      EditText show;
      //定义界面上的一个按钮
      Button send;
      OutputStream os;
      Handler handler;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mutil_tread_client);
        input = (EditText) findViewById(R.id.input);
        send = (Button) findViewById(R.id.send);
        show = (EditText) findViewById(R.id.show);
        Socket s ;
        handler = new Handler(){
          @Override
          public void handleMessage(Message msg) {
            // 如果消息来自于子线程
            if(msg.what == 0x123){
              //将读取的内容追加显示在文本框中
              show.append(" "+msg.obj.toString());
            }
          }
        };
        try {
          s = new Socket("172.18.5.198", 30000);
          //客户端启动ClientTread线程不断读取来自服务器的数据
          new Thread(new ClientThread(s,handle)).start();
          os = s.getOutputStream();
        } catch (Exception e) {
          e.printStackTrace();
        }
        send.setOnClickListener(new OnClickListener() {

          @Override
          public void onClick(View v) {
            try {
              //将用户在文本框内输入的内容写入网络
              os.write((input.getText().toString()+" ").getBytes("utf-8"));
              //清空input文本框
              input.setText("");
            } catch (Exception e) {
              e.printStackTrace();
            }

          }
        });
      }

    }

    当用户单击该程序界面中的“发送”按钮之后,程序将会把input输入框中的内容写入该Socket对应的输出流。

    除此之外,当主线程使用Socket连接到服务器之后,并启动ClientThread来处理该线程的Socket通信。ClientThread线程负责读取Socket输入流中的内容,并将读到的这些内容在界面上的文本框内显示出来。由于Android不允许子线程访问界面组件,因此上面的程序定义了一个Handler来处理来自子线程的消息。

    程序的子线程负责读取来自网络的数据,读到数据之后便通过Handler对象发送一条消息,该程序的子线程代码如下:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;

    import android.os.Handler;
    import android.os.Message;

    public class ClientThread implements Runnable{
      //该程序负责处理的Socket
      private Socket s;
      private Handler handler;
      //该线程所处理的Socket所对应的输入流
      BufferedReader br = null;
      public ClientThread(Socket s , Handler handler) throws IOException{
        this.s = s;
        this.handler = handler;
        br = new BufferedReader(new InputStreamReader(s.getInputStream()));
      }

      @Override
      public void run() {
        try {
          String content = null;
          //不断读取Socket输入流中的内容
          while((content = br.readLine()) != null){
            //每当读到来自服务器的数据后,发送消息通知程序界面显示该数据
            Message msg = new Message();
            msg.what = 0x123;
            msg.obj = content;
            handler.sendMessage(msg);
          }
        } catch (Exception e) {
          e.printStackTrace();
        }

      }

    }

    上面的线程只是不断获取Socket输入流中的内容,当读到Socket输入流中的内容后,便通过Handler对象那个发送一条消息,消息负责携带读到的数据。

     先运行上面程序中的MyServer类,该类运行后只是作为服务器,看不到任何输出,接着可以运行Android客户端-----相当于启动聊天室客户端登录该服务器,接着可以看到在任何一个Android客户端输入一些内容后单击“发送”按钮,将可看到所有客户端(包括自己)都会收到他刚刚输入的内容。

  • 相关阅读:
    Dictionary-Guided Editing Networks for Paraphrase Generation解读
    CGMH:Constrained Sentence Generation by Metropolis-Hastings Sampling解读
    Text Infilling解读
    K-MEANS算法
    SVM-支持向量机算法
    003-文本分析
    002-贝叶斯拼写纠正实例
    001-贝叶斯算法简介
    【矩阵的乘积/复合变换】- 图解线性代数 05
    【行列式】- 图解线性代数 04
  • 原文地址:https://www.cnblogs.com/jiww/p/5633113.html
Copyright © 2020-2023  润新知