按部就班Socket网络编程(多客户端、信息共享、文件传输)java
前言:在最近一个即将结束的项目中使用到了Socket编程,用于调用另外一系统进行处理并返回数据。故把Socket的基础知识总结梳理一遍。编程
一、TCP/IP协议缓存
既然是网络编程,涉及几个系统之间的交互,那么首先要考虑的是如何准确的定位到网络上的一台或几台主机,另外一个是如何进行可靠高效的数据传输。这里就要使用到TCP/IP协议。服务器
TCP/IP协议(传输控制协议)由网络层的IP协议和传输层的TCP协议组成。IP层负责网络主机的定位,数据传输的路由,由IP地址能够惟一的肯定Internet上的一台主机。TCP层负责面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象。网络
二、TCP与UDPsocket
TCP是一种面向链接的保证可靠传输的协议。经过TCP协议传输,获得的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须创建链接,以便在TCP协议的基础上进行通讯,当一个socket(一般都是server socket)等待创建链接时,另外一个socket能够要求进行链接,一旦这两个socket链接起来,它们就能够进行双向数据传输,双方均可以进行发送或接收操做。ide
UDP是一种面向无链接的协议,每一个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,所以可否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。测试
TCP与UDP区别:大数据
TCP特色:this
UDP特色:
TCP与UDP应用:
三、Socket是什么
Socket一般也称做"套接字",用于描述IP地址和端口,是一个通讯链的句柄。网络上的两个程序经过一个双向的通信链接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口号惟一肯定。应用程序一般经过"套接字"向网络发出请求或者应答网络请求。 Socket是TCP/IP协议的一个十分流行的编程界面,可是,Socket所支持的协议种类也不光TCP/IP一种,所以二者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
Socket通信过程:服务端监听某个端口是否有链接请求,客户端向服务端发送链接请求,服务端收到链接请求向客户端发出接收消息,这样一个链接就创建起来了。客户端和服务端均可以相互发送消息与对方进行通信。
Socket的基本工做过程包含如下四个步骤:
四、Java中的Socket
在java.net包下有两个类:Socket和ServerSocket。ServerSocket用于服务器端,Socket是创建网络链接时使用的。在链接成功时,应用程序两端都会产生一个Socket实例,操做这个实例,完成所需的会话。对于一个网络链接来讲,套接字是平等的,并无差异,不由于在服务器端或在客户端而产生不一样级别。不论是Socket仍是ServerSocket它们的工做都是经过SocketImpl类及其子类完成的。
列出几个经常使用的构造方法:
1
2
3
4
5
6
7
8
9
|
Socket(InetAddress address,
int
port);
//建立一个流套接字并将其链接到指定 IP 地址的指定端口号
Socket(String host,
int
port);
//建立一个流套接字并将其链接到指定主机上的指定端口号
Socket(InetAddress address,
int
port, InetAddress localAddr,
int
localPort);
//建立一个套接字并将其链接到指定远程地址上的指定远程端口
Socket(String host,
int
port, InetAddress localAddr,
int
localPort);
//建立一个套接字并将其链接到指定远程主机上的指定远程端口
Socket(SocketImpl impl);
//使用用户指定的 SocketImpl 建立一个未链接 Socket
ServerSocket(
int
port);
//建立绑定到特定端口的服务器套接字
ServerSocket(
int
port,
int
backlog);
//利用指定的 backlog 建立服务器套接字并将其绑定到指定的本地端口号
ServerSocket(
int
port,
int
backlog, InetAddress bindAddr);
//使用指定的端口、侦听 backlog 和要绑定到的本地 IP地址建立服务器
|
构造方法的参数中,address、host和port分别是双向链接中另外一方的IP地址、主机名和端 口号,stream指明socket是流socket仍是数据报socket,localPort表示本地主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既能够用来建立serverSocket又能够用来建立Socket。count则表示服务端所能支持的最大链接数。
注意:必须当心选择端口号。每个端口提供一种特定的服务,只有给出正确的端口,才 能得到相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 因此咱们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
几个重要的Socke方法:
1
2
3
|
public
InputStream getInputStream();
//方法得到网络链接输入,同时返回一个IutputStream对象实例
public
OutputStream getOutputStream();
//方法链接的另外一端将获得输入,同时返回一个OutputStream对象实例
public
Socket accept();
//用于产生"阻塞",直到接受到一个链接,而且返回一个客户端的Socket对象实例。
|
"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,而后程序继续;一般"阻塞"是由循环产生的。
注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,由于它们返回的流对象,一般都会被另外一个流对象使用。
四、基本的Client/Server程序
如下是一个基本的客户端/服务器端程序代码。主要实现了服务器端一直监听某个端口,等待客户端链接请求。客户端根据IP地址和端口号链接服务器端,从键盘上输入一行信息,发送到服务器端,而后接收服务器端返回的信息,最后结束会话。这个程序一次只能接受一个客户链接。
ps:这个小例子写好后,服务端一直接收不到消息,调试了好长时间,才发现误使用了PrintWriter的print()方法,而BufferedReader的readLine()方法一直没有遇到换行,因此一直等待读取。我晕死~~@_@
客户端程序:
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
|
package
com.socket;
import
java.io.BufferedReader;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.Socket;
public
class
SocketClient {
public
static
void
main(String[] args) {
try
{
/** 建立Socket*/
// 建立一个流套接字并将其链接到指定 IP 地址的指定端口号(本处是本机)
Socket socket =
new
Socket(
"127.0.0.1"
,
2013
);
// 60s超时
socket.setSoTimeout(
60000
);
/** 发送客户端准备传输的信息 */
// 由Socket对象获得输出流,并构造PrintWriter对象
PrintWriter printWriter =
new
PrintWriter(socket.getOutputStream(),
true
);
// 将输入读入的字符串输出到Server
BufferedReader sysBuff =
new
BufferedReader(
new
InputStreamReader(System.in));
printWriter.println(sysBuff.readLine());
// 刷新输出流,使Server立刻收到该字符串
printWriter.flush();
/** 用于获取服务端传输来的信息 */
// 由Socket对象获得输入流,并构造相应的BufferedReader对象
BufferedReader bufferedReader =
new
BufferedReader(
new
InputStreamReader(socket.getInputStream()));
// 输入读入一字符串
String result = bufferedReader.readLine();
System.out.println(
"Server say : "
+ result);
/** 关闭Socket*/
printWriter.close();
bufferedReader.close();
socket.close();
}
catch
(Exception e) {
System.out.println(
"Exception:"
+ e);
}
}
}
|
服务器端程序:
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
|
package
com.socket;
import
java.io.BufferedReader;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.ServerSocket;
import
java.net.Socket;
public
class
SocketServer {
public
static
void
main(String[] args) {
try
{
/** 建立ServerSocket*/
// 建立一个ServerSocket在端口2013监听客户请求
ServerSocket serverSocket =
new
ServerSocket(
2013
);
while
(
true
) {
// 侦听并接受到此Socket的链接,请求到来则产生一个Socket对象,并继续执行
Socket socket = serverSocket.accept();
/** 获取客户端传来的信息 */
// 由Socket对象获得输入流,并构造相应的BufferedReader对象
BufferedReader bufferedReader =
new
BufferedReader(
new
InputStreamReader(socket.getInputStream()));
// 获取从客户端读入的字符串
String result = bufferedReader.readLine();
System.out.println(
"Client say : "
+ result);
/** 发送服务端准备传输的 */
// 由Socket对象获得输出流,并构造PrintWriter对象
PrintWriter printWriter =
new
PrintWriter(socket.getOutputStream());
printWriter.print(
"hello Client, I am Server!"
);
printWriter.flush();
/** 关闭Socket*/
printWriter.close();
bufferedReader.close();
socket.close();
}
}
catch
(Exception e) {
System.out.println(
"Exception:"
+ e);
}
finally
{
// serverSocket.close();
}
}
}
|
五、多客户端链接服务器
上面的服务器端程序一次只能链接一个客户端,这在实际应用中显然是不可能的。一般的网络环境是多个客户端链接到某个主机进行通信,因此咱们要对上面的程序进行改造。
设计思路:服务器端主程序监听某一个端口,客户端发起链接请求,服务器端主程序接收请求,同时构造一个线程类,用于接管会话。当一个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
|
package
com.socket;
import
java.io.BufferedReader;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.Socket;
public
class
SocketClient {
public
static
void
main(String[] args) {
try
{
Socket socket =
new
Socket(
"127.0.0.1"
,
2013
);
socket.setSoTimeout(
60000
);
PrintWriter printWriter =
new
PrintWriter(socket.getOutputStream(),
true
);
BufferedReader bufferedReader =
new
BufferedReader(
new
InputStreamReader(socket.getInputStream()));
String result =
""
;
while
(result.indexOf(
"bye"
) == -
1
){
BufferedReader sysBuff =
new
BufferedReader(
new
InputStreamReader(System.in));
printWriter.println(sysBuff.readLine());
printWriter.flush();
result = bufferedReader.readLine();
System.out.println(
"Server say : "
+ result);
}
printWriter.close();
bufferedReader.close();
socket.close();
}
catch
(Exception e) {
System.out.println(
"Exception:"
+ e);
}
}
}
|
服务器端程序:
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
|
package
com.socket;
import
java.io.*;
import
java.net.*;
public
class
Server
extends
ServerSocket {
private
static
final
int
SERVER_PORT =
2013
;
public
Server()
throws
IOException {
super
(SERVER_PORT);
try
{
while
(
true
) {
Socket socket = accept();
new
CreateServerThread(socket);
//当有请求时,启一个线程处理
}
}
catch
(IOException e) {
}
finally
{
close();
}
}
//线程类
class
CreateServerThread
extends
Thread {
private
Socket client;
private
BufferedReader bufferedReader;
private
PrintWriter printWriter;
public
CreateServerThread(Socket s)
throws
IOException {
client = s;
bufferedReader =
new
BufferedReader(
new
InputStreamReader(client.getInputStream()));
printWriter =
new
PrintWriter(client.getOutputStream(),
true
);
System.out.println(
"Client("
+ getName() +
") come in..."
);
start();
}
public
void
run() {
try
{
String line = bufferedReader.readLine();
while
(!line.equals(
"bye"
)) {
printWriter.println(
"continue, Client("
+ getName() +
")!"
);
line = bufferedReader.readLine();
System.out.println(
"Client("
+ getName() +
") say: "
+ line);
}
printWriter.println(
"bye, Client("
+ getName() +
")!"
);
System.out.println(
"Client("
+ getName() +
") exit!"
);
printWriter.close();
bufferedReader.close();
client.close();
}
catch
(IOException e) {
}
}
}
public
static
void
main(String[] args)
throws
IOException {
new
Server();
}
}
|
六、信息共享
以上虽然实现了多个客户端和服务器链接,可是仍然是消息在一个客户端和服务器之间相互传播。如今咱们要实现信息共享,即客户端也能够向其余客户端发送消息,服务器接收客户端的消息而后广播给其余客户端。相似于聊天室的那种功能,实现信息能在多个客户端之间共享。
设计思路:客户端循环能够不停输入向服务器发送消息,而且启一个线程,专门用来监听服务器端发来的消息并打印输出。服务器端启动时,启动一个监听什么时候须要向客户端发送消息的线程。每次接受客户端链接请求,都启一个线程进行处理,而且将客户端信息存放到公共集合中。当客户端发送消息时,服务器端将消息顺序存入队列中,当须要输出时,从队列中取出广播到各客户端处。
启动服务端和客户端程序(能够启多个客户端),客户端第一次输入任意内容回车与服务器创建链接,而后按提示输入登陆用户名,接着就能够以输入的用户名身份进行聊天。客户端输入showuser命令能够查看在线用户列表,输入bye向服务器端申请退出链接。
PS:如下代码在测试时发现了一个中文乱码小问题,当文件设置UTF-8编码时,不管怎样在代码中设置输入流编码都不起做用,输入中文仍然会乱码。把文件设置为GBK编码后,不用在代码中设置输入流编码都能正常显示传输中文。
客户端代码:
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
|
import
java.io.BufferedReader;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.Socket;
public
class
SocketClient
extends
Socket{
private
static
final
String SERVER_IP =
"127.0.0.1"
;
private
static
final
int
SERVER_PORT =
2013
;
private
Socket client;
private
PrintWriter out;
private
BufferedReader in;
/**
* 与服务器链接,并输入发送消息
*/
public
SocketClient()
throws
Exception{
super
(SERVER_IP, SERVER_PORT);
client =
this
;
out =
new
PrintWriter(
this
.getOutputStream(),
true
);
in =
new
BufferedReader(
new
InputStreamReader(
this
.getInputStream()));
new
readLineThread();
while
(
true
){
in =
new
BufferedReader(
new
InputStreamReader(System.in));
String input = in.readLine();
out.println(input);
}
}
/**
* 用于监听服务器端向客户端发送消息线程类
*/
class
readLineThread
extends
Thread{
private
BufferedReader buff;
public
readLineThread(){
try
{
buff =
new
BufferedReader(
new
InputStreamReader(client.getInputStream()));
start();
}
catch
(Exception e) {
}
}
@Override
public
void
run() {
try
{
while
(
true
){
String result = buff.readLine();
if
(
"byeClient"
.equals(result)){
//客户端申请退出,服务端返回确认退出
break
;
}
else
{
//输出服务端发送消息
System.out.println(result);
}
}
in.close();
out.close();
client.close();
}
catch
(Exception e) {
}
}
}
public
static
void
main(String[] args) {
try
{
new
SocketClient();
//启动客户端
}
catch
(Exception e) {
}
}
}
|
服务器端代码:
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
138
139
140
141
142
143
|
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.ServerSocket;
import
java.net.Socket;
import
java.util.ArrayList;
import
java.util.LinkedList;
import
java.util.List;
public
class
Server
extends
ServerSocket {
private
static
final
int
SERVER_PORT =
2013
;
private
static
boolean
isPrint =
false
;
// 是否输出消息标志
private
static
List user_list =
new
ArrayList();
// 登陆用户集合
private
static
List<ServerThread> thread_list =
new
ArrayList<ServerThread>();
// 服务器已启用线程集合
private
static
LinkedList message_list =
new
LinkedList();
// 存放消息队列
/**
* 建立服务端Socket,建立向客户端发送消息线程,监听客户端请求并处理
*/
public
Server()
throws
IOException {
super
(SERVER_PORT);
// 建立ServerSocket
new
PrintOutThread();
// 建立向客户端发送消息线程
try
{
while
(
true
) {
// 监听客户端请求,启个线程处理
Socket socket = accept();
new
ServerThread(socket);
}
}
catch
(Exception e) {
}
finally
{
close();
}
}
/**
* 监听是否有输出消息请求线程类,向客户端发送消息
*/
class
PrintOutThread
extends
Thread {
public
PrintOutThread() {
start();
}
@Override
public
void
run() {
while
(
true
) {
if
(isPrint) {
// 将缓存在队列中的消息按顺序发送到各客户端,并从队列中清除。
String message = (String) message_list.getFirst();
for
(ServerThread thread : thread_list) {
thread.sendMessage(message);
}
message_list.removeFirst();
isPrint = message_list.size() >
0
?
true
:
false
;
}
}
}
}
/**
* 服务器线程类
*/
class
ServerThread
extends
Thread {
private
Socket client;
private
PrintWriter out;
private
BufferedReader in;
private
String name;
public
ServerThread(Socket s)
throws
IOException {
client = s;
out =
new
PrintWriter(client.getOutputStream(),
true
);
in =
new
BufferedReader(
new
InputStreamReader(client.getInputStream()));
in.readLine();
out.println(
"成功连上聊天室,请输入你的名字:"
);
|