socket + 线程池

总结:
socket 通信
socket通信是网络中计算机进行通信的一种方法

客户端:

public class SocketClientHandlerImpl implements Runnable {
private Socket socket = null;
private DataInputStream dataInputStream = null;
private DataOutputStream dataOutputStream = null;
Scanner scanner = new Scanner(System.in);
public SocketClientHandlerImpl(Socket socket) throws Exception {
this.socket = socket;
dataInputStream = new DataInputStream(socket.getInputStream());
dataOutputStream = new DataOutputStream(socket.getOutputStream());
}

public void run() {
	new Thread(new Runnable() {
		public void run() {
			scanner.useDelimiter("\n");
			while (true) {
				try {
					System.out.println("请输入:");
					dataOutputStream.writeUTF(scanner.next().trim());
					dataOutputStream.flush();
					break;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}).start();
	while (true) {
		// 循环读取客户端发送的消息
		try {
			String data = dataInputStream.readUTF();
			System.out.println(data);
		} catch (Exception e) {
			try {
				dataInputStream.close();
				dataOutputStream.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			break;
		}
	}
}

public class Client {
Socket s1 = new Socket(“127.0.0.1”, 8888);
// 实例化输入流
DataInputStream dis = new DataInputStream(s1.getInputStream()); // 用dataInputStream包装一下然后就不会产生线程阻塞
// 实例化输出流
DataOutputStream dos = new DataOutputStream(s1.getOutputStream());
// 实例化并启动两个进程
new MyClientReader(dis, s1).start();
new MyClientWriter(dos).start();
}
}

客户端需要创建一个socket 对象用来做数据绑定 需要服务器的IP地址 和端口号
连接后会产生一堆输入输出流,这对流是客户端跟服务端进行数据通信的
这里我把输入输出流用线程来处理,这样跟服务端就可以多次次通信了,
直到输入特定指令或者关闭客户端才终止通信

服务端:
public class SocketHandlerImpl implements Runnable {
MessageDaoImpl mdi = new MessageDaoImpl();
UsersDaoImpl udi = new UsersDaoImpl();
private Socket socket = null;
private DataInputStream dataInputStream = null;
private DataOutputStream dataOutputStream = null;

Scanner scanner = new Scanner(System.in);
public String name = "服务器";
Collection<SocketHandlerImpl> socketHandlers = SessionUtils.getUsers(); // 获取所以的socket对象

public SocketHandlerImpl(Socket socket) throws Exception {
	this.socket = socket;
	dataInputStream = new DataInputStream(socket.getInputStream());
	dataOutputStream = new DataOutputStream(socket.getOutputStream());
}

public void run() {

	new Thread(new Runnable() {

		public void run() {
			
			scanner.useDelimiter("\n");
			while (true) {
				try {
					System.out.print(name + ":");
					String str = scanner.next().trim();
					write(str);
					break;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}).start();
	while (true) {
		// 循环读取客户端发送的消息
		try {
			String data = dataInputStream.readUTF();
			handlerCMD(data);
		} catch (Exception e) {
			for (SocketHandlerImpl n : socketHandlers) {
				if (n != this) {
					n.write(name + ",下线了!"); // 当有用户下线时 通知所有人
				}
			}
			try {
				dataInputStream.close();
				dataOutputStream.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
			break;
		}
	}
}

private void handlerCMD(String cmd) throws Exception {
	if (cmd.indexOf("#") < 0) {
		System.out.println(name + ":收到消息:" + cmd);
		return;
	}
	int index = cmd.indexOf("#");
	String type = cmd.substring(0, index);
	String body = cmd.substring(index + 1, cmd.length());
	// 类型-消息体
	// 消息转发类型 ->1 消息体: who-what
	// 登录成功 ->2 消息体: name
	// 3#context 默认转发所有成员
	// 4#refname-url 指定人转发文件
	// 5
	switch (Integer.parseInt(type)) {
	case 1:
		forwardMsg(body);
	case 2:
		forwardMsgs(body);
		break;
	case 3:
		loginMsg(body);
		break;
	default:
		System.out.println("还未实现该消息类型");
		break;
	}
}

public void loginMsg(String body ){
	if (!body.contains("-")) {
		System.out.println("消息体不符合规范!");
		return;
	}
	String[] data = body.split("-");
	String name = data[0];
	String pwd = data[1];
	Users us = udi.getid(name);
	if(us!=null && us.getPwd().equals(pwd)){
		  this.name= name;
		  for (SocketHandlerImpl n : socketHandlers) {
				if (n != this) {
					n.write(name + ",上线了!");
				}
			}
	}else{
		write("登录失败,请输入正确的用户名和密码。");
	}
	 
}

// 发送消息
public void write(String str) {
	try {
		dataOutputStream.writeUTF(str);
		dataOutputStream.flush();
	} catch (Exception e) {
		try {
			dataOutputStream.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		e.printStackTrace();
		System.out.println("写入异常!");
	}
}

// 向特定的人转发消息
private void forwardMsg(String body) {
	if (!body.contains("-")) {
		System.out.println("消息体不符合规范!");
		return;
	}
	String[] data = body.split("-");
	String who = data[0];
	String what = data[1];
	SocketHandlerImpl whoImpl = SessionUtils.getUsers(who);
	if (whoImpl != null) {
		whoImpl.write(what);
	}
}

// 向所有人发送消息
private void forwardMsgs(String str) {
	if (str != "") {
		for (SocketHandlerImpl n : socketHandlers) {
			if (n != this) {
				n.write(str); // 默认向所有人转发
			}
		}
	}
}

}
public class Server {

public static void main(String[] args) throws Exception {
	final int index = 1;
	final ServerSocket serverSocket = new ServerSocket(8080);
	Collection<Object>  task= new ArrayList<Object>();
	final ExecutorService es = Executors.newFixedThreadPool(index); // 创建一个可重用固定线程数的线程池
	System.out.println("启动服务器8080端口服务!");
	int i  = 0 ;

	while (true) {
		Socket socket = null;
	
		  String name = String.valueOf(i);
		try {
			
				socket = serverSocket.accept();
				System.out.println(SessionUtils.userssize());
				if(SessionUtils.userssize()<index){
				System.out.println("连接成功" + socket.getPort());
				SocketHandlerImpl socketHandle = new SocketHandlerImpl(socket);
				es.execute(socketHandle);
				SessionUtils.save(name, socketHandle);
				System.out.println("在线人数:" + SessionUtils.userssize());
				Collection<SocketHandlerImpl> socketHandlers = SessionUtils.getUsers();
				for (SocketHandlerImpl n : socketHandlers) {
					if (n != socketHandle) {
						n.write(name + ",上线了!");
			}
				}
			}else{
			 DataOutputStream dos=new DataOutputStream( socket.getOutputStream());
			 dos.writeUTF("服务器正忙.....请重新连接。");
			 dos.flush();
			 System.out.println("连接超时");
		     socket.close();
			}
				
		} catch (Exception e1) {
			 es.shutdownNow();
		}
		i++;
		
	}

}

}

服务端需要创建一个serversock对象 来监听一个端口 来接受客户端的连接
服务端接收客户端的连接交给了线程池来做, 线程池是用来管理线程的生命周期的 比起多线程更方便。
服务端用accept()接受客户端的连接, 然后用map来存储socket信息 以用于客户端之间的通信
服务端和客户端一样 也有一对输入输出流来跟客户端通信 当客户端下线之后 服务端会通知其他客户端 然后在map中对下线的socket进行移除操作
这里运用了连接池技术,连接池有固定的线程来工作,每当新的客户端来连接时,服务端会判断当前的连接数是否已满,如果满了,
则通知该客户端提示 连接超时,然后结束当前的客户端 , 这是我采取的拒绝策略
类似于 在服务器爆满的情况下 你去连接服务器,会得到服务器连接超时…

总结:

  1. 基于http/tcp协议,应用程序通常通过 socket 向网络发出请求或者应答网络请求
    2.socket 开发分客户端与服务端
    3.服务端开启服务监听某一端口
    4.客户端向此服务器的这个端口发出请求,成功则会建立会话,形成通道.
    5.这个通道若不做其它操作会一直存在,就是所谓的长连接了,这时服务
    端与客户端可以通过此通道进行即时通信
    6.当服务端没接到一个客户端连接请求之后,都会把处理流交给线程来处理,然后等待下客户端的连接

线程池:ThreadPoolExecutor
ThreadPoolExecutor继承了AbstractExecutorService类 ,并且有四个构造方法分别是:

 int  size = 1;   核心线程数
 int maxsize = 2; 最大连接数
 long time = 10;  空闲时 失效时间

ArrayBlockingQueue abq = new ArrayBlockingQueue(100); // 任务队列
ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 默认的
 RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); //用于执行新的线程任务
ThreadPoolExecutor tpe1 = new ThreadPoolExecutor(size,maxsize,time,TimeUnit.SECONDS,abq);
ThreadPoolExecutor tpe2= new ThreadPoolExecutor(size,maxsize,time, TimeUnit.SECONDS,abq, threadFactory);
ThreadPoolExecutor tpe3= new ThreadPoolExecutor(size,maxsize,time, TimeUnit.SECONDS, abq, threadFactory, handler);
ThreadPoolExecutor tpe4= new ThreadPoolExecutor(size, maxsize,time, TimeUnit.SECONDS, abq, handler);

构造方法前四个参数都是相同的,只有后面的参数不同,你可以根据相应的场景选用合适的连接池

ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 默认的
第一种是默认的 也是最简单的连接池

size 是核心线程数,当线程池创建之后,内部是没有线程的,都是在等待有任务之后才创建线程执行,

默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中

maxsize 是最大连接数,他表示线程池最多能创建多少个线程

Time 当线程空闲时的存活时间

TimeUnit 是指当线程空闲时的存活时间的单位

abq 是任务队列 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务

threadFactory 执行程序创建新线程时使用的工厂。

handler 当线程池 任务队列也满了的情况下,处理该任务,

线程池还有许多方法
public void execute(Runnable command)
可以在新线程中或者在现有池线程中执行该任务 它需要一个runnable 对象,
如果该任务已达到最大连接数 或者程序关闭都会交给RejectedExecutionHandler 处理

public static void main(String[] args) throws Exception {
ArrayBlockingQueue abq = new ArrayBlockingQueue(8); // 任务队列
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); //用于执行新的线程
ThreadPoolExecutor tpe4= new ThreadPoolExecutor(1, 1,10, TimeUnit.SECONDS, abq,new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// TODO Auto-generated method stub
System.out.println(“错误”);
}
} );

 Runnable r = new Runnable() {
	
	public void run() {
		// TODO Auto-generated method stub
		try {
			System.out.println("++");
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
	}
};
for(int i=0; i<10; i++){
	
	Thread.sleep(1000);
	tpe4.execute(r);
	
}

上面用的是带RejectedExecutionHandler 的线程池
首先创建一个核心线程数为1 最大连接为1 任务队列为8的连接池
当程序执行时
++
++
++
++
++
++
++
++
错误
当线程超过最大连接数和队列容量时 它就会交给rejectedExecution来处理
rejectedExecution是一线程池对连接满了之后对新的任务的一种处理方式

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值