您现在的位置是:首页 >技术教程 >Socket(一)网站首页技术教程

Socket(一)

jakiechaipush 2024-07-04 06:01:03
简介Socket(一)

1. 简介

Socket允许程序员将网路连接看作是另外一个可以读/写字节的流,Socket对程序员掩盖了网络的底层细节,如错误检测、包大小、包分解、包重传、网络地址等。Socket是两台主机之间有一个连接,它可以完成7个基本操作:

  • 连接远程主机
  • 发送数据
  • 接受数据
  • 关闭端口
  • 绑定端口
  • 监听入站信息
  • 在绑定端口上接受来自远程机器的连接

Java的socket类(客户端和服务器都可以使用)提供了四个对应前面操作的方法。后面三个操作仅仅服务器需要,即等待客户端连接。这些操作有ServerSocket实现。Java程序通常采用以下方式使用客户端Socket

  • 程序用构造函数创建一个新的Socket
  • Socket尝试连接远程主机

一旦建立连接,本地和远程主机就从这个Socket得到输入流和输出流,使用这两个流相互发送数据。连接是全双工的,两台主机都可以同时发送和接受数据。数据的含义取决于协议,发送给HTTP服务器的命令就有所不同。一般会先完成某种协商握手,然后再具体传输数据。当数据传输结束后,一端或两端将关闭连接,有些协议,如HTTP1.0,要求每次请求得到服务后都要关闭连接。其他协议,如FTP和HTTP1.1 ,则允许在一个连接上处理多个请求。

2. 用Socket从服务器读取

我们用Telnet可以连接NIST(美国国家标准与技术研究院)的daytime服务器,请求当前时间。

在这里插入图片描述
60091 23-05-27 08:55:49 50 0 0 894.0 UTC(NIST) * 实际daytime的发送时间,读取这个socket的Inpustream时,就会得到这个结果。下面看看Socket是如果通过编程获取同样的内容的,首先在端口13打开time.nist.gov的连接

Socket socket=new Socket("time.nist.gov",13)

上面代码会在网络上建立连接,如果连接超时,或者由于服务器未在端口13上监听而失败,构造函数会抛出一个IOException异常,所以通常要把这个代码包装在try-catch语句块中。 使用setSoTimeout方法会为连接设置一个超时时间,超时时间按照毫秒度量。如果服务器拒绝Socket连接,那么socket要很快的抛出一个ConnectException,或者如果路由器无法确定如何将你的包发送到服务器,则要抛出一个NoRouteToHostException异常。如果一个有问题的服务器接受了连接,然后停止与你对话,但是没有主动关闭连接,对socket设置一个超时时间,这意味着对这个socket的每一个读/写都最多耗费一定的毫秒数。如果你连接的服务器挂起,会抛出一个SocketTimeoutException通知你。具体要设置多少时间这取决于你的应用的需要,以及你希望的服务器响应性。对于一个本地内部服务器来说,15s的响应时间太长了,但是对于一个负载很大的公共服务器,这个时间则很短。一旦代开socket并设置超时时间,可以调用getInpustStream来返回一个InputStream,用它从socket读取字节,一般来讲服务器可以发送任何字节。

public class QuizCardBuilder {

    public static void main(String[] args) throws IOException {
        Socket socket=null;
        try{
            socket =new Socket("time.nist.gov",13);
            socket.setSoTimeout(15000);
            InputStream in=socket.getInputStream();
            StringBuilder time=new StringBuilder();
            InputStreamReader reader=new InputStreamReader(in,"ASCII");
            for(int c=reader.read();c!=-1;c=reader.read()){
                time.append((char)c);
            }
            System.out.println(time);
        }catch (IOException ex){
            System.err.println(ex);
        }finally {
            if(socket!=null)
                try {
                    socket.close();
                }catch (IOException ex){
                    
                }
        }
    }
}

在这里插入图片描述
可以看出这和上面Telnet程序的输出是一样的

3. 用Socket写入服务器

写入服务器并不比读取服务器困难,只需要向socket请求一个输出流以及一个输入流。使用输入流在Socket发送数据时,同时还可以使用输入流读取数据,不过大多数协议都针对客户端只读取socket或者只写入socket,而不是二者同时进行,最常见的模式是,客户端发送一个请求,然后服务器响应。客户端可能发送另一个请求,服务器再做出响应。这个过程会继续,直到客户端或服务器完成工作,然后关闭连接。RFC 2229定义的dict是一个简单饿双向TCP。在这个协议中客户端向dict服务器的2628端口打开一个socket,并发送类似“DEFINE eng-lat gold”命令,这会告诉服务器使用它的英语-拉丁语字典发送单纯“gold”的定义。telnet测试结果如下:
在这里插入图片描述
下面用java代码实现:

public class QuizCardBuilder {

    public static void main(String[] args){
        Socket socket=null;
        try{
            socket =new Socket("dict.org",2628);
            socket.setSoTimeout(15000);
            OutputStream out=socket.getOutputStream();
            Writer writer=new OutputStreamWriter(out,"UTF-8");
            writer=new BufferedWriter(writer);
            InputStream in=socket.getInputStream();
            StringBuilder time=new StringBuilder();
            BufferedReader reader=new BufferedReader(new InputStreamReader(in,"UTF-8"));

            define("DEFINE english computer",writer,reader);
            writer.write("quit
");
            writer.flush();
            System.out.println(time);
        }catch (IOException ex){
            System.err.println(ex);
        }finally {
            if(socket!=null)
                try {
                    socket.close();
                }catch (IOException ex){

                }
        }
    }
    static  void define(String word,Writer writer,BufferedReader bufferedReader) throws IOException {
        writer.write(word+"
");
        for(String line=bufferedReader.readLine();line!=null;line=bufferedReader.readLine()){
            if(line.startsWith("250")){
                return;
            }else if(line.startsWith("552")){
                System.out.println("NOT MATCH");
                return;
            }else if(line.matches("\d\d\d"))continue;
            else if(line.trim().equals("."))continue;
            else System.out.println(line);
        }

    }
}

半关闭Socket
close()方法同时关闭Socket的输入和输出。有时你希望只关闭一半连接,即输入或者输出。shutdownInput()shutdownOutput()方法只关闭连接的一半。这两个方法并不关闭Socket,实际上它们的作用是调整与Socket连接的流。使它认为已经到了流的末尾。关闭输入之后再读取会返回-1。关闭输出之后再写入Socket会抛出一个IOException异常。注意,即使半关闭了连接,或许连接的两半都关闭了,使用结束后仍然需要关闭Socket。shudown方法只会影响Socket的流,它们并不释放与Socket关联的资源,如所占用的端口等。isInputShutdown()isOutputShutdown()方法分别指出输入流和输出流是打开的还是关闭的。

4. 构造和连接Socket

java.net.Socket类是Java完成客户端TCP操作的基础类,其他建立TCP网络连接的面向客户端的类(如URL、URLConnection、Applet和JEditorpane)最终都会调用这个类的方法。这个类本身使用原生代码与主机操作系统的本地TCP栈进行通信。每个Socket构造函数指定要连接的主机和端口,主机可以指定为InetAddress或String。远程端口指定为1到65535之间的int值:

public Socket(String host,int port)throws UnknowHostException,IOException
public SOcket(InetAddress host,int port)throws IOException

下面代码查看本机已使用端口

public class QuizCardBuilder {
    public static void main(String[] args)
    {
        for(int i=1;i<256;i++)
        {
            try
            {
                InetAddress localHost=InetAddress.getLocalHost();
                Socket socket=new Socket(localHost,i);
                System.out.println("本机已经使用了端口:"+i);
            }
            catch(UnknownHostException e)
            {
                //e.printStackTrace();
            }
            catch(IOException e)
            {
                // e.printStackTrace();
            }
        }
        System.out.println("执行完毕!");
    }
}

有3个构造函数可以创建未连接的Socket。这些构造函数对于底层Socket的行为提供了更多控制,例如可以选择一个不同的代理服务器或者一个加密机制。

public Socket()
public Socket(Proxy proxy)
protected Socket(SocketImpl impl)

4. 选择从哪个本地接口连接

有两个构造函数可以指定要连接的主机和端口,以及从哪个接口和端口连接:

public Socket(String port,int port, InetAddress interface,int localPort)
public Socket(InetAddress host, int port , InetAddress interface , int localPort)

这个Socket连接到前两个参数中指定的主机和端口,它从后两个参数指定的本地网络接口和端口来连接,网络接口可以是物理接口(例如,一个以太网卡),也可以是虚拟接口(一个有多个IP地址的多宿主主机)。如果localport传入0,Java会随机选择1024到65535之间的一个可用端口。

5. 构造但不连接

目前为止所有的构造函数在创建Socket对象的同时都会打开一个远程主机的网络连接。如果没有为Socket构造函数提供任何参数,它就没有目标主机可以连接:

public Socket()

可以以后再为某个connect()方法传入一个SocketAddress来建立连接:

try{
   Socket socket=new Socket();
   SocketAddress address=new InetSocketAddress("time.nist.gov".13);
   socket.connect(adress);
  }catch(IOException ex){
    System.err.println(ex);
}

connect方法还可以传入一个参数,来指定连接超时之前等待的时间(毫秒数),默认值0表示永远等待下去

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。