socket编程(二)---- 使用套接字连接多个客户端

在(一)中,客户端和服务器之间只有一个通讯线程,所以它们之间只有一条Socket信道。如果我们引入多线程机制,则可以让一个服务器端同时监听并接收多个客户端的请求,并同步地为他们提供通讯服务。基于多线程的通讯方式,将大大地提高服务器端的利用效率,并能使服务器端能具备完善的服务功能。

       下面通过一个例子来加深理解: 

       第一步:写服务端线程类

  1. public class Server {  
  2.     //端口号  
  3.     static final int PORTNO = 8888;  
  4.     public static void main(String[] args) throws IOException {  
  5.         //服务短的额socket  
  6.         ServerSocket s = new ServerSocket(PORTNO);  
  7.         System.out.println("the server is start:"+s);  
  8.         try {  
  9.             for(;;){  
  10.                 //阻塞,直到有客户端连接  
  11.                 Socket socket = s.accept();  
  12.                 //通过构造函数启动线程  
  13.                 new ThreadServer(socket);  
  14.             }  
  15.         } catch (Exception e) {  
  16.             e.printStackTrace();  
  17.         } finally{  
  18.             s.close();  
  19.         }  
  20.     }  
  21. }  

说明:
1.这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。

2.这个类提供了两种类型的重载函数。在参数类型为Socket的构造函数里, 通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。

3.在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。

4.catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

上述的线程主体代码将会在服务器端Server类里被调用。

 

第二步:服务端

  1. public class Server {  
  2.     //端口号  
  3.     static final int PORTNO = 8888;  
  4.     public static void main(String[] args) throws IOException {  
  5.         //服务短的额socket  
  6.         ServerSocket s = new ServerSocket(PORTNO);  
  7.         System.out.println("the server is start:"+s);  
  8.         try {  
  9.             for(;;){  
  10.                 //阻塞,直到有客户端连接  
  11.                 Socket socket = s.accept();  
  12.                 //通过构造函数启动线程  
  13.                 new ThreadServer(socket);  
  14.             }  
  15.         } catch (Exception e) {  
  16.             e.printStackTrace();  
  17.         } finally{  
  18.             s.close();  
  19.         }  
  20.     }  
  21. }  
  22. 说明:  
  1. <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>  

第三步:客户端线程类

  1. public class ThreadClient extends Thread{  
  2.     //客户端的socket  
  3.     private Socket socket;  
  4.     //线程统计数,用来给线程编号  
  5.     private static int count = 0;  
  6.     private int clientId = count++;  
  7.     private BufferedReader in;  
  8.     private PrintWriter out;  
  9.     //构造函数  
  10.     public ThreadClient(InetAddress addr){  
  11.         try {  
  12.             socket = new Socket(addr,8888);  
  13.         } catch (Exception e) {  
  14.             e.printStackTrace();  
  15.         }  
  16.         try {  
  17.             //实例化IO对象  
  18.             in = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
  19.             out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);  
  20.             start();  
  21.         } catch (IOException e) {  
  22.             try {  
  23.                 socket.close();  
  24.             } catch (Exception e2) {  
  25.                 e2.printStackTrace();  
  26.             }  
  27.         }  
  28.     }  
  29.     @Override  
  30.     public void run() {  
  31.         try {  
  32.             out.println("hello server , my id is "+clientId);  
  33.             String str = in.readLine();  
  34.             System.out.println(str);  
  35.             out.println("byebye");  
  36.         } catch (Exception e) {  
  37.             e.printStackTrace();  
  38.         } finally {  
  39.             try {  
  40.                 socket.close();  
  41.             } catch (Exception e2) {  
  42.                 e2.printStackTrace();  
  43.             }  
  44.         }  
  45.     }  
  46. }  

说明:

1. 在构造函数里, 通过参数类型为InetAddress类型参数和8888,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。

2. 在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。

3.同样地,catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

第四步:客户端

  1. public class Client {  
  2.     public static void main(String[] args) throws UnknownHostException {  
  3.         int threadNo = 0;  
  4.         InetAddress addr = InetAddress.getByName("localhost");  
  5.         for(threadNo = 0;threadNo<3;threadNo++){  
  6.             new ThreadClient(addr);  
  7.         }  
  8.     }  
  9. }  

说明:

       通过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个线程与之通讯。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值