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

来源:互联网 发布:苹果手机怎么清楚数据 编辑:程序博客网 时间:2024/06/07 03:49
 

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

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

       第一步:写服务端线程类

public class ThreadServer extends Thread {private static String NAME = "服务器Deny";private BufferedReader reader;private PrintWriter writer;private Socket socket;private String str;@Overridepublic void run() {try {while (true) {str = reader.readLine();if (str.endsWith("byebye"))break;System.out.println("客户端---" + socket + "说:" + str);writer.println("我是" + NAME + ",客户端说:" + str);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if (socket != null) {try {socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}public ThreadServer(Socket s) {try {socket = s;reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);start();} catch (IOException e) {e.printStackTrace();}}}


说明

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 ServerMain {private static int PORT = 8899;public static void main(String[] args) throws IOException {ServerSocket server = null;Socket socket = null;server = new ServerSocket(PORT);System.out.println("server start...");try {for (;;) {socket = server.accept();System.out.println("client连接上:" + socket.toString());new ThreadServer(socket);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if (server != null)server.close();}}}

说明:

1.首先定义了通讯所用的端口号。2.在main函数中根据端口号创建一个ServerSocket类型的服务端socket,用来同客户端通讯。3.在for循环中,调用accept方法,监听从客户端请求过来的socket,请注意,这里又是一个阻塞。当客户端有请求过来时,将通过ThreadServer的构造函数,创建一个线程类,用来接收客户端发送过来的字符串。在这里可以再一次观察ThreadServer类,在构造方法中调用start()方法,开启run方法,在run方法中输入输出。4.在finally中关闭socket,结束通讯。


第三步:客户端线程类

public class ThreadClient extends Thread {private Socket socket;private static int PROT = 8899;private int id = ID++;private static int ID = 0;private BufferedReader reader;private PrintWriter writer;@Overridepublic void run() {try {writer.println("HI server, i am " + id);String str = reader.readLine();System.out.println("id " + id + ",come from server" + str);writer.println("byebye");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if (socket != null)try {socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public ThreadClient(InetAddress address) {try {socket = new Socket(address, PROT);reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);start();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}


说明:

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

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

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

第四步:客户端启动

public class ClientMain {public static void main(String[] args) {int num;try {InetAddress address = InetAddress.getByName("localhost");for (num = 0; num < 3; num++) {new ThreadClient(address);}} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}


说明:

       通过for循环,根据指定的待创建的线程数量,通过ThreadClient构造函数,创建若干个客户端线程,同步地和服务器端通讯。
这段代码执行以后,在客户端将会有3个通讯线程,每个线程首先将先向服务器端发送信息,然后发送”byebye”,终止该线程的通讯。

执行测试:

第一步,我们先要启动服务器端的服务器端代码,启动后,在控制台里会出现如下的提示信息:

server start...

第二步,我们在启动完服务器后,运行客户端代码,运行后,我们观察服务器端的控制台,会出现如下的信息:

client连接上:Socket[addr=/127.0.0.1,port=11138,localport=8899]
client连接上:Socket[addr=/127.0.0.1,port=11139,localport=8899]
客户端---Socket[addr=/127.0.0.1,port=11138,localport=8899]说:HI server, i am 0
客户端---Socket[addr=/127.0.0.1,port=11139,localport=8899]说:HI server, i am 1
client连接上:Socket[addr=/127.0.0.1,port=11140,localport=8899]
客户端---Socket[addr=/127.0.0.1,port=11140,localport=8899]说:HI server, i am 2


这里,请大家注意,由于线程运行的不确定性,从第二行开始的打印输出语句的次序是不确定的。但是,不论输出语句的次序如何变化,我们都可以从中看到,客户端有三个线程请求过来,并且,服务器端在处理完请求后,会关闭Socker和IO。

 

第三步,当我们运行完Client.java的代码后,并切换到Client.java的控制台,我们可以看到如下的输出:

id 0,come from server我是服务器Deny,客户端说:HI server, i am 0
id 1,come from server我是服务器Deny,客户端说:HI server, i am 1
id 2,come from server我是服务器Deny,客户端说:HI server, i am 2
这说明在客户端开启了3个线程,并利用这3个线程,向服务器端发送字符串。

 

而在服务器端,用accept方法分别监听到了这3个线程,并与之对应地也开了3个线程与之通讯。