服务器Socket概述与实例

绝大部分知识与实例来自O’REILLY的《Java网络编程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O’REILLY))。

ServerSocket简介

ServerSocket类包含了使用Java编写服务器所需的全部内容,其中包括创建新ServerSocket对象的构造函数、在指定端口监听连接的方法、配置各个服务器Socket选项的方法,以及一些常见的方法(如toString)。
在Java中,一个服务器的生命周期如下:

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

下面看一个简单的服务器,用于获取时间:

实例1:Daytime服务器

public static void createDaytimeServer(){
    try(ServerSocket server = new ServerSocket(13)){
        while(true){
            try(Socket connection = server.accept()){
                Writer out = new OutputStreamWriter(
                                connection.getOutputStream(),
                                "UTF-8");
                Date now = new Date();
                out.write(now.toString() + "\r\n");
                out.flush();
            }catch (IOException e) {

            }
        }
    } catch (IOException e) {
        System.err.println("服务器意外退出");
    }
}

启动服务器:

createDaytimeServer();

请求服务器提供服务:

try(Socket socket = new Socket("127.0.0.1", 13)){
    BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream(), "UTF-8"));
    System.out.println(in.readLine());
} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

输出:
Wed Sep 13 09:34:09 CST 2017

注意点有以下几个:

  1. 客户端Socket与服务器Socket使用完毕后都需要调用close()关闭连接,Java 7及之后的版本可以利用try-with-resources实现。
  2. 启动服务器的代码和请求服务器提供服务的代码要分开执行,否则可能得不到输出。
  3. 服务器的代码中有两类异常,一类是某个客户端连接出错时抛出的,一类是服务器出错时抛出的,必须分别处理。
  4. 在同一台机器上测试的话,可以为Socket传入本地回送地址127.0.0.1。

多线程服务器

前面的服务器一次只能处理一个请求,如果有一个很慢的客户端先请求到了服务,会使得后面的客户端阻塞很长时间。一个比较好的方法是为每个连接提供一个线程。

实例2:使用线程池的多线程Daytime服务器

public static void createPooledDaytimeServer(){
    ExecutorService pool = Executors.newFixedThreadPool(50);

    try(ServerSocket server = new ServerSocket(13)){
        while(true){
            try {
                Socket connection = server.accept();
                pool.execute(new DaytimeTask(connection));
            } catch (IOException e) {

            }
        }
    } catch (IOException e) {
        System.err.println("服务器意外退出");
    } 
}

private static class DaytimeTask implements Runnable{
    private Socket connection;

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

    @Override
    public void run(){
        try(Writer out = new OutputStreamWriter(
                    connection.getOutputStream(),
                    "UTF-8")){
            Date now = new Date();
            out.write(now.toString() + "\r\n");
            out.flush();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

使用方法和输出结果和实例1基本相同。
注意点有以下几个:

  1. 多线程的情况下,客户端Socket的关闭应当由处理这个连接的线程完成,因为服务器不知道应当什么时候关闭。

日志(暂缺)

ServerSocket的其他构造器

public ServerSocket() throws IOException

public ServerSocket(int port) throws IOException

public ServerSocket(int port, int backlog)

public ServerSocket(int port, int backlog, InetAddress bindAddr)

port指要绑定的端口;如果传入0,系统会自动选择一个可用的端口;
backlog指等待连接队列的最大长度;
bindAddr指一个特定的本地IP地址。默认情况下,如果一个主机有多个网络接口或IP地址,ServerSocket会在每个接口和IP地址上监听。如果设置了bindAddr,那么就只会监听这个地址上的入站连接。
无参构造器在创建对象时不会绑定端口,可以在之后使用bind()方法进行绑定。

性能优先级

利用setPerformancePreferences(int connectionTime,int latency,int bandwidth)可以设置服务器各项性能指标的优先级。例如:setPerformancePreferences(2,1,3)表示最大带宽是最重要的性能,最小延迟最不重要,连接时间居中。

HTTP服务器

构建一个HTTP服务器的关键在于实现HTTP协议,即根据HTTP协议读取客户端的请求报文,并构造出对应的响应报文。下面是一个简单的单文件服务器,它无论接收到什么请求都返回200 OK,并传输一个指定文件。

实例3:单文件HTTP服务器

public class SingleFileHTTPServer {
    private final byte[] content;
    private final byte[] header;
    private final int port = 80;
    private final String encoding = "UTF-8";

    public SingleFileHTTPServer(String data,String mimeType) {
        this(data.getBytes(), mimeType);
    }

    public SingleFileHTTPServer(byte[] data, String mimeType){
        this.content = data;
        //响应报文首部
        String header = "HTTP/1.0 200 OK \r\n"
                + "Server: OneFile 2.0\r\n"
                + "Content-length:" + this.content.length + "\r\n"
                + "Content-type:" + mimeType 
                + "; charset=" + encoding + "\r\n\r\n";
        this.header = header.getBytes(Charset.forName("US-ASCII"));
    }

    public void start(){
        ExecutorService pool = Executors.newFixedThreadPool(10);
        try(ServerSocket server = new ServerSocket(port)){
            while(true){
                try{
                    Socket connection = server.accept();
                    pool.execute(new HTTPHandler(connection));
                }catch (IOException e) {
                    e.printStackTrace();
                }catch (RuntimeException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class HTTPHandler implements Runnable{
        private final Socket connection;

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

        @Override
        public void run() {
            try{
                OutputStream out = 
                        new BufferedOutputStream(connection.getOutputStream());
                InputStream in =
                        new BufferedInputStream(connection.getInputStream());

                StringBuilder request = new StringBuilder(80);
                while(true){
                    int c = in.read();
                    if(c == '\r' || c == '\n' || c == -1){
                        break;
                    }
                    request.append((char)c);
                }
                System.out.println(request.toString());
                if(request.toString().indexOf("HTTP/") != -1){
                    out.write(header);
                }
                out.write(content);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //客户端测试用,会保存服务器发来的文件
    public static void test(){
        try {
            URL url = new URL("http://127.0.0.1");
            URLConnection connection = url.openConnection();
            InputStream in = new BufferedInputStream(connection.getInputStream());
            int total = Integer.parseInt(connection.getHeaderField("Content-length"));      
            File file = new File("C:\\Users\\qwer\\Desktop" 
            + File.separator + "123.pdf");

            try(FileOutputStream out = new FileOutputStream(file)){
                byte[] bytes = new byte[64 * 1024];
                int size;
                int len = 0;
                while(len < total){
                    size = in.read(bytes);
                    out.write(bytes, 0, size);
                    len += size;
                    System.out.println(len);
                }
                out.flush();
                System.out.println("finished");
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            String filepath = "C:\\Users\\qwer\\Desktop\\osu\\漫画数据库.pdf";
            Path path = Paths.get(filepath);
            byte[] data = Files.readAllBytes(path);

            //用于获取文件资源的MIME类型
            String contentType = URLConnection.getFileNameMap().getContentTypeFor(filepath);
            SingleFileHTTPServer server = new SingleFileHTTPServer(data, contentType);
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
//      test();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Socket编程是一种网络编程技术,它可以实现客户端与服务器之间的通信。在Socket编程中,客户端和服务器通过套接字(Socket)建立连接,然后通过Socket发送和接收数据。 客户端和服务器之间的通信可以分为两种方式:TCP和UDP。TCP是一种可靠的连接协议,它可以保证数据的可靠传输,但是速度较慢。UDP是一种不可靠的连接协议,它可以快速传输数据,但是数据的可靠性不如TCP。 在Socket编程中,客户端和服务器需要分别创建一个Socket对象,然后通过Socket对象建立连接。客户端通过Socket对象向服务器发送数据,服务器通过Socket对象接收数据,并且可以向客户端发送数据。 Socket编程需要掌握网络编程的基础知识,包括IP地址、端口号、协议等。同时,还需要了解Socket编程的相关API,如socket()、bind()、listen()、accept()、connect()、send()、recv()等函数。 Socket编程可以应用于各种网络应用程序,如Web服务器、邮件服务器、聊天程序等。它是网络编程中的重要技术之一,对于网络程序员来说,掌握Socket编程是必不可少的。 ### 回答2: Socket编程是一种在计算机网络中实现进程间通信的常用方式,可以用来实现客户端与服务器之间的通信。实现Socket通信的核心在于建立连接,通过连接实现数据的发送和接收。下面,我们将从以下几个方面来说明Socket编程实现客户端与服务器通信的过程: 一、建立连接 客户端与服务器之间的连接可以采用TCP或UDP协议来建立。当采用TCP协议时,需要先建立三次握手,确保连接的可靠性,这会消耗一定的时间。而采用UDP协议则不需要建立连接,可以直接发送数据包,但是由于UDP协议不具备可靠性,可能会导致数据丢失或失序。 二、数据发送 在建立连接之后,客户端和服务器之间可以进行数据的发送和接收。客户端在发送数据时,需要先创建一个套接字,调用send()函数发送数据。服务器端在接收数据时,则需要先创建一个套接字,调用recv()函数接收数据。在数据发送时,可以选择发送数据的大小,也可以选择分包发送。 三、数据接收 当客户端发送数据之后,服务器端会接收到数据。服务器端需要先创建一个套接字,然后监听并接收客户端发送的数据。在接收数据时,服务器端也可以选择一次性接收全部数据,也可以选择分批次接收数据。 通过以上步骤,就可以实现客户端与服务器之间的通信。在实际应用中,还需要考虑多线程和多进程的应用,以提高系统的并发性能。同时,还需要加入安全措施,防止黑客攻击。总之,Socket编程是网络编程的基础,掌握Socket编程的方法和技巧,可以更好地实现客户端与服务器之间的通信。 ### 回答3: Socket编程是一种基于网络通信的编程技术,其主要目的是实现客户端与服务器之间的通信。在Socket编程中,客户端和服务器之间通过Socket通信协议建立连接,进行数据传输和通信。 Socket编程实现客户端与服务器通信的过程,通常包括以下几个步骤: 1. 创建Socket对象:客户端和服务器都需要创建Socket对象来进行通信。客户端使用Socket对象来连接服务器服务器使用Socket对象来监听客户端请求并返回数据。 2. 连接服务器:客户端通过Socket对象连接服务器,可以指定服务器的IP地址和端口号。服务器通过监听客户端请求,接收客户端请求信息。 3. 发送消息:客户端创建输出流,向服务器发送消息。服务器通过读取客户端发送的消息,处理请求,并返回响应数据。 4. 接收消息:服务器创建输入流,读取客户端发送的数据并处理请求。服务器通过输出流向客户端返回响应数据。 5. 关闭Socket连接:当通信结束时,客户端和服务器均需关闭Socket连接。 Socket编程的实现方式多种多样,可以使用不同的编程语言和工具进行开发。例如,Java语言可以使用Java Socket API实现Socket编程,C++可以使用Winsock或Socket类库实现Socket编程,Python可以使用socket模块实现Socket编程等。 在实际应用中,Socket编程广泛应用于各种网络应用场景,如Web服务器、FTP服务器、邮件服务器等。通过Socket编程,可以实现高效、可靠的网络通信,为各种应用提供了强有力的支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值