读懂Java中的Socket编程

餐前甜点

Unix的输入输出(IO)系统遵循Open-Read-Write-Close这样的操做范本。当一个用户进程进行IO操做以前,它须要调用Open来指定并获取待操做文件或设备读取或写入的权限。一旦IO操做对象被打开,那么这个用户进程能够对这个对象进行一次或屡次的读取或写入操做。Read操做用来从IO操做对象读取数据,并将数据传递给用户进程。Write操做用来将用户进程中的数据传递(写入)到IO操做对象。 当全部的Read和Write操做结束以后,用户进程须要调用Close来通知系统其完成对IO对象的使用。java

在Unix开始支持进程间通讯(InterProcess Communication,简称IPC)时,IPC的接口就设计得相似文件IO操做接口。在Unix中,一个进程会有一套能够进行读取写入的IO描述符。IO描述符能够是文件,设备或者是通讯通道(socket套接字)。一个文件描述符由三部分组成:建立(打开socket),读取写入数据(接受和发送到socket)还有销毁(关闭socket)。shell

在Unix系统中,类BSD版本的IPC接口是做为TCP和UDP协议之上的一层进行实现的。消息的目的地使用socket地址来表示。一个socket地址是由网络地址和端口号组成的通讯标识符。编程

进程间通讯操做须要一对儿socket。进程间通讯经过在一个进程中的一个socket与另外一个进程中得另外一个socket进行数据传输来完成。当一个消息执行发出后,这个消息在发送端的socket中处于排队状态,直到下层的网络协议将这些消息发送出去。当消息到达接收端的socket后,其也会处于排队状态,直到接收端的进程对这条消息进行了接收处理。服务器

TCP和UDP通讯

关于socket编程咱们有两种通讯协议能够进行选择。一种是数据报通讯,另外一种就是流通讯。网络

数据报通讯

数据报通讯协议,就是咱们常说的UDP(User Data Protocol 用户数据报协议)。UDP是一种无链接的协议,这就意味着咱们每次发送数据报时,须要同时发送本机的socket描述符和接收端的socket描述符。所以,咱们在每次通讯时都须要发送额外的数据。app

流通讯

流通讯协议,也叫作TCP(Transfer Control Protocol,传输控制协议)。和UDP不一样,TCP是一种基于链接的协议。在使用流通讯以前,咱们必须在通讯的一对儿socket之间创建链接。其中一个socket做为服务器进行监听链接请求。另外一个则做为客户端进行链接请求。一旦两个socket创建好了链接,他们能够单向或双向进行数据传输。less

读到这里,咱们多少有这样的疑问,咱们进行socket编程使用UDP仍是TCP呢。选择基于何种协议的socket编程取决于你的具体的客户端-服务器端程序的应用场景。下面咱们简单分析一下TCP和UDP协议的区别,或许能够帮助你更好地选择使用哪一种。curl

在UDP中,每次发送数据报时,须要附带上本机的socket描述符和接收端的socket描述符。而因为TCP是基于链接的协议,在通讯的socket对之间须要在通讯以前创建链接,所以会有创建链接这一耗时存在于TCP协议的socket编程。socket

在UDP中,数据报数据在大小上有64KB的限制。而TCP中也不存在这样的限制。一旦TCP通讯的socket对创建了链接,他们之间的通讯就相似IO流,全部的数据会按照接受时的顺序读取。ui

UDP是一种不可靠的协议,发送的数据报不必定会按照其发送顺序被接收端的socket接受。而后TCP是一种可靠的协议。接收端收到的包的顺序和包在发送端的顺序是一致的。

简而言之,TCP适合于诸如远程登陆(rlogin,telnet)和文件传输(FTP)这类的网络服务。由于这些须要传输的数据的大小不肯定。而UDP相比TCP更加简单轻量一些。UDP用来实现实时性较高或者丢包不重要的一些服务。在局域网中UDP的丢包率都相对比较低。

Java中的socket编程

下面的部分我将经过一些示例讲解一下如何使用socket编写客户端和服务器端的程序。

注意:在接下来的示例中,我将使用基于TCP/IP协议的socket编程,由于这个协议远远比UDP/IP使用的要普遍。而且全部的socket相关的类都位于java.net包下,因此在咱们进行socket编程时须要引入这个包。

客户端编写

开启Socket

若是在客户端,你须要写下以下的代码就能够打开一个socket。

String host = "127.0.0.1";
int port = 8919;
Socket client = new Socket(host, port);

上面代码中,host即客户端须要链接的机器,port就是服务器端用来监听请求的端口。在选择端口时,须要注意一点,就是0~1023这些端口都已经被系统预留了。这些端口为一些经常使用的服务所使用,好比邮件,FTP和HTTP。当你在编写服务器端的代码,选择端口时,请选择一个大于1023的端口。

写入数据

接下来就是写入请求数据,咱们从客户端的socket对象中获得OutputStream对象,而后写入数据后。很相似文件IO的处理代码。

public class ClientSocket {
  public static void main(String args[]) {
        String host = "127.0.0.1";
        int port = 8919;
        try {
          Socket client = new Socket(host, port);
          Writer writer = new OutputStreamWriter(client.getOutputStream());
          writer.write("Hello From Client");
          writer.flush();
          writer.close();
          client.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
    }
 
}

关闭IO对象

相似文件IO,在读写数据完成后,咱们须要对IO对象进行关闭,以确保资源的正确释放。

服务器端编写

打开服务器端的socket

int port = 8919;
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();

上面的代码建立了一个服务器端的socket,而后调用accept方法监听并获取客户端的请求socket。accept方法是一个阻塞方法,在服务器端与客户端之间创建联系以前会一直等待阻塞。

读取数据

经过上面获得的socket对象获取InputStream对象,而后安装文件IO同样读取数据便可。这里咱们将内容打印出来。

public class ServerClient {
  public static void main(String[] args) {
        int port = 8919;
        try {
            ServerSocket server = new ServerSocket(port);
                Socket socket = server.accept();
            Reader reader = new InputStreamReader(socket.getInputStream());
            char chars[] = new char[1024];
            int len;
            StringBuilder builder = new StringBuilder();
            while ((len=reader.read(chars)) != -1) {
               builder.append(new String(chars, 0, len));
            }
            System.out.println("Receive from client message=: " + builder);
            reader.close();
            socket.close();
            server.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
  }
}

关闭IO对象

仍是不能忘记的,最后须要正确地关闭IO对象,以确保资源的正确释放。

附注一个例子

这里咱们增长一个例子,使用socket实现一个回声服务器,就是服务器会将客户端发送过来的数据传回给客户端。代码很简单。

import java.io.*;
import java.net.*;
public class EchoServer {
    public static void main(String args[]) {
        // declaration section:
        // declare a server socket and a client socket for the server
        // declare an input and an output stream
        ServerSocket echoServer = null;
        String line;
        DataInputStream is;
        PrintStream os;
        Socket clientSocket = null;
        // Try to open a server socket on port 9999
        // Note that we can't choose a port less than 1023 if we are not
        // privileged users (root)
        try {
           echoServer = new ServerSocket(9999);
        }
        catch (IOException e) {
           System.out.println(e);
        }
        // Create a socket object from the ServerSocket to listen and accept 
        // connections.
        // Open input and output streams
        try {
               clientSocket = echoServer.accept();
               is = new DataInputStream(clientSocket.getInputStream());
               os = new PrintStream(clientSocket.getOutputStream());
               // As long as we receive data, echo that data back to the client.
               while (true) {
                 line = is.readLine();
                 os.println(line);
               }
        } catch (IOException e) {
               System.out.println(e);
            }
        }
}

编译运行上面的代码,进行以下请求,就能够看到客户端请求携带的数据的内容。

15:00 $ curl http://127.0.0.1:9999/?111
GET /?111 HTTP/1.1
User-Agent: curl/7.37.1
Host: 127.0.0.1:9999
Accept: */*

总结

进行客户端-服务器端编程仍是比较有趣的,同时在Java中进行socket编程要比其余语言(如C)要简单快速编写。

java.net这个包里面包含了不少强大灵活的类供开发者进行网络编程,在进行网络编程中,建议使用这个包下面的API。同时Sun.*这个包也包含了不少的网络编程相关的类,可是不建议使用这个包下面的API,由于这个包可能会改变,另外这个包不能保证在全部的平台都有包含。

相关文章
相关标签/搜索