`
阅读更多

前一段时间看了IBM developerWork上的一篇关于socket的tutorial,由浅入深的实现了server-client程序,现在这里整理一下。
    什么是socket?
    think in java 里给的回答:套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节。简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道.
    Socket 和 ServerSocket例子
   
一.客户端步骤:
    1)用您想连接的机器的 IP 地址和端口实例化 Socket(如有问题则抛出 Exception)。
    2)获取 Socket 上的流。
    3)把流包装进 BufferedReader/PrintWriter 的实例,如果这样做能使事情更简单的话。
    4)对 Socket 进行读写。
    5)关闭打开的流。
   
    RemoteFileClient类
    import java.io.*;
    import java.net.*;
     public class RemoteFileClient {
    protected String hostIp;
    protected int hostPort;
    protected BufferedReader socketReader;
    protected PrintWriter socketWriter;

    public RemoteFileClient(String aHostIp, int aHostPort) {
        hostIp = aHostIp;
        hostPort = aHostPort;
    }
    public static void main(String[] args) {
    }
    public void setUpConnection() {              //连接到远程服务器
    }
    public String getFile(String fileNameToGet) {//向远程服务器请求 fileNameToGet 的内容,在服务器传回其内容时接收该内容
    }
    public void tearDownConnection() {           //从远程服务器上断开
    }
}

       main()函数:
   public static void main(String[] args) {
    RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);
    remoteFileClient.setUpConnection();
    String fileContents =
        remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");
    remoteFileClient.tearDownConnection();

    System.out.println(fileContents);
}

     setUpConnection()函数实现:
   public void setUpConnection() {
    try {
        Socket client = new Socket(hostIp, hostPort);

        socketReader = new BufferedReader(
             new InputStreamReader(client.getInputStream()));//使我们能够读取流的行
        socketWriter = new PrintWriter(client.getOutputStream());//使我们能够发送文件请求到服务器:

    } catch (UnknownHostException e) {
        System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
    } catch (IOException e) {
        System.out.println("Error setting up socket connection: " + e);
    }
}

     getFile() 的实现:
    public String getFile(String fileNameToGet) {
    StringBuffer fileLines = new StringBuffer();

    try {
        socketWriter.println(fileNameToGet);  //把请求发送到主机,PrintWriter 是我们在创建连接期间建立的               
        socketWriter.flush();

        String line = null;
        while ((line = socketReader.readLine()) != null)
            fileLines.append(line + "\n");
    } catch (IOException e) {
        System.out.println("Error reading from file: " + fileNameToGet);
    }

    return fileLines.toString();
}

    tearDownConnection()方法的实现:
public void tearDownConnection() {
    try {
        socketWriter.close();
        socketReader.close();
    } catch (IOException e) {
        System.out.println("Error tearing down socket connection: " + e);
    }
}
    在上次做的聊天服务器程序中,因为"清除"的方法不对,导致占用大量内存:(

二.服务器步骤:
    1)用一个您想让它侦听传入客户机连接的端口来实例化一个 ServerSocket(如有问题则抛出 Exception)。
    2)调用 ServerSocket 的 accept() 以在等待连接期间造成阻塞。
    3)获取位于该底层 Socket 的流以进行读写操作。
    4)按使事情简单化的原则包装流。
    5)对 Socket 进行读写。
    6)关闭打开的流(并请记住,永远不要在关闭 Writer 之前关闭 Reader)。
   
    RemoteFileServer 类
    import java.io.*;
    import java.net.*;
    public class RemoteFileServer {
    protected int listenPort = 3000;
    public static void main(String[] args) {
    }
    public void acceptConnections() {                         //允许客户机连接到服务器
    }
    public void handleConnection(Socket incomingConnection) { //与客户机 Socket 交互以将您所请求的文件的内容发送到客户机
    }
}
   
     main() 方法的实现:
     public static void main(String[] args) {
    RemoteFileServer server = new RemoteFileServer();
    server.acceptConnections();
}

    acceptConnections()方法的实现:
    public void acceptConnections() {
    try {
        ServerSocket server = new ServerSocket(listenPort);
        Socket incomingConnection = null;
        while (true) {
            incomingConnection = server.accept();
            handleConnection(incomingConnection);
        }
    } catch (BindException e) {
        System.out.println("Unable to bind to port " + listenPort);
    } catch (IOException e) {
        System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
    }
}
    我们通过调用该 ServerSocket 的 accept() 来告诉它开始侦听。accept() 方法将造成阻塞直到来了一个连接请求。此时,accept()     返回一个新的 Socket,这个 Socket 绑定到服务器上一个随机指定的端口,返回的 Socket 被传递给 handleConnection()。

    handleConnection() 方法的实现:
    public void handleConnection(Socket incomingConnection) {
    try {
        OutputStream outputToSocket = incomingConnection.getOutputStream();
        InputStream inputFromSocket = incomingConnection.getInputStream();

        BufferedReader streamReader =
            new BufferedReader(new InputStreamReader(inputFromSocket));

        FileReader fileReader = new FileReader(new File(streamReader.readLine()));//获取一条有效的文件路径

        BufferedReader bufferedFileReader = new BufferedReader(fileReader);
        PrintWriter streamWriter =
            new PrintWriter(incomingConnection.getOutputStream());
        String line = null;
        while ((line = bufferedFileReader.readLine()) != null) {
            streamWriter.println(line);
        }

        fileReader.close();
        streamWriter.close();
        streamReader.close();
    } catch (Exception e) {
        System.out.println("Error handling a client: " + e);
    }
}
     如果您在关闭 streamWriter 之前关闭 streamReader,则您可以往 Socket 写任何东西,但却没有任何数据能通过通道(通道被关       闭了)。
   
三.使服务器支持多线程
   一般步骤:
   1)修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 1 的指定数字)实例化 ServerSocket。
   2)修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。
   3)借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类
    public void acceptConnections() {
        try {
        ServerSocket server = new ServerSocket(listenPort, 5);
        Socket incomingConnection = null;
        while (true) {
            incomingConnection = server.accept();
            handleConnection(incomingConnection);
        }
    } catch (BindException e) {
    System.out.println("Unable to bind to port " + listenPort);
    } catch (IOException e) {
    System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
    }
}
    假设我们指定待发数(backlog 值)是 5 并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是 5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2—6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝。
   
   public void handleConnection(Socket connectionToHandle) {
     new Thread(new ConnectionHandler(connectionToHandle)).start();
}
    我们对 RemoteFileServer 所做的大改动就体现在这个方法上。我们仍然在服务器接受一个连接之后调用 handleConnection(),但现在我们把该 Socket 传递给 ConnectionHandler 的一个实例,它是 Runnable 的。我们用 ConnectionHandler 创建一个新 Thread 并启动它。ConnectionHandler 的 run() 方法包含Socket 读/写和读 File 的代码,这些代码原来在 RemoteFileServer 的 handleConnection() 中。
   
    ConnectionHandler 类
    import java.io.*;
    import java.net.*;

public class ConnectionHandler implements Runnable{
   Socket socketToHandle;

   public ConnectionHandler(Socket aSocketToHandle) {
      socketToHandle = aSocketToHandle;
   }

   public void run() {
   }
}

    run() 方法的实现,同RemoteFileServer 的 handleConnection():
    public void run() {
        try {
            PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
            BufferedReader streamReader =
                new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));

            String fileToRead = streamReader.readLine();
            BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));

            String line = null;
            while ((line = fileReader.readLine()) != null)
                streamWriter.println(line);

            fileReader.close();
            streamWriter.close();
            streamReader.close();
        } catch (Exception e) {
            System.out.println("Error handling a client: " + e);
        }
    }

四.更高效地管理服务器端
    我们可以维护一个进入的连接池,一定数量的 ConnectionHandler 将为它提供服务。这种设计能带来以下好处:
    1)它限定了允许同时连接的数目。
    2)我们只需启动 ConnectionHandler Thread 一次。
    步骤:
    1)创建一个新种类的连接处理程序(我们称之为 PooledConnectionHandler)来处理池中的连接。
    2)修改服务器以创建和使用一组 PooledConnectionHandler
    在服务器端,我们在服务器启动时创建一定数量的 ConnectionHandler,我们把进入的连接放入“池”中并让 ConnectionHandler 打理剩下的事情。这种设计中有很多我们不打算讨论的可能存在的技巧。例如,我们可以通过限定允许在“池”中建立的连接的数目来拒绝客户机。
    PooledRemoteFileServer类
    import java.io.*;
    import java.net.*;
    import java.util.*;
    public class PooledRemoteFileServer {
    protected int maxConnections;         //我们的服务器能同时处理的活动客户机连接的最大数目
    protected int listenPort;             //进入的连接的侦听端口
    protected ServerSocket serverSocket;  //接受客户机连接请求的 ServerSocket

    public PooledRemoteFileServer(int aListenPort, int maxConnections) {
        listenPort = aListenPort;
        this.maxConnections = maxConnections;
    }
    public static void main(String[] args) {
    }
    public void setUpHandlers() {        //创建数目为 maxConnections 的大量 PooledConnectionHandler
    }
    public void acceptConnections() {
    }
    protected void handleConnection(Socket incomingConnection) {
    }
}

   main() 方法的实现:
   public static void main(String[] args) {
    PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3);
    server.setUpHandlers();
    server.acceptConnections();
}
   我们实例化一个新的 PooledRemoteFileServer,它将通过调用 setUpHandlers() 来建立三个 PooledConnectionHandler。一旦服务器就绪,我们就告诉它 acceptConnections()。
  
   public void setUpHandlers() {
    for (int i = 0; i < maxConnections; i++) {
        PooledConnectionHandler currentHandler = new PooledConnectionHandler();
        new Thread(currentHandler, "Handler " + i).start();
    }
}
    setUpHandlers() 方法创建 maxConnections(例如 3)个 PooledConnectionHandler 并在新 Thread 中激活它们。用实现了 Runnable 的对象来创建 Thread 使我们可以在 Thread 调用 start() 并且可以期望在 Runnable 上调用了 run()。换句话说,我们的 PooledConnectionHandler 将等着处理进入的连接,每个都在它自己的 Thread 中进行。我们在示例中只创建三个 Thread,而且一旦服务器运行,这就不能被改变。
   
    我们实现需作改动的 handleConnections() 方法,它将委派 PooledConnectionHandler 处理连接:
    protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
   
    PooledConnectionHandler 类:
   
    import java.io.*;
    import java.util.*;
    public class PooledConnectionHandler implements Runnable {
    protected Socket connection;                      //当前正在处理的 Socket
    protected static List pool = new LinkedList();    //名为 pool 的静态 LinkedList 保存需被处理的连接

    public PooledConnectionHandler() {
    }
    public void handleConnection() {
    }
    public static void processRequest(Socket requestToHandle) {
    }
    public void run() {
    }
}
  
   processRequest() 方法,它将把传入请求添加到池中,并告诉其它正在等待的对象该池已经有一些内容:
   public static void processRequest(Socket requestToHandle) {
    synchronized (pool) {
        pool.add(pool.size(), requestToHandle);
        pool.notifyAll();
    }
}

   实现 PooledConnectionHandler 上需作改动的 run()方法,它将在连接池上等待,并且池中一有连接就处理它:
   public void run() {
        while (true) {
             synchronized (pool) {
                  while (pool.isEmpty()) {
                       try {
                            pool.wait();
                       } catch (InterruptedException e) {
                            return;
                       }
                   }
                   connection = (Socket) pool.remove(0);
             }
             handleConnection();
        }
}
   
    实现需做改动的 handleConnection() 方法,该方法将攫取连接的流,使用它们,并在任务完成之后清除它们:
    public void handleConnection() {
    try {
        PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());
        BufferedReader streamReader =
            new BufferedReader(new InputStreamReader(connection.getInputStream()));

        String fileToRead = streamReader.readLine();
        BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));

        String line = null;
        while ((line = fileReader.readLine()) != null)
            streamWriter.println(line);

        fileReader.close();
        streamWriter.close();
        streamReader.close();
    } catch (FileNotFoundException e) {
        System.out.println("Could not find requested file on the server.");
    } catch (IOException e) {
        System.out.println("Error handling a client: " + e);
    }
}
    总结:在现实生活中使用套接字只是这样一件事,即通过贯彻优秀的 OO 设计原则来保护应用程序中各层间的封装。

 

 

 

 

 

   

 

 


 

分享到:
评论

相关推荐

    sockets_tutorial.pdf

    《RPG IV Socket Tutorial》由Scott Klement撰写,是一份专为使用RPG IV编程语言在AS/400或iSeries系统上的程序员设计的网络编程教程,主要讲解如何使用套接字(Sockets)进行TCP通信。教程假设读者已经熟悉RPG IV...

    socket_tutorial

    《Java Socket编程详解》 在Java编程中,Socket是网络通信的重要组成部分,它提供了一种在不同计算机之间建立连接并交换数据的机制。本教程将深入讲解Java Socket编程的基础知识和实战技巧,帮助开发者理解如何利用...

    Socket说明文档(英文版)

    - Oracle 官方文档关于 Socket 的介绍:[Java Networking Guide](https://docs.oracle.com/javase/tutorial/networking/sockets/) - Java Socket API 文档:[Java SE 11 Documentation]...

    Socket编程

    ### Socket编程概述 #### 一、什么是Socket? 在计算机网络通信中,**Socket**是一种用于在应用程序进程与传输层之间建立连接的接口。通过Socket,应用程序可以实现与本地或远程另一应用程序之间的消息发送与接收...

    ns3-tutorial

    5. Socket Programming:介绍了 ns-3 中的套接字编程基础。 Getting Started 部分涵盖了 ns-3 的安装、配置和使用,包括: 1. Overview:介绍了 ns-3 的安装和配置过程。 2. Downloading ns-3:介绍了如何下载和...

    python-socket-programming-tutorial:python socket编程教程

    Python中的套接字编程介绍套接字是基础。... 现在让我们创建一个: # Code 1.pyimport sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)print("Socked created!")代码很简单。 我们首先导入s

    C_Socket_Programming_Tutorial-_Writing_ClientServer_Programs_in_C_Using

    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) ) { perror("accept failed"); exit(EXIT_FAILURE); } // 发送数据 send(new_socket, hello, strlen(hello), 0);...

    Socket_CHAT

    Instance running on VS2008, with a brief tutorial explaining in detail some of the necessary knowledge Socket programming as well as examples of the implementation process, for C# development of ...

    tutorial.pdf

    比如,`os`模块用于操作系统交互,`sys`模块处理系统相关的信息,`socket`模块用于网络编程,`json`模块则用于JSON数据的编码和解码。 对于初学者,Python的异常处理机制也相当友好。通过`try/except`语句,你可以...

    SUN Java certificate tutorial.rar

    在这个"SUN Java certificate tutorial.rar"压缩包中,很显然包含了一个关于如何理解和使用Java证书的教学资源。 首先,Java证书是公钥基础设施(PKI)的一部分,它由可信的证书颁发机构(CA)签署,以确认一个实体...

    Python Tutorial Python

    - **网络编程**:如HTTP客户端和服务端的实现,套接字(socket)编程等。 - **并发与多线程**:Python提供了多线程和多进程模型,以及异步IO模型如asyncio库。 **Python 2.5b2版本** "Release 2.5b2"指的是Python的...

    Sockets Tutorial

    **Sockets Tutorial** 在计算机网络编程中,Sockets是一种接口,允许程序通过网络发送和接收数据。本教程将深入探讨Sockets的工作原理及其在实际应用中的实现。我们将重点关注基于TCP/IP协议栈的Socket编程,主要...

    Java Tutorial

    例如,FileInputStream和FileOutputStream用于文件操作,Socket和ServerSocket用于网络编程。 8. **多线程** Java内置了对多线程的支持,通过Thread类和Runnable接口可以创建并运行多个执行线程。同步机制(如...

    The Java Tutorial Fourth Edition

    - **Socket编程**:Java提供了Socket和ServerSocket类,支持TCP/IP通信。 - **URL和URLConnection**:用于访问网络资源,执行HTTP请求。 8. **JDBC数据库编程** - **连接数据库**:使用DriverManager.get...

    Java2 Certification last minute tutorial

    【Java2 Certification Last Minute Tutorial】是一本专门为Java2(即Java SE 2)认证考试准备的教程,旨在帮助考生在最后冲刺阶段巩固和复习关键知识点。这个教程可能包含了一系列章节,详细涵盖了Java语言的核心...

    视频通话android-webrtc-tutorial-master.zip

    - **Signaling Channel**:用于传输SDP和ICE候选等信令信息,可能使用WebSocket、Socket.io或其他网络通信技术实现。 - **ViewRender**:自定义的SurfaceView渲染器,用于在屏幕上显示视频流。 在学习这个教程时,...

    api 例子theForger's Win32 API Tutorial

    《theForger's Win32 API Tutorial》是一个深入学习Windows操作系统API编程的教程资源,包含了丰富的源代码示例。在Windows编程中,API(Application Programming Interface)是操作系统为应用程序提供的接口,让...

    Python 2.7 Tutorial 中文版.zip

    - **Socket编程**:socket模块提供低级网络通信接口,用于创建TCP/UDP套接字。 9. **并发编程** - **线程**:使用threading模块创建和管理线程,实现并发执行。 - **进程**:multiprocessing模块提供跨进程的...

    chatting-app-tutorial:使用MERN Stack + Socket.io聊天应用程序教程

    在这个“chatting-app-tutorial”教程中,我们将深入探讨如何使用MERN Stack(MongoDB、Express.js、React和Node.js)结合Socket.io构建一个实时聊天应用程序。MERN Stack是当前Web开发领域中非常流行的技术栈,它...

Global site tag (gtag.js) - Google Analytics