Java网络编程-代理2

1.Socket与ServerSocket介绍

(1)客户端使用Socket与服务方通话

getInputStream() 即可获得服务方发送的消息,getOutputStream即可发送消息给服务方

(2)服务方使用 ServerSocket 来监听客户端的接入

ServerSocket.accept() 即监听入站请求,当有客户端接入时,得到一个Socket

这时服务方可以拿着这个 Socket 对话

此时你把这个服务方这个Socket看成和客户端Socket一样,双方可以平等的对话了


2.服务方与客户端通信简单程序

(1)编写服务方主程序

package com.yli.socket;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 启动ServerSocke,并与客户端通信
 * 
 * @author yli
 *
 * @see InputThread
 * @see OutputThread
 * @see StartClientTest
 */
public class StartServerTest {

	public static void main(String[] args) {
		ServerSocket server = null;
		try {
			// 绑定本机7000号端口,启动 socket服务
			server = new ServerSocket();
			// server.setSoTimeout(300000);
			server.bind(new InetSocketAddress(InetAddress.getLocalHost(), 7000));

			System.out.println("start server...");

			// 此处只接受一个监听,然后跟接入方对话
			// 所以把while注释掉,不注释的话,就允许接入多个客户端
			// (但Socket的队列长长度可设置,一般不能超过操作系统默认的,比如50个)
			// while (true) {
			try {
				// 监听客户端连接
				Socket socket = server.accept();

				// 启动一个线程处理:客户端的输入
				String user = "server";
				Thread in = new InputThread(socket.getInputStream());
				in.start();

				// 启动一个线程处理:服务端的响应(本例服务端也从控制台输入一些消息,作为对客户端的响应)
				Thread out = new OutputThread(socket.getOutputStream(), user);
				out.start();

				try {
					in.join();
					out.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

			} catch (IOException e) {
				e.printStackTrace();
			}
			// }

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != server) {
				try {
					server.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			System.out.println("-------- stop server --------");
		}
	}

}

(2)编写客户端主程序

package com.yli.socket;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
 * 启动一个客户端来测试
 * 
 * @author yli
 *
 *
 * @see StartServerTest
 */
public class StartClientTest {
	
	public static boolean isClosed = false;

	public static void main(String[] args) {
		// Socket 在Input、Output 两个流中任意一个被关闭
		// 或者应用程序执行完毕,或者在抛出异常之后,Socket都会自动关闭
		// 但是关闭输入流、输出流的效果不一样,可以测试得出结论
		/**
		 * 1. 客户端主动输入 bye ,客户端的输出流主动关闭, OutputThread 主动正常退出!(此时客户端Socket其实已被关闭)
		 * 这将触发客户端的输入流也会被关闭,它是由于捕获到socket被关闭的异常,导致InputThread线程也退出
		 * 这时客户端 main 主线程里面的 in.join() 和 out.join() 也只行完毕,主线程因此执行完毕
		 * 这才导致 客户端主动结束了整个应用程序
		 * 
		 * 客户端输入 bye,由于服务端是输入流获取到这个信息,这时服务端的InputThread 主动正常退出!(此时服务端用于和客户端保持连接的Socket已被关闭)
		 * 但是服务端 main 主线程却没有结束!!是因为服务端 OutputThread 即输出流的线程未结束
		 * 这时在服务端,你仍然可以在控制台写消息,只是发送的时候,输出流才捕获到Socket已被关闭
		 * 这才导致 OutputThread会退出,最终main主线程主动退出
		 * 
		 * 2.反过来测试:服务端主动输入 bye ,过程其实是一样的,服务端由于主动关闭输出流,将导致输入流也主动关闭,最终整个main主线程退出
		 * 但是客户端是因为输入流收到 bye ,主动关闭输入流,但这也不会导致整个客户端线程退出!
		 * 
		 */
		
		Socket client = new Socket();
		try {
			// 等待服务端响应 30秒钟超时(本例服务端也需要人从控制台输入...等待时间设置长一些)
			client.setSoTimeout(300000);
			// 连接到本机7000号端口的server-socket服务
			client.connect(new InetSocketAddress(InetAddress.getLocalHost(), 7000));

			
			System.out.println("已经连上远程主机,开始对话吧:");
			
			String user = "client";
			InputThread in = new InputThread(client.getInputStream());
			in.start();

			Thread out = new OutputThread(client.getOutputStream(), user);
			out.start();
			

			in.join();
			out.join();

		} catch (SocketException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			if (null != client) {
				try {
					client.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}
}

class HandlerClientInput {

}

(3)编写服务方和客户端都依赖的输入输出处理程序

package com.yli.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 启动一个线程:不停的监听来自对方输入的消息!
 * 当对方输入 bye 时,认为要结束对话,主动关闭输入流
 * @author yli
 *
 */
public class InputThread extends Thread {

	private BufferedReader reader;
	

	public InputThread(InputStream in) {
		reader = new BufferedReader(new InputStreamReader(in));
	}

	@Override
	public void run() {

		// 读取对方的输入:服务端和客户端都可以输入一些信息
		String line = null;
		try {
			while (true) {
				line = reader.readLine();
				if (line == null || "".equals(line.trim())) {
					// 如果客户端输入空消息,则继续监听输入
					continue;
				} 
				// 打印对方发送的消息
				System.out.println(line);
				
				// 如果读取到bye,那么结束对话!
				if (line.endsWith("bye")) {
					System.out.println("**********主动结束对话 **************");
					break;
				}
			}
			
		} catch (IOException e) {
			System.out.println("************* Input **************");
			e.printStackTrace();
			System.out.println("************* Input **************");
		} finally {
			if (null != reader) {
				try {
					// 运行结束后:关闭输入流
					reader.close();
					System.out.println("输入流主动-正常关闭!");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

package com.yli.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;


/**
 * 启动一个线程:这里通过控制台来输入消息,并把消息写出去
 * 当你输入 bye 时,认为是主动结束对话,程序将主动关闭输出流
 * 
 * @author yli
 *
 */
public class OutputThread extends Thread {

	private OutputStreamWriter writer;
	private String user;

	public OutputThread(OutputStream out, String user) {
		writer = new OutputStreamWriter(out);
		this.user = user;
	}

	@Override
	public void run() {
		// 从控制台录入:如果是客户端录入,在发给服务端;反之则是服务端录入发给客户端!
		BufferedReader reader = null;
		try {
			String line;
			reader = new BufferedReader(new InputStreamReader(System.in));
			while (true) {
				line = reader.readLine();
				if (null == line || "".equals(line.trim())) {
					System.out.println("请输入内容...");
				} else {
					// 把内容输出到客户端!
					writer.write(user + ":" + line + "\r\n");
					writer.flush();
				}
				// 如果输入 bye 说明主动终止对话,服务方和客户端都可以主动终止!
				if ("bye".equals(line)) {
					break;
				}
			}
		} catch (IOException e) {
			System.out.println("************* Output **************");
			e.printStackTrace();
			System.out.println("************* Output **************");
		} finally {
			if (null != reader) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != writer) {
				try {
					writer.close();
					System.out.println("输出流主动-正常关闭!");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

启动主程序和客户端程序,可以在 cmd 命令行使用 java 命令启动,或者直接在eclipse中运行

开始客户度和服务端的对话吧,测试结果如下


3.需要关注的一些问题

(1)服务方如果允许多个客户端接入,那么会产生多个线程

线程数量太多,系统会崩溃,可以借助 ServerSocketChannel类

它提供基于通道(而非阻塞IO)的多路复用技术,这样一个线程可处理多个连接,效率更高

(2)实例化ServerSocket时可以显示指定queue即队列深度

即允许多少个客户端同时连接,但这个连接不会超过操作系统默认的允许最大值,一般是50

(3)如果客户端非正常退出,比如网络中断,服务方如何能主动感知到

比如服务方实时记录在线人数,客户端异常退出,服务方怎么能捕获到,一般可以采用心跳检测




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值