下面通过一个例子来加深理解:
第一步:写服务端线程类
- public class Server {
- //端口号
- static final int PORTNO = 8888;
- public static void main(String[] args) throws IOException {
- //服务短的额socket
- ServerSocket s = new ServerSocket(PORTNO);
- System.out.println("the server is start:"+s);
- try {
- for(;;){
- //阻塞,直到有客户端连接
- Socket socket = s.accept();
- //通过构造函数启动线程
- new ThreadServer(socket);
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally{
- s.close();
- }
- }
- }
说明:
1.这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。
2.这个类提供了两种类型的重载函数。在参数类型为Socket的构造函数里, 通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。
3.在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。
4.catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。
上述的线程主体代码将会在服务器端Server类里被调用。
第二步:服务端
- public class Server {
- //端口号
- static final int PORTNO = 8888;
- public static void main(String[] args) throws IOException {
- //服务短的额socket
- ServerSocket s = new ServerSocket(PORTNO);
- System.out.println("the server is start:"+s);
- try {
- for(;;){
- //阻塞,直到有客户端连接
- Socket socket = s.accept();
- //通过构造函数启动线程
- new ThreadServer(socket);
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally{
- s.close();
- }
- }
- }
- 说明:
- <p>1.首先定义了通讯所用的端口号。</p><p>2.在main函数里,根据端口号,创建一个ServerSocket类型的服务器端的Socket,用来同客户端通讯。</p><p>3.在for(;;)的循环里,调用accept方法,监听从客户端请求过来的socket,请注意这里又是一个阻塞。当客户端有请求过来时,将通过ServerThreadCode的构造函数, 创建一个线程类,用来接收客户端发送来的字符串。在这里我们可以再一次观察ServerThreadCode类,在其中,这个类通过构造函数里的start方法,开启run方法,而在run方法里,是通过sin对象来接收字符串,通过sout对象来输出。</p><p>4.在finally从句里,关闭服务器端的Socket,从而结束本次通讯。</p>
第三步:客户端线程类
- public class ThreadClient extends Thread{
- //客户端的socket
- private Socket socket;
- //线程统计数,用来给线程编号
- private static int count = 0;
- private int clientId = count++;
- private BufferedReader in;
- private PrintWriter out;
- //构造函数
- public ThreadClient(InetAddress addr){
- try {
- socket = new Socket(addr,8888);
- } catch (Exception e) {
- e.printStackTrace();
- }
- try {
- //实例化IO对象
- in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
- start();
- } catch (IOException e) {
- try {
- socket.close();
- } catch (Exception e2) {
- e2.printStackTrace();
- }
- }
- }
- @Override
- public void run() {
- try {
- out.println("hello server , my id is "+clientId);
- String str = in.readLine();
- System.out.println(str);
- out.println("byebye");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- socket.close();
- } catch (Exception e2) {
- e2.printStackTrace();
- }
- }
- }
- }
说明:
1. 在构造函数里, 通过参数类型为InetAddress类型参数和8888,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。
2. 在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。
3.同样地,catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。
第四步:客户端
- public class Client {
- public static void main(String[] args) throws UnknownHostException {
- int threadNo = 0;
- InetAddress addr = InetAddress.getByName("localhost");
- for(threadNo = 0;threadNo<3;threadNo++){
- new ThreadClient(addr);
- }
- }
- }
说明:
通过for循环,根据指定的待创建的线程数量,通过ThreadClient构造函数,创建若干个客户端线程,同步地和服务器端通讯。
这段代码执行以后,在客户端将会有3个通讯线程,每个线程首先将先向服务器端发送"Hello Server,My id is "的字符串,然后发送”byebye”,终止该线程的通讯。
执行测试:
第一步,我们先要启动服务器端的服务器端代码,启动后,在控制台里会出现如下的提示信息:
the server is start:ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=8888]
上述的提示信息里,我们同样可以看到,服务器在开启服务后,会阻塞在accept这里,直到有客户端请求过来。
第二步,我们在启动完服务器后,运行客户端代码,运行后,我们观察服务器端的控制台,会出现如下的信息:
the server is start:ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=8888]
in service received the info:hello server , my id is 0
closing the server socket!
close the server socket and the io.
in service received the info:hello server , my id is 1
in service received the info:hello server , my id is 2
closing the server socket!
close the server socket and the io.
closing the server socket!
close the server socket and the io.
其中,第一行是原来就有,在后面的几行里,首先将会输出了从客户端过来的线程请求信息,比如
in service received the info:hello server , my id is 0
接下来则会显示关闭Server端的IO和Socket的提示信息。
这里,请大家注意,由于线程运行的不确定性,从第二行开始的打印输出语句的次序是不确定的。但是,不论输出语句的次序如何变化,我们都可以从中看到,客户端有三个线程请求过来,并且,服务器端在处理完请求后,会关闭Socker和IO。
第三步,当我们运行完Client.java的代码后,并切换到Client.java的控制台,我们可以看到如下的输出:
hello server , my id is 0
hello server , my id is 2
hello server , my id is 1
这说明在客户端开启了3个线程,并利用这3个线程,向服务器端发送字符串。
而在服务器端,用accept方法分别监听到了这3个线程,并与之对应地也开了3个线程与之通讯。