Java NIO Socket实现C/S架构

一般的Server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package thread.socket;  
import java.io.*;
import java.net.*;
import java.util.*;
public class Server extends ServerSocket {
private static final int SERVER_PORT = 10000;
private List<WorkerThread> workers;

public Server() throws IOException {
super(SERVER_PORT);
workers = new LinkedList<WorkerThread>();

try {
System.out.println("server is listening...");
while (true) {
Socket socket = accept();
workers.add(new WorkerThread(socket));
System.out.println(String.format("new worker created, total %d", getWorkerCount()));
}
} catch (IOException e) {
} finally {
close();
}
}
public synchronized int getWorkerCount() {
return workers.size();
}
public static void main(String[] args) throws IOException {
new Server();
}
}
// --- WorkerThread
class WorkerThread extends Thread {
private Socket client;
private BufferedReader in;
private PrintWriter out;
public WorkerThread(Socket s) throws IOException {
System.out.println(String.format("create a new thread. %s", s));
client = s;
in = new BufferedReader(new InputStreamReader(client
.getInputStream(), "GB2312"));
out = new PrintWriter(client.getOutputStream(), true);
out.println("--- Welcome ---" + client.getRemoteSocketAddress());
start();
}
public void run() {
try {
String line = in.readLine();
while (!line.equals("bye")) {
System.out.println("client " + client.getRemoteSocketAddress() + " says: " + line);
String msg = createMessage(line);
out.println(msg);
line = in.readLine();
}
out.println("bye");
System.out.println("client " + client.getRemoteSocketAddress() + " quit");
client.close();
} catch (IOException e) {
}
}
private String createMessage(String line) {
// ;
return "response to " + line;
}
}

使用NIO的Server端 (从1.5开始,Java对InputStream/OutputStream 进行了重新改写,用的就是NIO,因此,就算你不显示声明要用NIO,只要你的类继承了InputStream/OutputStream就已经在用NIO了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import java.io.BufferedWriter;  
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
public class SelectorServer
{
private static int DEFAULT_SERVERPORT = 6018;//默认端口
private static int DEFAULT_BUFFERSIZE = 1024;//默认缓冲区大小为1024字节
private static String DEFAULT_CHARSET = "GB2312";//默认码集
private static String DEFAULT_FILENAME = "bigfile.dat";
private ServerSocketChannel channel;
private LinkedList<SocketChannel> clients;
private Selector selector;//选择器
private ByteBuffer buffer;//字节缓冲区
private int port;
private Charset charset;//字符集
private CharsetDecoder decoder;//解码器


public SelectorServer(int port) throws IOException
{
this.port = port;
this.clients = new LinkedList<SocketChannel>();
this.channel = null;
this.selector = Selector.open();//打开选择器
this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
this.charset = Charset.forName(DEFAULT_CHARSET);
this.decoder = this.charset.newDecoder();

}

private class HandleClient
{
private String strGreeting = "welcome to VistaQQ";
public HandleClient() throws IOException
{
}
public String readBlock()
{//读块数据
return this.strGreeting;
}
public void close()
{

}
}
protected void handleKey(SelectionKey key) throws IOException
{//处理事件
if (key.isAcceptable())
{ // 接收请求
ServerSocketChannel server = (ServerSocketChannel) key.channel();//取出对应的服务器通道
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);//客户socket通道注册读操作
}
else if (key.isReadable())
{ // 读信息
SocketChannel channel = (SocketChannel) key.channel();
int count = channel.read(this.buffer);
if (count > 0)
{
this.buffer.flip();
CharBuffer charBuffer = decoder.decode(this.buffer);
System.out.println("Client >>" + charBuffer.toString());
SelectionKey wKey = channel.register(selector,
SelectionKey.OP_WRITE);//为客户sockt通道注册写操作
wKey.attach(new HandleClient());
}
else
{//客户已经断开
channel.close();
}
this.buffer.clear();//清空缓冲区
}
else if (key.isWritable())
{ // 写事件
SocketChannel channel = (SocketChannel) key.channel();
HandleClient handle = (HandleClient) key.attachment();//取出处理者
ByteBuffer block = ByteBuffer.wrap(handle.readBlock().getBytes());
channel.write(block);
// channel.socket().getInputStream().(block);
// PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
// channel.socket().getOutputStream())), true);
// out.write(block.toString());
}
}
public void listen() throws IOException
{ //服务器开始监听端口,提供服务
ServerSocket socket;
channel = ServerSocketChannel.open(); // 打开通道
socket = channel.socket(); //得到与通到相关的socket对象
socket.bind(new InetSocketAddress(port)); //将scoket榜定在制定的端口上
//配置通到使用非阻塞模式,在非阻塞模式下,可以编写多道程序同时避免使用复杂的多线程
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
try
{
while(true)
{// 与通常的程序不同,这里使用channel.accpet()接受客户端连接请求,而不是在socket对象上调用accept(),这里在调用accept()方法时如果通道配置为非阻塞模式,那么accept()方法立即返回null,并不阻塞
this.selector.select();
Iterator iter = this.selector.selectedKeys().iterator();
while(iter.hasNext())
{
SelectionKey key = (SelectionKey)iter.next();
iter.remove();
this.handleKey(key);

}
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
public static void main(String[] args) throws IOException
{
System.out.println("服务器启动");
SelectorServer server = new SelectorServer(SelectorServer.DEFAULT_SERVERPORT);
server.listen(); //服务器开始监听端口,提供服务
}
}

使用NIO的Client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package thread.socket;  
import java.io.*;
import java.net.*;
public class Client {
Socket socket;
BufferedReader in;
PrintWriter out;
public Client() {
try {
socket = new Socket("127.0.0.1", 10000);
in = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader line = new BufferedReader(new InputStreamReader(
System.in));
String cmd = "";
while (!cmd.equals("bye")) {
System.out.println("server says: " + in.readLine());
out.println(cmd = line.readLine());
}
System.out.println("socket " + socket + " stop");
line.close();
out.close();
in.close();
socket.close();
} catch (IOException e) {
}
}
public static void main(String[] args) {
new Client();
}
}

Do you like my piece of paper?

What I see every day is people creating pieces of paper to take into a room for people to look at and decide if they like that piece of paper, and if they don’t totally like it they say what they would prefer to see on that piece of paper.

This repeats until everybody is happy with a piece of paper.

capture3

Read More

TDD中变形动作的优先顺序 - Transformation Priority Premise

在TDD循环中,重构(Refactoring)是不改变行为而改变内部结构的动作,保持测试常绿。而变形(Transformation)是改变内部实现来使测试由红变绿。这些变形的变化使代码形式从特殊specific到一般generic。

Uncle Bob的一篇文章。http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html
中用了bowling game kata 和 Prime Factor kata来解释。

Read More

“设计”工作应该放在迭代中吗?

在教练敏捷团队的过程中,一些团队反映“某些用户故事不好拆分,无法在一个迭代内完成”。对于这些故事,一些团队的做法是先指定资深人员作为“设计师”,他从“需求分析师”或业务方拿到需求后,花上1个迭代的时间进行“设计”,然后由他将编码任务进一步分配给“开发人员”,并且负责验收代码。这些故事往往就会持续1~2个迭代才能完成,甚至还来不及测试。

Read More

所有代码都需要单元测试覆盖吗?

test-coverage

单元测试(unit testing)已经越来越得到广大开发者的认可。作为低成本、速度快、稳定度高的自动化测试手段,单元测试可以在类和函数级别对代码进行质量守护,有助于避免尴尬、耗时的错误。当然,相比功能测试(Functional testing)和端到端测试(end-to-end testing),单元测试能够寄予的产品级别的信心要略低一些,因而各个粒度的测试应该是相辅相成的,互为补充。

常常听到一些组织要求开发团队提高单元测试覆盖率,换来的却是怨声载道,或者是一堆应付差事的垃圾测试(没有断言的测试,都见过吧)。尽管,低测试覆盖率意味着对质量的信心不足,但是,单元测试覆盖率真的要达到100%才好吗?

Read More

精益软件度量

(题图感谢@杜伟忠)
该书由Thoughtworks的张松编著,试图回答敏捷转型中常被问到的”度量“问题。该问题常常隐藏着高级管理层的期待,但也包含敏捷怀疑者的抵触。作者从敏捷实践、团队成长、项目质量与价值等角度列出了一些手段,有一些大家都耳熟能详了,比如代码质量或故事点等。
然而,某些问题并不是那么容易回答的,或者只能用定性而非定量手段;或者反馈速度较慢。还有些问题,例如资源利用率(或人天成本等),作者恐怕也难以给出通用的答案。

此外,度量要耗费工作量,却并不产生价值;而且容易因为绩效考核的原因而走样,所以选择度量手段也需要斟酌。

Read More

听上去很美的O2O本地生活服务

近一两年,O2O(Online to Offline)的概念炒的火热,不管是BAT互联网大鳄,还是秉承精益创业的理想者,甚至传统产业的觉悟者,纷纷跳海。前几年惨烈的团购大战之后,去年的打车软件烧钱补贴还历历在目,誓要改变人们的生活行为。如今战火继续蔓延,保姆、洗车等领域也都卷入其中。

远镜投资的金戈先生,最近家里恰好有保姆钟点工的需求,于是他选用了一款叫做e家洁的app软件。结果他大呼慎用啊,因为这软件真是太“巧妙”了。
他发现,一开始用的时候阿姨几乎90%以上都是好评。定了服务之后:
1.你定的时间他们来不了,会和你重新约时间。
2.你定的人来不了,需要重新指派。
3.指派的人到时间完全不来,还联系不上。
4.投诉后重新派单,居然没到的这单显示完成,要求我评价。
5.该评价不能点差评,文字无法录入自动清空,选1星提交会自动返回4星好评。最终此单会成为默认好评。难怪大部分好评都是所谓的默认好评。

目前的本地生活服务o2o大多是从小时工、保洁等家政服务入手,进一步试图进军家电维保等领域。无论是e家洁、95081、云家政之类都是如此。尤其从小时工领域更易入手,以互联网+LBS的模式一方面为用户提供足够多的选择,另一方面通过评价体系尽可能的消除信息不对称,此外还依赖LBS尽量提高服务效率减少路程奔波所带来的时间浪费。
听上去很美,实际上很难。几大难点:
1.家政服务员本身管理困难,服务质量难于标准化评测和监督,与用户交互界面时间很长,需要培训的关键点太多。这就导致,如果采用纯平台的轻模式(类似95081),则服务质量保证难度较大。而如果采用员工均自聘的模式,则扩张受限,员工人数、用户规模、用工效率也易成为难以解决的矛盾,管理成本也会居高不下。
2.家政服务容易“短路”平台。用户当发现来的家政服务员很满意的时候,往往会选择直接联系服务员(尤其是遇到e家洁这种,很可能你约的服务员来不了会安排其他人的),而如果用户用的家政服务员不满意的时候,用户干脆就不会继续用这个app了。
3.利润微薄,作为中间商家政服务app目前几乎都是烧钱在做。在一个本身客单并不很高的行业,做平台商哪怕收取中介费用是否能支撑起一个足够有规模的品牌令人怀疑。这也是诸位竞争者不断进军新的领域的原因之一。当然,BAT或投资或合作插手其中,自有着大公司o2o战略的考量。
所以,金戈先生觉得本地生活服务o2o从家政入手未必是个好想法,虽然入手容易但想要做好有以上种种问题。还是找个高客单,低频次,易于标准化考量工作结果,用户对消除信息不对称和质量保障要求更高的事儿更靠谱。例如家庭维修安装,汽车保养维修等等。