第一次学习 Socket 编程

来源:互联网 发布:中国企业数据 编辑:程序博客网 时间:2024/05/22 03:02

服务器端:

import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.PrintWriter;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;public class Server {    // 绑定地址    private static String ADDRESS = "127.0.0.1";    // 监听端口号    private static final int PORT = 8080;    public static void main(String[] args) throws IOException {        // 创建 server 对象        Server server = new Server();        // 调用 service() 方法,等待请求        server.service();    }    public void service() {        // 初始化        ServerSocket server = null;        try {            // 创建 InetAddress 对象简单的方法是调用其静态方法 getByName()            InetAddress adr = InetAddress.getByName(ADDRESS);            // 创建 ServerSocket 对象            // 参数说明:1.监听端口号 2.请求最大队列长度 3.绑定地址            server = new ServerSocket(PORT, 5, adr);            System.out.println("服务启动成功");        } catch (IOException e) {            System.out.println("服务启动失败");            e.printStackTrace();        }        // 循环等待请求        while (true) {            // 初始化参数            Socket socket = null;            try {                // accept 方法会从连接请求队列中取出一个连接请求,然后创建与客户端连接的 Socket 对象,并将它返回                // 如果队列中没有连接请求,accept() 方法就会一直等待,直到接收到了连接请求才返回                socket = server.accept();                // 打印请求地址和端口                System.out.println("连接来自:" + socket.getLocalSocketAddress());                // 解析客户端发来的请求                String request = parse(socket.getInputStream());                // 对请求进行处理                String response = process(request);                // 将请求结果发送给客户端                send(socket.getOutputStream(), response);                // 关闭当前的 socket                socket.close();            } catch (Exception e) {                e.printStackTrace();                // 出错时重新开始新的循环                continue;            }        }    }    // 返回值可以认为是一个 Request 的对象    protected String parse(InputStream input) {        int len;        String charset = "UTF-8";        byte[] buffer = new byte[2048];        try {            // 一次读取全部的请求信息(因为请求一般不会太长)            len = input.read(buffer);            // 解析处理(暂无)            // TODO            // 解析结果返回            return new String(buffer, 0, len, charset);        } catch (IOException e) {            e.printStackTrace();            return "解析请求时发生异常!";        }    }    // 对请求进行处理,并返回处理结果    protected String process(String request) {        // 这里只做了简单的处理        return "来自服务端的消息:\r\n" + request;    }    // 这里的第二个参数,可以理解为 Response 对象    protected void send(OutputStream output, String response) {        // 创建 PrintWriter 对象,第二个参数为 true,则 println、printf 或 format 方法将刷新输出缓冲区        PrintWriter out = new PrintWriter(output, true);        // 将 response 发送给客户端        out.println(response);    }}

客户端:

import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class Client {    public static void main(String[] args) {        // 连接到的主机地址        String host = "127.0.0.1";        // 连接的端口号        int port = 8080;        try {            // 创建连接服务器的 Socket 对象            Socket socket = new Socket(host, port);            // 要从连接的另一端接受字节流,需要调用 Socket 类的 getInputStream 方法获取 InputStream 对象,            // 之后可以将其作为参数创建需要的 BufferedReader 对象            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));            // 需要发送字节流时,需要调用 Socket 类的 getOutputStream 方法获取 OutputStream 对象,            // 之后可以将其用来创建需要的 PrintWriter 对象            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);            // 向服务器端发送请求            out.println("Hello World!");            // 接受服务器端发送回来的信息            while (true) {                // ready() 方法判断此数据流是否已准备好被读取,这里主要是判断缓冲区是否为空                if (in.ready()) {                    // 缓冲区不为空时,按行读取流中数据                    String line;                    while ((line = in.readLine()) != null) {                        System.out.println(line);                    }                    // 读取结束后跳出循环                    break;                }                // 缓冲区为空,则线程等待 50 毫秒(等待服务器发送数据)                Thread.sleep(50);            }            // 关闭 socket            socket.close();        } catch (Exception e) {            e.printStackTrace();        }    }}

知识点补充:

1. 构造 ServerSocket

        ServerSocket 构造方法有以下四种形式:

  • ServerSocket()throws IOException
  • ServerSocket(int port) throws IOException
  • ServerSocket(int port, int backlog) throws IOException
  • ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

        在以上构造方法中,参数 port 指定服务器要绑定的端口(监听端口),参数 backlog 指定客户端连接请求队列的长度,参数 bindAddr 指定服务端要绑定的 IP 地址。


1.1 绑定端口

       除了第一个不带参数的构造方法之外,其它构造方法都会使服务器和特定端口绑定,端口由参数 port 指定,当运行时无法绑定到某端口时,会抛出 IOException, 更确切的说是 BindException,它是 IOException 的子类。BindException 一般有以下几个原因造成:

        a)端口已经被其它服务器进程占用

        b)在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到 1~1023 端口

        如果把参数 port 设为 0,表示由操作系统分配任意一个可用的端口。由服务器分配的端口也称之为匿名端口。对于多数服务器,会使用明确的端口,因为客户程序需要事先知道服务器端口,才能方便的访问服务器。在某些场合才会使用到匿名端口,比如 FTP(文件传输)协议等。


1.2 设定客户连接请求队列的长度

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

        对于客户端进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户和服务器的连接建立成功,客户进程从 Socket 构造方法中正常返回。

        ServerSocket 构造方法的 backlog 参数用来显示设置连接请求队列的长度,它将覆盖系统设定的最大长度,但在以下情况,仍然会采用系统限定的队列的最大长度:

        a)backlog 参数的值大于系统限定的队列的最大长度

        b)backlog 参数的值小于或等于 0

        c)在 ServerSocket 构造函数中没有设置 backlog 参数


1.3 设定绑定的 IP 地址

        如果主机只有一个 IP 地址,那么默认情况下,服务器就会与该 IP 地址绑定。ServerSocket 4个构造方法中只有第四个显示指定服务器要绑定的 IP 地址,该构造方法适用于具有多个 IP 地址的主机。


1.4 默认构造方法的作用

        ServerSocket 有一个不带任何参数的默认构造方法,通过该构造方法创建的 ServerSocket 不和任何端口绑定,但是之后需要通过 bind() 方法与特定端口绑定。这么做的目的是,允许服务器在绑定到特定端口之前,先设置 ServerSocket 的一些选项。因为一点服务器与特定端口绑定,有些选项就不能在改变了。

        例如:

ServerSocket serverSocket=new ServerSocket();serverSocket.setReuseAddress(true); // 设置 ServerSocket 选项serverSocket.bind(new InetSocketAddress(8080)); // 与8080端口绑定
        如果把 2、3 句代码互换,那么设置 ServerSocket 选项就完全不起作用了。

        ServerSocket 有以下 3 个选项:

  • SO_TIMEOUT:表示等待客户连接的超时时间
  • SO_REUSEADDR:表示是否允许重用服务器所绑定的地址
  • SO_RCVBUF:表示接收数据的缓冲区的大小
原创粉丝点击