2019独角兽企业重金招聘Python工程师标准>>>

前一章从客户端的角度讨论了Socket,客户端就是向监听连接的服务器打开一个Socket的程序,不过如果不能与服务器对话,客户端并没有什么用处。对于接受连接的服务器,Java提供了一个ServerSocket类表示服务器Socket,基本来说服务器Socket的任务就是坐在电话旁等电话。从技术上讲,服务器Socket在服务器上运行,监听入站TCP连接,每个服务器Socket监听服务器机器上的一个特定端口。

1、使用ServerSocket

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

  1. 使用一个ServerSocket()构造函数在一个特定的端口创建一个新的ServerSocket。
  2. ServerSocket使用其accept()方法监听这个端口的入站连接,accept()会一直阻塞,直到一个客户端尝试建立连接,此时accept()将返回一个连接客户端和服务器的Socket对象。
  3. 根据服务器的类型,会调用Socket的getInputStream()方法或getOutputStream()方法,获得与客户端通信的输入和输出流。
  4. 服务器和客户端根据已协商的协议交互, 直到要关闭连接。
  5. 服务器或客户端关闭连接。
  6. 服务器返回步骤2,等待下一次连接。

下面是一个daytime服务器的例子:

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;public class TestSocket {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(13)) {while (true) {try (Socket connection = serverSocket.accept()) {Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();connection.close();} catch (Exception e) {}}} catch (IOException e) {e.printStackTrace();}}}

这里的服务器发送最多几十个字节,然后关闭连接,非常快。但可能会有一个很慢或者崩溃的客户端使用服务器挂起几秒的时间,直到它注意到socket已经中断,如果发送数据花费大量时间,你肯定不希望每个连接都必须等待。这里的解决方案是为每个连接提供它自己的一个线程,与接收入站连接放入队列的那个线程分开,防止一个慢客户端阻塞所有其他客户端。这是一种“每个连接对应一个线程”的设计。

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;public class TestSocket {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(13)) {while (true) {try {Socket connection = serverSocket.accept();Thread task = new DaytimeThread(connection);task.start();} catch (IOException e) {}}} catch (IOException e) {e.printStackTrace();}}private static class DaytimeThread extends Thread {private Socket connection;public DaytimeThread(Socket connection) {this.connection = connection;}@Overridepublic void run() {try {Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {connection.close();} catch (IOException e) {}}}}}

不过,这个服务器上有可能发生一种拒绝服务攻击(denial-of-service),由于上述示例为每个连接生成一个新线程,大量几乎同时入站连接可能导致它生成极大数量的线程,最终Java虚拟机会耗尽内存而崩溃。一种更好的办法是使用一个固定的线程池来限制可能的资源使用,假设最多50个线程,它可能会拒绝连接,但起码不会崩溃。

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TestSocket {public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(50);try (ServerSocket serverSocket = new ServerSocket(13)) {while (true) {try {Socket connection = serverSocket.accept();Callable<Void> task = new DaytimeTask(connection);pool.submit(task);} catch (IOException e) {}}} catch (IOException e) {e.printStackTrace();}}private static class DaytimeTask implements Callable<Void> {private Socket connection;public DaytimeTask(Socket connection) {this.connection = connection;}@Overridepublic Void call() throws Exception {try {Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {connection.close();} catch (IOException e) {}}return null;}}}

在目前为止的所有例子中,服务器只写入客户端socket,而没有从客户端读取。不过大多数协议要求服务器同时读写客户端,下面将会给出相关例子。如果使用完一个服务器Socket,就应当将它关闭,这会释放端口,使其他希望使用这个端口的程序可以使用。

2、日志

服务器要在无人看管的情况下运行很长时间,通常需要在很久之后对服务器中发生的情况进行调试,这很重要。建议使用Logger logger = LoggerFactory.getLogger(“sampleName”)来打印日志,具体用法不在这里细述。

3、构造服务器Socket

服务器Socket的构造函数可以指定端口、保存入站连接请求所用的队列的长度,以及要绑定的本地网络接口。例如,在端口80创建服务器Socket可以写为ServerSocket server=new ServerSocket (80),队列一次最多可以保存50个入站连接,则写为ServerSocket server=new ServerSocket (80,50),如果试图将队列长度设置为大于操作系统的最大队列长度,则会使用最大队列长度。

如果一个主机有多个网络接口或IP地址,服务器Socket会在所有接口和IP地址的指定端口上监听。不过,还可以增加第3个参数,要求只绑定一个特定的本地IP地址。如:

InetAddress local=InetAddress.getByName("192.168.210.122");
ServerSocket server=new ServerSocket (80,50,local);

所有在这3个构造函数中,可以为端口号传入0,这样系统就会为你选择可用的端口,像这样由系统选择的端口有时称为匿名端口(anonymous port),这对于多socket协议(如FTP)很有用。

无参数构造函数会创建一个ServerSocket对象,但未将它具体绑定到某个端口,所以初始时它不能接受任何连接,之后可以用bind()来进行绑定。这个特性的主要用途是,允许程序在绑定端口之前设置服务器socket选项,有些选项在服务器socket绑定后必须固定。

4、获得服务器socket的有关信息

ServerSocket类提供了 两个获取方法,可以指出这个服务器Socket占用的本地地址和端口,如果你在一个匿名端口或一个未指定的网络接口上打开一个服务器socket,这会很有用。

5、Socket选项

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

  • SO_TIMEOUT:accept()在抛出java.io.InterruptedIOException异常前等待入站连接的时间,一般使用它的默认值0,表示永远不超时。
  • SO_REUSEADDR:跟客户端Socket的选项类似,表示是否允许一个新的Socket绑定到之前使用过的一个端口,而此时可能还有一些发送到原Socket的数据正在网络上传输。
  • SO_RCVBUF:设置服务器Socket接受的客户端Socket默认接收缓冲区大小。

不同的Internet服务类型有不同的性能需求,例如,体育运动的直播视频需要相对较高的带宽,另一方面,电影可能仍需要高带宽,但是可以接受较大的延迟,电子邮件可以通过低带宽的连接传递,甚至延迟几个小时也不会造成大的危害。为TCP定义了4个通用业务流类型:

  • 低成本
  • 高可靠性
  • 最大吞吐量
  • 最小延迟

对于服务器所接受的Socket,setPerformancePreferences()方法描述了为其连接时间、延迟和带宽给定的相对优先级。

public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

例如通过将connectionTime设置为2,latency设置为1,bandwidth设置为3,表示最大带宽是最重要的特性,最小延迟最不重要,连接时间居中。ss.setPerformancePreferences(2,1,3)。至于给定的VM究竟如何实现,则取决于具体的Socket实现,底层Socket实现不要求考虑这些需求。它们只是为TCP栈提供了所需策略的一个提示。

6、HTTP服务器

HTTP是个大协议,提供完备功能的HTTP服务器必须响应文件请求,将URL转换为本地系统的文件名,响应POST和GET请求,处理不存在的文件的请求,解释MIME类型等。大多数HTTP服务器都是受带宽和延迟限制,而不是CPU速度的限制,因此Java的其他优点,如半编译/半解释特性、动态类加载、垃圾回收和内存保护得到了发挥的机会,特别是有些网站通过servlet或其他机制大量使用了动态内容,会在Java的Web服务器上运行的更快,这是Java可与C竞争的一个领域。利用Java的网络类库,我们可以编写简单的定制服务器。下面给出一个例子:

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class JHTTP {private static final Logger logger = LoggerFactory.getLogger(JHTTP.class.getCanonicalName());private static final int NUM_THREADS=50;private static final String INDEX_FILE="index.html";private final File rootDirectory;private final int port;/*** @param rootDirectory* @param port* @throws IOException */public JHTTP(File rootDirectory, int port) throws IOException {if(!rootDirectory.isDirectory()){throw new IOException("根目录不是以一个目录的形式存在");}this.rootDirectory = rootDirectory;this.port = port;}public void start() throws IOException {ExecutorService pool=Executors.newFixedThreadPool(NUM_THREADS);try(ServerSocket server=new ServerSocket(port)){logger.info("端口:"+server.getLocalPort());logger.info("根目录:"+rootDirectory);while(true){try {Socket request=server.accept();Runnable runnable=new RequestProcessor(rootDirectory, INDEX_FILE, request);pool.submit(runnable);} catch (Exception e) {logger.error("接收连接异常",e);}}}}public static void main(String[] args) {File docroot;//得到文档根try {docroot=new File(args[0]);} catch (ArrayIndexOutOfBoundsException e) {System.out.println("使用java JHTTP docroot port");return;}int port;//设置要监听的端口try {port=Integer.parseInt(args[1]);if(port<0||port>65535){port=8080;}} catch (RuntimeException e) {port=8080;}try {JHTTP webserver=new JHTTP(docroot,port);webserver.start();} catch (Exception e) {logger.error("服务器启动异常",e);}}}

处理HTTP请求的runnable类:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.Date;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class RequestProcessor implements Runnable {private static final Logger logger = LoggerFactory.getLogger(RequestProcessor.class.getCanonicalName());private File rootDirectory;private String indexFileName = "index.html";private Socket connection;public RequestProcessor(File rootDirectory, String indexFileName, Socket connection) {if (rootDirectory.isFile()) {throw new IllegalArgumentException("根目录必须是目录,不是文件");}try {rootDirectory = rootDirectory.getCanonicalFile();} catch (Exception e) {}this.rootDirectory = rootDirectory;if (indexFileName != null) {this.indexFileName = indexFileName;}this.connection = connection;}@Overridepublic void run() {// 安全检查String root = rootDirectory.getPath();try {OutputStream raw = new BufferedOutputStream(connection.getOutputStream());Writer out = new OutputStreamWriter(raw);Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()));StringBuilder requestLine = new StringBuilder();while (true) {int c = in.read();if (c == '\r' || c == '\n') {break;}requestLine.append((char) c);}String get = requestLine.toString();logger.info(connection.getRemoteSocketAddress() + " " + get);String[] tokens = get.split("\\s+");String method = tokens[0];String version = "";if (method.equals("GET")) {String fileName = tokens[1];if (fileName.endsWith("/")) {fileName += indexFileName;}String contentType = URLConnection.getFileNameMap().getContentTypeFor(fileName);if (tokens.length > 2) {version = tokens[2];}File theFile = new File(rootDirectory, fileName.substring(1, fileName.length()));// 不要让客户端超出文档根之外if (theFile.canRead() && theFile.getCanonicalPath().startsWith(root)) {byte[] theData = Files.readAllBytes(theFile.toPath());if (version.startsWith("HTTP/")) {// 发送一个MIME首部sendHeader(out, "HTTP/1.0 200 OK", contentType, theData.length);}raw.write(theData);raw.flush();} else {// 无法找到文件String body = new StringBuilder("<HTML>\r\n").append("<HEAD><TITLE>File Not Found</TITLE>\r\n").append("</HEAD>\r\n").append("<BODY>").append("<H1>HTTP Error 404:File Not Found</H1>\r\n").append("</BODY></HTML>\r\n").toString();if (version.startsWith("HTTP/")) {// 发送一个MIME首部sendHeader(out, "HTTP/1.0 404 File Not Found", "text/html;charset=utf-8", body.length());}out.write(body);out.flush();}} else {// 不是get方法String body = new StringBuilder("<HTML>\r\n").append("<HEAD><TITLE>Not Implemented</TITLE>\r\n").append("</HEAD>\r\n").append("<BODY>").append("<H1>HTTP Error 501:Not Implemented</H1>\r\n").append("</BODY></HTML>\r\n").toString();if (version.startsWith("HTTP/")) {// 发送一个MIME首部sendHeader(out, "HTTP/1.0 501 Not Implemented", "text/html;charset=utf-8", body.length());}out.write(body);out.flush();}} catch (Exception e) {logger.error("Error talking to " + connection.getRemoteSocketAddress(), e);} finally {try {connection.close();} catch (IOException ex) {}}}private void sendHeader(Writer out, String responseCode, String contentType, int length) throws IOException {out.write(responseCode + "\r\n");Date now = new Date();out.write("Date:" + now + "\r\n");out.write("Server:JHTTP 2.0\r\n");out.write("Content-length:" + length + "\r\n");out.write("Content-type:" + contentType + "\r\n\r\n");out.flush();}}

这个服务器还可以添加以下的一些特性:

  • 服务器管理界面
  • 支持Java Servlet API
  • 支持其他请求方法,如POST、PUT等
  • 支持多个文档根目录

如果真的希望使用JHTTP来运行一个高流量的网站,还要做很多事情来优化,比如实现智能缓存,使用非阻塞I/O和通道来代替线程和流。

转载于:https://my.oschina.net/zhaoyi1/blog/888213

Java网络编程——9.服务器Socket相关推荐

  1. Java网络编程二:Socket详解

    Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点. 一.使用Socket进行网络通信的过程 服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接 ...

  2. 【Java 网络编程】客户端 Socket 创建

    文章目录 I TCP 无代理或无参构造函数 II 创建 Socket 时指定代理 III 创建 Socket 时直接指定服务器地址和端口号 IV 创建 Socket 时直接指定服务器地址和端口号 并绑 ...

  3. 【Java 网络编程】客户端 Socket 配置 ( 超时时间 | 端口复用 | Nagle 算法 | 心跳包机制 | 连接关闭机制 | 缓冲区大小 | 性能权重设置 | 紧急数据设置 )

    文章目录 I 设置读取超时时间 II Socket 复用绑定端口设置 III 开启 Nagle 算法 ( 沾包 ) IV 心跳包机制 V 连接关闭处理 VI Socket 紧急数据内敛设置 VII S ...

  4. 菜猪的JAVA 网络编程学习之Socket用法详解(上)

    在客户/服务器通信模式中,客户端需要主动创建与服务器连接的Socket(套接字),服务器端收到了客户端的连接请求,也会创建与客户连接的Socket.Socket可以看做是通信连接两端的收发器,服务器与 ...

  5. Java网络编程 Socket、ServerSocket 详解,方法介绍及完整代码示例

    Java网络编程 Socket.ServerSocket 详解,方法介绍及完整代码示例 概念 什么是网络编程? 网络编程是指编写运行在多个设备(计算机)的程序,这些设备通过网络连接起来.当这些通过网络 ...

  6. java网络编程,通过TCP,Socket实现多对一的局域网聊天室

    java网络编程,通过TCP,Socket实现多对一的局域网聊天室 可以实现多个客户端连接服务器,服务器接收到信息就会把信息广播到所有的客户端 这是服务器端的代码 View Code import j ...

  7. winsock使用java编写_利用Socket进行Java网络编程(一)

    利用Socket进行Java网络编程(一) 考试吧(Exam8.com) 2003-03-04 08:00:00 评论(0)条 作者: 郗旻 Socket是网络上运行的两个程序间双向通讯的一端,它既可 ...

  8. (Java)socket网络编程及处理socket粘包拆包问题

    目录 1.socket简介 2.TCP/IP协议 3.tcp三次握手 4.socket的一些接口函数原理 5.java socket 长连接粘包拆包问题 6.socket模拟服务端客户端发消息 7.U ...

  9. Java网络编程:socket与Netty

    Java网络编程:socket与Netty Java网络编程:socket与Netty TCP/IP介绍 简介 数据传输 TCP粘包和黏包 现象 为什么出现 如何解决 Socket介绍 介绍 功能开发 ...

最新文章

  1. 裁员屠刀下一个目标会是谁?
  2. 4.1 配置Flask-WTF
  3. 对话图灵奖得主John Hennessy,他说对美国留学签证变化很忧心
  4. ajax-典型应用-验证用户名
  5. JavaScript基础08-day10【函数的返回值、实参、立即执行函数、方法、全局作用域、函数作用域、提前声明、this、工厂方法创建对象、构造函数】
  6. SAAS平台的基本元素
  7. 2014年英语一作文partB漫画作文
  8. 花书+吴恩达深度学习(一)前馈神经网络(多层感知机 MLP)
  9. 区块链100篇之第三篇--数字签名
  10. 测试操作数据库mysql数据库吗_软件测试-MySQL(六:数据库函数)
  11. Python统计磁盘代码文件行数
  12. Python爬虫-爬取科比职业生涯高清图集
  13. 【前端】【HTML+CSS+JavaScript(JS)】简易工资计算器的实现
  14. origin2021绘图软件安装教程
  15. 同步异步数据采集卡参数选择
  16. 厦门龙凤419_福建生物工程职业技术学校2019招生通知书EMS单号
  17. Ubuntu16.04安装ftp配置
  18. 计算机原理与智能-翻译
  19. 模拟电路实验 01 - | 基本共射放大电路
  20. 【信号处理】python按原理实现BPSK、QPSK、QAM信号调制

热门文章

  1. Ubuntu 下添加OpenERP command 快捷启动方式
  2. 嵌入式开发从业人员生存状况
  3. BFC与垂直外边距折叠笔记
  4. 文件服务器:共享文件夹的移动和权限设置备份2012-04-28
  5. linux下如何安装rzsz
  6. Unable to start activity ComponentInfo
  7. C#算法大全-1-Hanoi
  8. Linux系统管理学习路线图
  9. HR最常用的Excel技巧,职场必备,值得收藏转发!
  10. python中的有序字典