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

java学习 专栏收录该内容
6 篇文章 0 订阅
 

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

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

       第一步:写服务端线程类

public class ThreadServer extends Thread {
	private static String NAME = "服务器Deny";
	private BufferedReader reader;
	private PrintWriter writer;
	private Socket socket;
	private String str;

	@Override
	public 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 block
			e.printStackTrace();
		} finally {
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.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 block
			e.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;

	@Override
	public 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 block
			e.printStackTrace();
		} finally {
			if (socket != null)
				try {
					socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.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 block
			e.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 block
			e.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个线程与之通讯。

  • 4
    点赞
  • 2
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值