​ServerSocket​​ ​​ 使用ServerSocket​​​​ 处理服务端异常​​​​ 阻塞​​​​ 服务端队列​​​​ 构造但不绑定端口​​​​ 随机端口​​ ​​Socket选项​​​​ 服务器第一版​​​​ 服务器第二版(重定向服务器)​​ ServerSocket

Java提供了一个ServerSocket类表示服务器Socket,举例来说,服务器Socket的任务就是坐在电话旁等电话.从技术上讲,服务器Socket在服务器主机上运行,监听入站TCP连接.每个服务器Socket监听服务器主机上的一个特定端口.当远程主机上的一个客户端尝试连接这个端口是,服务器Socket就被唤醒,协商建立客户端和服务器之间的连接,并返回一个常规的Socket对象,表示两台主机之间的Socket.换句话说,服务器Socket等待连接,而客户端Socket发起连接.一旦ServerSocket建立了连接,服务器会使用一个常规的Socket对象向客户端发送数据和接受客户端的数据.数据总是通过常规的Socket传输.

使用ServerSocket

在Java中,服务器程序的基本生命周期如下:

1 使用一个ServerSocket()构造函数在一个特定端口创建一个新的ServerSocket.

2 ServerSocket使用其accept()方法监听这个端口的入站连接.accept()会一直阻塞,直到一个客户端尝试建立连接,此时accept()将返回一个连接客户端和服务器的Socket对象.

3 调用Socket的getInputStream()和getOutputStream()方法,获得与客户端通信的输入和输出流.

4 服务器和客户端根据已协商的协议进行交互,直到要关闭连接.

5 服务器或客户端(或二者)关闭连接.

6 服务器返回到步骤2,等待下一次连接.



public static void main(String[] args)throws Exception {
ServerSocket server = new ServerSocket(13);
while (true) {
Socket socket = server.accept();
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.append("success");
bw.newLine();
bw.flush();
socket.close();
}
}//服务端



public static void main(String[] args) throws Exception{
Socket socket = new Socket("127.0.0.1",13);
InputStream in = socket.getInputStream();
BufferedReader bw = new BufferedReader(new InputStreamReader(in));
String s = null;
while ((s=bw.readLine())!=null){
System.out.println("接收的数据"+s);
}
socket.close();
}//客户端


处理服务端异常

服务端异常处理时,代码会变得有些复杂.有两类异常,一类异常可能关闭服务器并记录一个错误信息,另一类异常只会关闭活动连接,区分这两类异常非常重要.



public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(13);
while (true) {
Socket socket = null;
try {
socket = server.accept();
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.append("success");
bw.newLine();
bw.flush();
socket.close();
} catch (Exception e) {
System.out.println("连接关闭");
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
} catch (Exception e) {
System.out.println("服务器关闭了");
} finally {
if (server != null) {
try {
server.close();
} catch (Exception e) {
}
}
}
}


阻塞



1 public static void main(String[] args) {
2 ServerSocket server = null;
3 try {
4 server = new ServerSocket(13);
5 while (true) {
6 Socket socket = null;
7 try {
8 socket = server.accept();
9 OutputStream os = socket.getOutputStream();
10 System.out.println("正在发送数据");
11 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
12 bw.append("success");
13 bw.newLine();
14 bw.flush();
15
16 socket.shutdownOutput();
17
18 System.out.println("正在接收数据");
19 InputStream in = socket.getInputStream();
20 BufferedReader br = new BufferedReader(new InputStreamReader(in));
21 System.out.println(br.readLine());
22
23 } catch (Exception e) {
24 System.out.println("连接关闭");
25 } finally {
26 if (socket != null) {
27 try {
28 socket.close();
29 } catch (Exception e) {
30 }
31 }
32 }
33 }
34 } catch (Exception e) {
35 System.out.println("服务器关闭了");
36 } finally {
37 if (server != null) {
38 try {
39 server.close();
40 } catch (Exception e) {
41 }
42 }
43 }
44 }//服务端



1 public static void main(String[] args) throws Exception{
2 Socket socket = new Socket("127.0.0.1",13);
3 InputStream in = socket.getInputStream();
4 System.out.println("正在接收数据");
5 BufferedReader br = new BufferedReader(new InputStreamReader(in));
6 String s = null;
7 while ((s=br.readLine())!=null){
8 System.out.println("接收的数据"+s);
9 }
10
11
12 System.out.println("正在发送数据");
13 OutputStream out = socket.getOutputStream();
14 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
15 bw.write("ddddddddd");
16 bw.flush();
17
18 socket.shutdownOutput();
19 }//客户端


把服务端第16行注释掉,服务端和服务端的打印结果为



//这是服务端打印
正在发送数据
正在接收数据



//这是客户端打印
正在接收数据
接收的数据success


这种结果并不完全反映在打印上,其实此时客户端一直阻塞着,并未结束.原因是服务端写入数据后没有关闭输出流,客户端还在等待读取数据.这种情况会把客户端与服务端的连接一直阻塞.

把客户端第18行注释掉,服务端和客户端的打印结果为



//这是服务端打印
正在发送数据
正在接收数据
连接关闭



//这是客户端打印
正在接收数据
接收的数据success
正在发送数据


出现这种结果的原因是,服务端写完数据后关闭了写入流,客户端从而读取完毕.接着向服务端写入数据,但写入完毕后并没有关闭写入流,程序直接结束.服务端还在等待读数据,但是客户端已经结束,所以服务端抛出了异常.

服务端队列

管理客户连接请求的任务是由操作系统来完成的. 操作系统把这些连接请求存储在一个先进先出的队列中. 许多操作系统限定了队列的最大长度, 一般为 50 . 当队列中的连接请求达到了队列的最大容量时, 服务器进程所在的主机会拒绝新的连接请求. 只有当服务器进程通过 ServerSocket 的 accept() 方法从队列中取出连接请求, 使队列腾出空位时, 队列才能继续加入新的连接请求.



public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(15,1);
while (true) {
Socket socket = null;
try {
socket = server.accept();
Thread.sleep(1000*10);
} catch (Exception e) {
System.out.println("连接关闭");
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
} catch (Exception e) {
System.out.println("服务器关闭了");
} finally {
if (server != null) {
try {
server.close();
} catch (Exception e) {
}
}
}
}// 服务端



public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 15);
}// 客户端


构造但不绑定端口



ServerSocket server = new ServerSocket();
SocketAddress address = new InetSocketAddress(15);
server.bind(address);


随机端口

传入端口号为0,操作系统会为你选择可用端口,在FTP协议中这是很常见的,客户端首先连接到已知的21端口,不过在传输文件时,服务器开始监听任何可用的端口,然后服务器会使用已经在端口21打开的命令连接告诉客户端应当连接到哪一个端口来得到数据。



public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(0);
System.out.println(server.getLocalPort());
while (true) {
Socket socket = server.accept();
}
}// 服务端


Socket选项

Socket选项制定了ServerSocket类所依赖的原生Socket如何发送和接收数据。对于服务器Socket,Java支持三个选项。

SO_TIMEOUT:SO_TIMEOUT是accept()在抛出java.io.InterruptedIOException异常前等待入站连接的时间,以毫秒为单位。如果SO_TIMEOUT为0,accept()就永远不会超时。这个默认值的作用就是永远不会超时。设置了该值并大于0则在指定的时间内没有客户端连接则抛出异常。



public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(15);
server.setSoTimeout(1000*3);
System.out.println(server.getSoTimeout());
while (true) {
Socket socket = server.accept();
}
}// 服务端


SO_REUSEADDR:服务端的SO_REUSEADDR与客户端的SO_REUSEADDR作用类似,它确定了是否允许一个新的Socket绑定到之前使用过的一个端口,而此时可能还有一些发送到原Socket的数据正在网络上传输。



public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(15);
server.setReuseAddress(true);
System.out.println(server.getReuseAddress());
while (true) {
Socket socket = server.accept();
}
}// 服务端


SO_RCVBUF:SO_RCVBUF服务器Socket接收客户端的缓冲区大小,设置一个服务器的SO_RCVBUF就像在accept()返回的各个Socket上调用setReceiveBufferSize()。如果你设置的缓冲区大于64KB则必须在绑定端口之前设置。



public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket();
server.setReceiveBufferSize(1024*1024);
System.out.println(server.getReceiveBufferSize());
server.bind(new InetSocketAddress(15));
while (true) {
Socket socket = server.accept();
}
}// 服务端


服务器第一版

以下代码片段为一个简单的HTTP服务器,无论接收到什么内容都返回一个固定的字符串,如果请求内容中包含HTTP则表示客户端可以解析HTTP相应,则返回固定的响应头+固定的字符串。注意,以下示例不能直接用浏览器访问!



package com.datang.bingxiang.demo;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleFileHTTPServer {
private final byte[] content;
private final byte[] header;
private final int port;
private final String encoding;

public SingleFileHTTPServer(String data, String encoding, String mimeType, int port) throws Exception {
this(data.getBytes(encoding), encoding, mimeType, port);
}

public SingleFileHTTPServer(byte[] data, String encoding, String mimeType, int port) {
this.content = data;
this.encoding = encoding;
this.port = port;
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.0 200 OK\r\n");
sb.append("Server: OneFile 2.0\r\n");
sb.append("Content-length: " + this.content.length + "\r\n");
sb.append("Content-type: " + mimeType + "; charset=" + encoding + "\r\n\r\n");
this.header = sb.toString().getBytes(Charset.forName("US-ASCII"));
}

public void start() throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(100);
ServerSocket server = new ServerSocket(this.port);
while (true) {
Socket connection = server.accept();
pool.submit(new HTTPHandler(connection));
}
}

private class HTTPHandler implements Runnable {

private final Socket connection;

HTTPHandler(Socket connection) {
this.connection = connection;
}

@Override
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder request = new StringBuilder(80);
String data = null;
while ((data=in.readLine())!=null) {
request.append(data);
}
connection.shutdownInput();
System.out.println("------"+request);

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
//如果是HTTP/1.0或以后的版本,则发送一个MIME首部
if (request.toString().indexOf("HTTP/") != -1) {
out.append(new String(header,encoding));
}

out.append(new String(content,encoding));
out.flush();
connection.shutdownOutput();

} catch (Exception e) {
e.printStackTrace();
}finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) throws Exception {
SingleFileHTTPServer server = new SingleFileHTTPServer("我是服务器", "UTF-8", "text/plain", 80);
server.start();
}
}//服务端



package com.datang.bingxiang.demo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 80);

OutputStream out = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
bw.append("HTTP/1.1");
bw.flush();
socket.shutdownOutput();

InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
socket.shutdownInput();

socket.close();
}// 客户端
}


服务器第二版(重定向服务器) 

以下DEMO支持GET请求和POST请求(请求体需要换行,看下图)在这里出现问题,不知道Tomcat服务器是如何处理的,无论是用字节流读取判断-1还是字符流读取判断null都无法正确的读取到末尾,所以只能根据不同的请求做不同的处理,如果是GET请求则在空行后直接停止循环,否则不会自己停止,如果是POST请求则需要拿到Content-Length请求头,然后从空行后开始计算Body的长度,注意你的Body一定要自动的加上空行,否则依然无法结束读取。就POST请求来说,我使用Tomcat服务器并不需要特意的指定结束行,这是一个严重的坑!

不过在浏览器访问该Demo是可以成功重定向的。



package com.datang.bingxiang.demo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class Redirector {
private final int port;
private final String newSite;

public Redirector(String newSite, int port) {
this.port = port;
this.newSite = newSite;
}

public void start() throws Exception {
ServerSocket server = new ServerSocket(port);
while (true) {
Socket s = server.accept();
Thread t = new RedirectThread(s);
t.start();
}
}

private class RedirectThread extends Thread {
private final Socket connection;

RedirectThread(Socket s) {
this.connection = s;
}

public void run() {
try {
StringBuilder request = new StringBuilder();
InputStream inputStream = connection.getInputStream();

//字符流读取方式
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));

String methodLine = in.readLine();
String method = "";
if (methodLine.startsWith("GET")) {
method = "GET";
} else if (methodLine.startsWith("POST")) {
method = "POST";
}

request.append(methodLine);
int contentLength = -1;
int bodyLnegth = 0;
String line = null;
boolean isBody = false;
while ((line = in.readLine()) != null) {
request.append(line + "\r\n");
System.out.println("!!!" + line+"--"+(line.equals("")));


if (line.startsWith("Content-Length")) {
contentLength = Integer.parseInt(line.split(":")[1].trim());
}

if (line.equals("")) {
if (method.equals("GET")) {
break;
} else {
isBody=true;
System.out.println("读到空行了");
}
}

if(isBody) {
bodyLnegth += line.length();
if(bodyLnegth==contentLength-2) {
break;
}
}
}

System.out.println("----------------------------------------------");


//字节流读取方式
// byte[] b = new byte[1024];
// int len = 0;
// while ((len = inputStream.read(b)) != -1) {
// String s = new String(b, 0, len);
// System.out.println(s);
// request.append(s);
// }

connection.shutdownInput();

BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(connection.getOutputStream(), "US-ASCII"));

// 如果是HTTP/1.0或以后版本,则发送一个Mime首部
if (request.toString().indexOf("HTTP") != -1) {
out.write("HTTP/1.1 302 FOUND\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: Redirector 1.1\r\n");
out.write("Location: " + newSite + "\r\n");
out.write("Content-type: text/html\r\n\r\n");
out.flush();
}

// 并不是所有浏览器都支持重定向,所有我们需要生成HTML指出文档转移到哪里
out.write("<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>\r\n");
out.write("<BODY><H1>Document moved</H1>\r\n");
out.write("The document " + " has moved to\r\n<A href=\"" + newSite + "\">" + newSite
+ "</A>.\r\n Please update your bookmarks<P>");
out.write("</BODY></HTML>\r\n");

out.flush();
connection.shutdownOutput();
} catch (Exception e) {
try {
connection.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}

public static void main(String[] args) throws Exception {
int thePort = 1111;
String theSite = "http://localhost:8888/test";
Redirector redirector = new Redirector(theSite, thePort);
redirector.start();
}
}// 服务端



@RequestMapping(value = "test")
public String g(HttpServletRequest request) throws IOException {
System.out.println(request.getRemotePort());
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
Enumeration<String> headers = request.getHeaders(key);
while (headers.hasMoreElements()) {
String value = headers.nextElement();
System.out.println(key + " " + value);
}

}
return "success1";
}// 服务端


 JAVA网络编程-服务器Socket_数据