标签:
一,网络编程中两个主要的问题
一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。
在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。
而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提 出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也 能及时得到服务。
二,两类传输协议:TCP;UDP
TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建 立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
比较:
UDP:1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
2,UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
3,UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:1,面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接
时间。
2,TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的
数据。
3,TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
应用:
1,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
2,UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
三,基于Socket的java网络编程
1,什么是Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
2,Socket通讯的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
(1) 创建Socket;
(2) 打开连接到Socket的输入/出流;
(3) 按照一定的协议对Socket进行读/写操作;
(4) 关闭Socket.(在实际应用中,并未使用到显示的close,虽然很多文章都推荐如此,不过在我的程序中,可能因为程序本身比较简单,要求不高,所以并未造成什么影响。)
3,创建Socket
创建Socket
java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
Socket(InetAddress address, int port);
Socket(InetAddress address, int port, boolean stream);
Socket(String host, int prot);
Socket(String host, int prot, boolean stream);
Socket(SocketImpl impl)
Socket(String host, int port, InetAddress localAddr, int localPort)
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
ServerSocket(int port);
ServerSocket(int port, int backlog);
ServerSocket(int port, int backlog, InetAddress bindAddr)
其中address、host和port分别是双向连接中另一方的IP地址、主机名和端 口号,stream指明socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和 bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可 以用来创建Socket。count则表示服务端所能支持的最大连接数。例如:学习视频网 http://www.xxspw.com
Socket client = new Socket("127.0.01.", 80);
ServerSocket server = new ServerSocket(80);
注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才 能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket是必须捕获或抛出例外。
4,简单的Client/Server程序
1. 客户端程序
import java.io.*;
import java.net.*;
public class TalkClient {
public static void main(String args[]) {
try{
Socket socket=new Socket("127.0.0.1",4700);
//向本机的4700端口发出客户请求
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由系统标准输入设备构造BufferedReader对象
PrintWriter os=new PrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//由Socket对象得到输入流,并构造相应的BufferedReader对象
String readline;
readline=sin.readLine(); //从系统标准输入读入一字符串
while(!readline.equals("bye")){
//若从标准输入读入的字符串为 "bye"则停止循环
os.println(readline);
//将从系统标准输入读入的字符串输出到Server
os.flush();
//刷新输出流,使Server马上收到该字符串
System.out.println("Client:"+readline);
//在系统标准输出上打印读入的字符串
System.out.println("Server:"+is.readLine());
//从Server读入一字符串,并打印到标准输出上
readline=sin.readLine(); //从系统标准输入读入一字符串
} //继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
}catch(Exception e) {
System.out.println("Error"+e); //出错,则打印出错信息
}
}
}
2. 服务器端程序
import java.io.*;
import java.net.*;
import java.applet.Applet;
public class TalkServer{
public static void main(String args[]) {
try{
ServerSocket server=null;
try{
server=new ServerSocket(4700);
//创建一个ServerSocket在端口4700监听客户请求
}catch(Exception e) {
System.out.println("can not listen to:"+e);
//出错,打印出错信息
}
Socket socket=null;
try{
socket=server.accept();
//使用accept()阻塞等待客户请求,有客户
//请求到来则产生一个Socket对象,并继续执行
}catch(Exception e) {
System.out.println("Error."+e);
//出错,打印出错信息
}
String line;
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//由Socket对象得到输入流,并构造相应的BufferedReader对象
PrintWriter os=newPrintWriter(socket.getOutputStream());
//由Socket对象得到输出流,并构造PrintWriter对象
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
//由系统标准输入设备构造BufferedReader对象
System.out.println("Client:"+is.readLine());
//在标准输出上打印从客户端读入的字符串
line=sin.readLine();
//从标准输入读入一字符串
while(!line.equals("bye")){
//如果该字符串为 "bye",则停止循环
os.println(line);
//向客户端输出该字符串
os.flush();
//刷新输出流,使Client马上收到该字符串
System.out.println("Server:"+line);
//在系统标准输出上打印读入的字符串
System.out.println("Client:"+is.readLine());
//从Client读入一字符串,并打印到标准输出上
line=sin.readLine();
//从系统标准输入读入一字符串
} //继续循环
os.close(); //关闭Socket输出流
is.close(); //关闭Socket输入流
socket.close(); //关闭Socket
server.close(); //关闭ServerSocket
}catch(Exception e){
System.out.println("Error:"+e);
//出错,打印出错信息
}
}
}
5,支持多客户的client/server程序
前面的Client/Server程序只能实现Server和一个客户的对话。在实际应用 中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,需要对上 面的程序进行改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响 应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。
来源:http://haohaoxuexi.iteye.com/blog/1979837
Java Socket编程
对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。这样就有两个Socket了,客户端和服务端各一个。
对于Socket之间的通信其实很简单,服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。下面来看一些服务端与客户端通信的例子:
1、客户端写服务端读
服务端代码
1 public class Server {
2
3 public static void main(String args[]) throws IOException {
4 //为了简单起见,所有的异常信息都往外抛
5 int port = 8899;
6 //定义一个ServerSocket监听在端口8899上
7 ServerSocket server = new ServerSocket(port);
8 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
9 Socket socket = server.accept();
10 //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
11 Reader reader = new InputStreamReader(socket.getInputStream());
12 char chars[] = new char[64];
13 int len;
14 StringBuilder sb = new StringBuilder();
15 while ((len=reader.read(chars)) != -1) {
16 sb.append(new String(chars, 0, len));
17 }
18 System.out.println("from client: " + sb);
19 reader.close();
20 socket.close();
21 server.close();
22 }
23
24 }
服务端从Socket的InputStream中读取数据的操作也是阻塞式的,如果从输入流中没有读取到数据程序会一直在那里不动,直到客户端往Socket的输出流中写入了数据,或关闭了Socket的输出流。当然,对于客户端的Socket也是同样如此。在操作完以后,整个程序结束前记得关闭对应的资源,即关闭对应的IO流和Socket。
客户端代码
25 public class Client {
26
27 public static void main(String args[]) throws Exception {
28 //为了简单起见,所有的异常都直接往外抛
29 String host = "127.0.0.1"; //要连接的服务端IP地址
30 int port = 8899; //要连接的服务端对应的监听端口
31 //与服务端建立连接
32 Socket client = new Socket(host, port);
33 //建立连接后就可以往服务端写数据了
34 Writer writer = new OutputStreamWriter(client.getOutputStream());
35 writer.write("Hello Server.");
36 writer.flush();//写完后要记得flush
37 writer.close();
38 client.close();
39 }
40
41 }
对于客户端往Socket的输出流里面写数据传递给服务端要注意一点,如果写操作之后程序不是对应着输出流的关闭,而是进行其他阻塞式的操作(比如从输入流里面读数据),记住要flush一下,只有这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待。在稍后讲到客户端和服务端同时读和写的时候会说到这个问题。
2、客户端和服务端同时读和写
前面已经说了Socket之间是双向通信的,它既可以接收数据,同时也可以发送数据。
服务端代码
42 public class Server {
43
44 public static void main(String args[]) throws IOException {
45 //为了简单起见,所有的异常信息都往外抛
46 int port = 8899;
47 //定义一个ServerSocket监听在端口8899上
48 ServerSocket server = new ServerSocket(port);
49 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
50 Socket socket = server.accept();
51 //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
52 Reader reader = new InputStreamReader(socket.getInputStream());
53 char chars[] = new char[64];
54 int len;
55 StringBuilder sb = new StringBuilder();
56 while ((len=reader.read(chars)) != -1) {
57 sb.append(new String(chars, 0, len));
58 }
59 System.out.println("from client: " + sb);
60 //读完后写一句
61 Writer writer = new OutputStreamWriter(socket.getOutputStream());
62 writer.write("Hello Client.");
63 writer.flush();
64 writer.close();
65 reader.close();
66 socket.close();
67 server.close();
68 }
69
70 }
在上述代码中首先我们从输入流中读取客户端发送过来的数据,接下来我们再往输出流里面写入数据给客户端,接下来关闭对应的资源文件。而实际上上述代码可能并不会按照我们预先设想的方式运行,因为从输入流中读取数据是一个阻塞式操作,在上述的while循环中当读到数据的时候就会执行循环体,否则就会阻塞,这样后面的写操作就永远都执行不了了。除非客户端对应的Socket关闭了阻塞才会停止,while循环也会跳出。针对这种可能永远无法执行下去的情况的解决方法是while循环需要在里面有条件的跳出来,纵观上述代码,在不断变化的也只有取到的长度len和读到的数据了,len已经是不能用的了,唯一能用的就是读到的数据了。针对这种情况,通常我们都会约定一个结束标记,当客户端发送过来的数据包含某个结束标记时就说明当前的数据已经发送完毕了,这个时候我们就可以进行循环的跳出了。那么改进后的代码会是这个样子:
71 public class Server {
72
73 public static void main(String args[]) throws IOException {
74 //为了简单起见,所有的异常信息都往外抛
75 int port = 8899;
76 //定义一个ServerSocket监听在端口8899上
77 ServerSocket server = new ServerSocket(port);
78 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
79 Socket socket = server.accept();
80 //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
81 Reader reader = new InputStreamReader(socket.getInputStream());
82 char chars[] = new char[64];
83 int len;
84 StringBuilder sb = new StringBuilder();
85 String temp;
86 int index;
87 while ((len=reader.read(chars)) != -1) {
88 temp = new String(chars, 0, len);
89 if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
90 sb.append(temp.substring(0, index));
91 break;
92 }
93 sb.append(temp);
94 }
95 System.out.println("from client: " + sb);
96 //读完后写一句
97 Writer writer = new OutputStreamWriter(socket.getOutputStream());
98 writer.write("Hello Client.");
99 writer.flush();
100 writer.close();
101 reader.close();
102 socket.close();
103 server.close();
104 }
105
106 }
在上述代码中,当服务端读取到客户端发送的结束标记,即“eof”时就会结束数据的接收,终止循环,这样后续的代码又可以继续进行了。
客户端代码
107 public class Client {
108
109 public static void main(String args[]) throws Exception {
110 //为了简单起见,所有的异常都直接往外抛
111 String host = "127.0.0.1"; //要连接的服务端IP地址
112 int port = 8899; //要连接的服务端对应的监听端口
113 //与服务端建立连接
114 Socket client = new Socket(host, port);
115 //建立连接后就可以往服务端写数据了
116 Writer writer = new OutputStreamWriter(client.getOutputStream());
117 writer.write("Hello Server.");
118 writer.flush();
119 //写完以后进行读操作
120 Reader reader = new InputStreamReader(client.getInputStream());
121 char chars[] = new char[64];
122 int len;
123 StringBuffer sb = new StringBuffer();
124 while ((len=reader.read(chars)) != -1) {
125 sb.append(new String(chars, 0, len));
126 }
127 System.out.println("from server: " + sb);
128 writer.close();
129 reader.close();
130 client.close();
131 }
132
133 }
在上述代码中我们先是给服务端发送了一段数据,之后读取服务端返回来的数据,跟之前的服务端一样在读的过程中有可能导致程序一直挂在那里,永远跳不出while循环。这段代码配合服务端的第一段代码就正好让我们分析服务端永远在那里接收数据,永远跳不出while循 环,也就没有之后的服务端返回数据给客户端,客户端也就不可能接收到服务端返回的数据。解决方法如服务端第二段代码所示,在客户端发送数据完毕后,往输出 流里面写入结束标记告诉服务端数据已经发送完毕了,同样服务端返回数据完毕后也发一个标记告诉客户端。那么修改后的客户端代码就应该是这个样子:Java代码
134 public class Client {
135
136 public static void main(String args[]) throws Exception {
137 //为了简单起见,所有的异常都直接往外抛
138 String host = "127.0.0.1"; //要连接的服务端IP地址
139 int port = 8899; //要连接的服务端对应的监听端口
140 //与服务端建立连接
141 Socket client = new Socket(host, port);
142 //建立连接后就可以往服务端写数据了
143 Writer writer = new OutputStreamWriter(client.getOutputStream());
144 writer.write("Hello Server.");
145 writer.write("eof");
146 writer.flush();
147 //写完以后进行读操作
148 Reader reader = new InputStreamReader(client.getInputStream());
149 char chars[] = new char[64];
150 int len;
151 StringBuffer sb = new StringBuffer();
152 String temp;
153 int index;
154 while ((len=reader.read(chars)) != -1) {
155 temp = new String(chars, 0, len);
156 if ((index = temp.indexOf("eof")) != -1) {
157 sb.append(temp.substring(0, index));
158 break;
159 }
160 sb.append(new String(chars, 0, len));
161 }
162 System.out.println("from server: " + sb);
163 writer.close();
164 reader.close();
165 client.close();
166 }
167
168 }
169
我们日常使用的比较多的都是这种客户端发送数据给服务端,服务端接收数据后再返回相应的结果给客户端这种形式。只是客户端和服务端之间不再是这种一对一的关系,而是下面要讲到的多个客户端对应同一个服务端的情况。 3、多个客户端连接同一个服务端
像前面讲的两个例子都是服务端接收一个客户端的请求之后就结束了,不能再接收其他客户端的请求了,这往往是不能满足我们的要求的。通常我们会这样做:
170 public class Server {
171
172 public static void main(String args[]) throws IOException {
173 //为了简单起见,所有的异常信息都往外抛
174 int port = 8899;
175 //定义一个ServerSocket监听在端口8899上
176 ServerSocket server = new ServerSocket(port);
177 while (true) {
178 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
179 Socket socket = server.accept();
180 //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
181 Reader reader = new InputStreamReader(socket.getInputStream());
182 char chars[] = new char[64];
183 int len;
184 StringBuilder sb = new StringBuilder();
185 String temp;
186 int index;
187 while ((len=reader.read(chars)) != -1) {
188 temp = new String(chars, 0, len);
189 if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
190 sb.append(temp.substring(0, index));
191 break;
192 }
193 sb.append(temp);
194 }
195 System.out.println("from client: " + sb);
196 //读完后写一句
197 Writer writer = new OutputStreamWriter(socket.getOutputStream());
198 writer.write("Hello Client.");
199 writer.flush();
200 writer.close();
201 reader.close();
202 socket.close();
203 }
204 }
205
206 }
在上面代码中我们用了一个死循环,在循环体里面ServerSocket调用其accept方法试图接收来自客户端的连接请求。当没有接收到请求的时候,程序会在这里阻塞直到接收到来自客户端的连接请求,之后会跟当前建立好连接的客户端进行通信,完了后会接着执行循环体再次尝试接收新的连接请求。这样我们的ServerSocket就能接收来自所有客户端的连接请求了,并且与它们进行通信了。这就实现了一个简单的一个服务端与多个客户端进行通信的模式。 上 述例子中虽然实现了一个服务端跟多个客户端进行通信,但是还存在一个问题。在上述例子中,我们的服务端处理客户端的连接请求是同步进行的,每次接收到来自 客户端的连接请求后,都要先跟当前的客户端通信完之后才能再处理下一个连接请求。这在并发比较多的情况下会严重影响程序的性能,为此,我们可以把它改为如 下这种异步处理与客户端通信的方式:
207 public class Server {
208
209 public static void main(String args[]) throws IOException {
210 //为了简单起见,所有的异常信息都往外抛
211 int port = 8899;
212 //定义一个ServerSocket监听在端口8899上
213 ServerSocket server = new ServerSocket(port);
214 while (true) {
215 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
216 Socket socket = server.accept();
217 //每接收到一个Socket就建立一个新的线程来处理它
218 new Thread(new Task(socket)).start();
219 }
220 }
221
222 /**
223 * 用来处理Socket请求的
224 */
225 static class Task implements Runnable {
226
227 private Socket socket;
228
229 public Task(Socket socket) {
230 this.socket = socket;
231 }
232
233 public void run() {
234
235 try {
236
237 handleSocket();
238 } catch (Exception e) {
239 e.printStackTrace();
240 }
241 }
242
243 /**
244 * 跟客户端Socket进行通信
245 * @throws Exception
246 */
247 private void handleSocket() throws Exception {
248 Reader reader = new InputStreamReader(socket.getInputStream());
249 char chars[] = new char[64];
250 int len;
251 StringBuilder sb = new StringBuilder();
252 String temp;
253 int index;
254 while ((len=reader.read(chars)) != -1) {
255 temp = new String(chars, 0, len);
256 if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
257 sb.append(temp.substring(0, index));
258 break;
259 }
260 sb.append(temp);
261 }
262 System.out.println("from client: " + sb);
263 //读完后写一句
264 Writer writer = new OutputStreamWriter(socket.getOutputStream());
265 writer.write("Hello Client.");
266 writer.flush();
267 writer.close();
268 reader.close();
269 socket.close();
270 }
271
272 }
273
274 }
在上面代码中,每次ServerSocket接收到一个新的Socket连接请求后都会新起一个线程来跟当前Socket进行通信,这样就达到了异步处理与客户端Socket进行通信的情况。 在从Socket的InputStream中接收数据时,像上面那样一点点的读就太复杂了,有时候我们就会换成使用BufferedReader来一次读一行,如:
275 public class Server {
276
277 public static void main(String args[]) throws IOException {
278 //为了简单起见,所有的异常信息都往外抛
279 int port = 8899;
280 //定义一个ServerSocket监听在端口8899上
281 ServerSocket server = new ServerSocket(port);
282 while (true) {
283 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
284 Socket socket = server.accept();
285 //每接收到一个Socket就建立一个新的线程来处理它
286 new Thread(new Task(socket)).start();
287 }
288 }
289
290 /**
291 * 用来处理Socket请求的
292 */
293 static class Task implements Runnable {
294
295 private Socket socket;
296
297 public Task(Socket socket) {
298 this.socket = socket;
299 }
300
301 public void run() {
302 try {
303 handleSocket();
304 } catch (Exception e) {
305 e.printStackTrace();
306 }
307 }
308
309 /**
310 * 跟客户端Socket进行通信
311 * @throws Exception
312 */
313 private void handleSocket() throws Exception {
314 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
315 StringBuilder sb = new StringBuilder();
316 String temp;
317 int index;
318 while ((temp=br.readLine()) != null) {
319 System.out.println(temp);
320 if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
321 sb.append(temp.substring(0, index));
322 break;
323 }
324 sb.append(temp);
325 }
326 System.out.println("from client: " + sb);
327 //读完后写一句
328 Writer writer = new OutputStreamWriter(socket.getOutputStream());
329 writer.write("Hello Client.");
330 writer.write("eof\n");
331 writer.flush();
332 writer.close();
333 br.close();
334 socket.close();
335 }
336 }
337 }
这个时候需要注意的是,BufferedReader的readLine方法是一次读一行的,这个方法是阻塞的,直到它读到了一行数据为止程序才会继续往下执行,那么readLine什么时候才会读到一行呢?直到程序遇到了换行符或者是对应流的结束符readLine方法才会认为读到了一行,才会结束其阻塞,让程序继续往下执行。所以我们在使用BufferedReader的readLine读取数据的时候一定要记得在对应的输出流里面一定要写入换行符(流结束之后会自动标记为结束,readLine可以识别),写入换行符之后一定记得如果输出流不是马上关闭的情况下记得flush一下,这样数据才会真正的从缓冲区里面写入。对应上面的代码我们的客户端程序应该这样写:Java代码
338 public class Client {
339
340 public static void main(String args[]) throws Exception {
341 //为了简单起见,所有的异常都直接往外抛
342 String host = "127.0.0.1"; //要连接的服务端IP地址
343 int port = 8899; //要连接的服务端对应的监听端口
344 //与服务端建立连接
345 Socket client = new Socket(host, port);
346 //建立连接后就可以往服务端写数据了
347 Writer writer = new OutputStreamWriter(client.getOutputStream());
348 writer.write("Hello Server.");
349 writer.write("eof\n");
350 writer.flush();
351 //写完以后进行读操作
352 BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
353 StringBuffer sb = new StringBuffer();
354 String temp;
355 int index;
356 while ((temp=br.readLine()) != null) {
357 if ((index = temp.indexOf("eof")) != -1) {
358 sb.append(temp.substring(0, index));
359 break;
360 }
361 sb.append(temp);
362 }
363 System.out.println("from server: " + sb);
364 writer.close();
365 br.close();
366 client.close();
367 }
368 }
4、设置超时时间
假设有这样一种需求,我们的客户端需要通过Socket从服务端获取到XX信息,然后给用户展示在页面上。我们知道Socket在读数据的时候是阻塞式的,如果没有读到数据程序会一直阻塞在那里。在同步请求的时候我们肯定是不能允许这样的情况发生的,这就需要我们在请求达到一定的时间后控制阻塞的中断,让程序得以继续运行。Socket为我们提供了一个setSoTimeout()方法来设置接收数据的超时时间,单位是毫秒。当设置的超时时间大于0,并且超过了这一时间Socket还没有接收到返回的数据的话,Socket就会抛出一个SocketTimeoutException。
假设我们需要控制我们的客户端在开始读取数据10秒后还没有读到数据就中断阻塞的话我们可以这样做:
369 public class Client {
370
371 public static void main(String args[]) throws Exception {
372 //为了简单起见,所有的异常都直接往外抛
373 String host = "127.0.0.1"; //要连接的服务端IP地址
374 int port = 8899; //要连接的服务端对应的监听端口
375 //与服务端建立连接
376 Socket client = new Socket(host, port);
377 //建立连接后就可以往服务端写数据了
378 Writer writer = new OutputStreamWriter(client.getOutputStream());
379 writer.write("Hello Server.");
380 writer.write("eof\n");
381 writer.flush();
382 //写完以后进行读操作
383 BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
384 //设置超时间为10秒
385 client.setSoTimeout(10*1000);
386 StringBuffer sb = new StringBuffer();
387 String temp;
388 int index;
389 try {
390 while ((temp=br.readLine()) != null) {
391 if ((index = temp.indexOf("eof")) != -1) {
392 sb.append(temp.substring(0, index));
393 break;
394 }
395 sb.append(temp);
396 }
397 } catch (SocketTimeoutException e) {
398 System.out.println("数据读取超时。");
399 }
400 System.out.println("from server: " + sb);
401 writer.close();
402 br.close();
403 client.close();
404 }
405 }
406
407
5、接收数据乱码
对于这种服务端或客户端接收中文乱码的情况通常是因为数据发送时使用的编码跟接收时候使用的编码不一致。比如有下面这样一段服务端代码:
408 public class Server {
409
410 public static void main(String args[]) throws IOException {
411 //为了简单起见,所有的异常信息都往外抛
412 int port = 8899;
413 //定义一个ServerSocket监听在端口8899上
414 ServerSocket server = new ServerSocket(port);
415 while (true) {
416 //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
417 Socket socket = server.accept();
418 //每接收到一个Socket就建立一个新的线程来处理它
419 new Thread(new Task(socket)).start();
420 }
421 }
422
423 /**
424 * 用来处理Socket请求的
425 */
426 static class Task implements Runnable {
427
428 private Socket socket;
429
430 public Task(Socket socket) {
431 this.socket = socket;
432 }
433
434 public void run() {
435 try {
436 handleSocket();
437 } catch (Exception e) {
438 e.printStackTrace();
439 }
440 }
441
442 /**
443 * 跟客户端Socket进行通信
444 * @throws Exception
445 */
446 private void handleSocket() throws Exception {
447 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
448 StringBuilder sb = new StringBuilder();
449 String temp;
450 int index;
451 while ((temp=br.readLine()) != null) {
452 System.out.println(temp);
453 if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
454 sb.append(temp.substring(0, index));
455 break;
456 }
457 sb.append(temp);
458 }
459 System.out.println("客户端: " + sb);
460 //读完后写一句
461 Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
462 writer.write("你好,客户端。");
463 writer.write("eof\n");
464 writer.flush();
465 writer.close();
466 br.close();
467 socket.close();
468 }
469 }
470 }
这里用来测试我就弄的混乱了一点。在上面服务端代码中我们在定义输入流的时候明确定义了使用GBK编码来读取数据,而在定义输出流的时候明确指定了将使用UTF-8编码来发送数据。如果客户端上送数据的时候不以GBK编码来发送的话服务端接收的数据就很有可能会乱码;同样如果客户端接收数据的时候不以服务端发送数据的编码,即UTF-8编码来接收数据的话也极有可能会出现数据乱码的情况。所以,对于上述服务端代码,为使我们的程序能够读取对方发送过来的数据,而不出现乱码情况,我们的客户端应该是这样的:Java代码
471 public class Client {
472
473 public static void main(String args[]) throws Exception {
474 //为了简单起见,所有的异常都直接往外抛
475 String host = "127.0.0.1"; //要连接的服务端IP地址
476 int port = 8899; //要连接的服务端对应的监听端口
477 //与服务端建立连接
478 Socket client = new Socket(host, port);
479 //建立连接后就可以往服务端写数据了
480 Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");
481 writer.write("你好,服务端。");
482 writer.write("eof\n");
483 writer.flush();
484 //写完以后进行读操作
485 BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
486 //设置超时间为10秒
487 client.setSoTimeout(10*1000);
488 StringBuffer sb = new StringBuffer();
489 String temp;
490 int index;
491 try {
492 while ((temp=br.readLine()) != null) {
493 if ((index = temp.indexOf("eof")) != -1) {
494 sb.append(temp.substring(0, index));
495 break;
496 }
497 sb.append(temp);
498 }
499 } catch (SocketTimeoutException e) {
500 System.out.println("数据读取超时。");
501 }
502 System.out.println("服务端: " + sb);
503 writer.close();
504 br.close();
505 client.close();
506 }
507 }
标签:
原文地址:http://blog.csdn.net/network_boy/article/details/44839779