Java网络编程
1、Socket编程
Socket(套接字):封装着如端口号,ip地址,计算机名等信息的类。通过Socket我们可以和远程计算机通信。
网络通信模型
C/S Client/Server
客户端通航运行在用户的计算机上,客户端是一个特有的程序,实现特有的功能,连接服务器进行通信,谁发起连接谁是用户。
服务器端通常是等待客户端连接,提供功能服务并与之通信。
B/S
固定了客户端和通信协议和C/S结构。
通信协议:计算机通信的实质就是相互收发字节。那么按照一定的格式收发字节就是通信协议。
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
/**
* 创建客户端Socket
* Socket客户端类
* 构造的时候就会根据给定的服务端ip地址和服务端的端口号尝试连接
*/
try {
System.out.println("开始连接");
Socket socket = new Socket("172.16.3.33", 8088);
System.out.println("与服务端连接成功!");
/**
* 通过socket可以获取一组与服务器通信的输入输出流
* 我们对其包装就可以方便进行读写信息了。
* 通过socket拿到的是两个低级流(字节流)
*/
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
/**
* 向服务器发送字符,将字符输出流转换成缓冲字符输出流
*/
PrintWriter pw = new PrintWriter(out);
pw.println("你好服务器!");
pw.flush();
/**
* 收取服务器发送的字符串,包装为缓冲字符输入流
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
//读取服务器发回的信息
String info = reader.readLine();
System.out.println("服务端:"+info);
} catch (IOException e) {
//e.printStackTrace();
}
/**
* 服务端
* 服务器端打开socket等待客户端的连接
* 服务器端的Socket名称是ServerSocket
* 创建ServerSocket需要指定服务端口号
*/
public static void main(String[] args) {
try {
System.out.println("dddd");
ServerSocket server = new ServerSocket(8050);
/**
* 监听端口
* 等待客户端连接
* 等待客户端连接的方法accept()
* 该方法是一个阻塞方法,知道有客户端连接上该方法才会返回,返回的就是当前客户端的套接字。
* 从中我们可以知道客户端的ip等信息。
*
* accept()方法可以重复调用
*/
System.out.println("启动完毕,等待连接");
Socket client = server.accept();
System.out.println("有一个客户端和我连接了");
/**
* 通过socket获取输入流读取客户端的信息
*/
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
System.out.println("客户端:"+reader.readLine());
//向客户端发信息
PrintWriter pw = new PrintWriter(out);
pw.println("你好客户端");
pw.flush();
} catch (IOException e) {
//e.printStackTrace();
}
}
2、多线程Socket
Server端多线程:
服务器端无限循环接受客户端的访问,每连接都能茶圣一对新的Socket的实例。
为每个客户端连接创建一个独立线程,处理客户请求。
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
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8088);
System.out.println("启动完毕,等待连接");
while (true) {
Socket client = server.accept();
if(client==null)continue;
System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
Handler handler = new Handler(client);//交给线程去处理
Thread t = new Thread(handler);
t.start();
}
} catch (IOException e) {
}
}
/**
* 与客户端通信线程
* 负责与某个特定的socket的客户端进行通信
* 每个线程实例负责一个客户端的通信
*/
private static class Handler implements Runnable {
/**
* 当前线程要通信的客户端socket
*/
private Socket client;
public Handler(Socket client) {
this.client = client;
}
public void run(){
try {
/** 通过socket获取客户端信息 */
InputStream in = this.client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
/** 死循环读取客户端信息 */
while (true) {
if (reader.readLine() == null)
return;
System.out.println("客户端"+ this.client.getInetAddress().getHostAddress() + ":" + reader.readLine());
}
} catch (Exception e) {
}
}
}
3、线程池
上面这种频繁的创建线程和销毁线程是非常消耗资源和性能的。
可以创建一些空的线程,将它们保存起来,当有任务需要并发执行时,我们取出一个空的线程来运行这个任务,当任务运行完毕后,在将线程收回,等待下次分配任务。
这样自始自终我们都只使用了开始创建的那些线程,并可以重复利用来节省性能开销。
JDK提供了线程池的管理器ExecutorService
通过Executors类的静态方法创建几个不同实现的线程池。
Executors.newCachedThreadPool();创建一个缓存线程池,当有任务的时候会检查线程池中是否有空线程,若有就使用它,若没有就创建新的线程。若长久没有使用的线程会自动回收。
Executors.newFixedThreadPool(int threads);创建一个可重用的,具有固定线程数的线程池。
Executors.newScheduledExecutor();创建只有一条线程的线程池,它可以在指定延迟后执行线程任务。
Executors.newSingleThreadExecutor();创建一个只有单线程的线程池,相当于Exceutors.newFixedThreadPool方法时传入参数1。里边维护着一个任务队列。
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
/**
* 创建一个线程池,具有50个
*/
ExecutorService threadPool =Executors.newFixedThreadPool(50);
try {
ServerSocket server = new ServerSocket(8088);
System.out.println("启动完毕,等待连接");
/** 将转发消息线程启动 */
SendMessageHandler sHandler = new SendMessageHandler();
Thread sendTh = new Thread(sHandler);
sendTh.start();
while (true) {
Socket client = server.accept();
System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
Handler handler = new Handler(client);//交给线程去处理
/**
* 将需要并发的任务(Runnable)交给线程池
* 让其分配空线程去运行该任务
* 若线程都在工作,那么登载,直到有空线程为止。
*/
threadPool.execute(handler);
//Thread t = new Thread(handler);
//t.start();
}
} catch (IOException e) {
}
4、双端队列
内部由两个队列实现,他们交替进行存取工作。这样至少可以保证2个线程同时进行存取操作。但是对于两个线程同时进行存或者取,还是要求同步的。
但是比单队列实现线程安全还是要快的。
BlockingDeque双端队列
实现:
1)ArrayBlockingDeque该类实现的构造方法要求我们传入一个整数,代表当前队列的长度。所以这个是一个固定大小的双端队列,存取原则为FIFO先入先出的原则。
当元素调用offer存入了队列时,若队列已满,那么可以设置延时等待,当超过了延时等待会返回存入失败。
2)LinkedBlockingDeque变长双端队列。长度不定,随着元素数量而增加,最大可以达到Integer.MAX_VALUE,重载构造方法可以传入一个整数,使之变为一个定长的队列。
3)PriorityBlockingDeque 这个和LinkedBlockingDeque相似,只不过是进去了自然排序后获取。
4)SynchronousQueue特殊的双端队列 存取步骤有要求,必须存一次取一次,交替进行。
例:
服务端
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
/**
* 创建一个静态集合保护所有客户端的数输入流
* 注意:因为这个集合被多个线程使用,所哟一集合要是安全的。
*/
static Vector
allOut = new Vector();
/** 消息队列 */
static BlockingDeque msgQueue = new LinkedBlockingDeque();
public static void main(String[] args) {
/**
* 创建一个线程池,具有50个
*/
ExecutorService threadPool =Executors.newFixedThreadPool(50);
try {
ServerSocket server = new ServerSocket(8088);
System.out.println("启动完毕,等待连接");
/** 将转发消息线程启动 */
SendMessageHandler sHandler = new SendMessageHandler();
Thread sendTh = new Thread(sHandler);
sendTh.start();
while (true) {
Socket client = server.accept();
System.out.println("与客户端连接成功"+client.getInetAddress().getHostAddress());
Handler handler = new Handler(client);//交给线程去处理
/**
* 将需要并发的任务(Runnable)交给线程池
* 让其分配空线程去运行该任务
* 若线程都在工作,那么登载,直到有空线程为止。
*/
threadPool.execute(handler);
//Thread t = new Thread(handler);
//t.start();
}
} catch (IOException e) {
}
}
/**
* 与客户端通信线程
* 负责与某个特定的socket的客户端进行通信
* 每个线程实例负责一个客户端的通信
*/
private static class Handler implements Runnable {
/**
* 当前线程要通信的客户端socket
*/
private Socket client;
public Handler(Socket client) {
this.client = client;
}
public void run(){
PrintWriter pw = null;
try {
/** 通过socket获取客户端信息 */
InputStream in = this.client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
/**
* 将当前客户端的输出流保存到共享集合
*/
pw = new PrintWriter(client.getOutputStream());
allOut.add(pw);
/** 死循环读取客户端信息 */
while (true) {
String str = reader.readLine();
/**
* 将信息放入到消息队列
*/
if("q".equals(str)){
client.close();
}
msgQueue.offer(str);
/**
* arg1:存放的元素
* arg2:延时时间
* arg3:延时的时间单位
*
* 下面方法是向队列中存放元素,设置5秒超时,若超时了还没有放入队列这返回false
*/
boolean tf = msgQueue.offer(str, 5, TimeUnit.SECONDS);
}
} catch (Exception e) {
e.printStackTrace();
/**
* 抛出异常,我们应该将这个客户端的输出流从共享集合中删除
* 告诉其他线程不要再发信息了。
*/
allOut.remove(pw);
}
}
}
/**
* 转发消息
* 循环消息队列,将每一个消息通过所有客户端的输入流发送给客户端
* 做到广播的效果。
*/
public static class SendMessageHandler implements Runnable{
public void run() {
while (true) {
/** 循环将消息发送给每一个客户端 每50ms作一次*/
String str = null;
while ((str=msgQueue.poll())!=null) {//and msgQueue.poll()!=null
/** 获取所有客户端的输出流,将字符串输出 */
for (PrintWriter pw : allOut) {
pw.println(str);
pw.flush();
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
客户端:
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
public static void main(String[] args) {
/**
* 创建客户端Socket
* Socket客户端类
* 构造的时候就会根据给定的服务端ip地址和服务端的端口号尝试连接
*/
try {
System.out.println("开始连接");
Socket socket = new Socket("172.16.3.14", 8088);
System.out.println("与服务端连接成功!");
//启动消息线程
Thread readerMsgTh = new Thread(new ReadMessageHandler(socket.getInputStream()));
readerMsgTh.start();
OutputStream out = socket.getOutputStream();
PrintWriter pw = new PrintWriter(out);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
pw.println(reader.readLine());
pw.flush();
}
} catch (IOException e) {
//e.printStackTrace();
}
}
/**
* 该线程用于从服务器读取信息。
*/
public static class ReadMessageHandler implements Runnable{
private InputStream in;//从服务端读取信息
public ReadMessageHandler(InputStream in) {
this.in = in;
}
public void run() {
try {
//将字节输入流转换为缓冲字符输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while (true) {
System.out.println(reader.readLine());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html这里有一篇文章讲的比较细,把地址记录下来,供查询,感谢作者。