【网络编程1】Java套接字Socket

来源:互联网 发布:近视眼手术知乎 编辑:程序博客网 时间:2024/05/18 00:42

这篇博客是本人学习《Java网络程序设计》书中第4章套接字的学习总结。初学者网友学习这篇Java套接字文章,如果难于理解文章前面理论部分,可以先运行后面的程序,边看运行后面的程序边理解前面的原理,这对初学者是最好的方法。所有源代码都在文章后面我的github链接代码中。
——惠州学院 13网络工程 吴成兵 20160607

目录 1

  • 目录 1
  • 一 流套接字概述
  • 二 服务器套接字ServerSocket
    • 21 ServerSocket的工程过程
    • 22 ServerSocket构造方法
    • 23 ServerSocket常用方法
  • 三 客户端套接字Socket
    • 31 Socket的7种基本操作
    • 32 Socket构造方法
    • 33 Socket常用方法
  • 四 Socket编程示例
    • 41 一对一的通信
      • 411 服务器程序
      • 412 客户端程序
    • 42 一对多的通信
      • 421 服务器主程序
      • 422 服务器线程程序
    • 43 带GUI界面的聊天程序
      • 431 聊天服务器程程序
      • 432 聊天客户端程程序

一 流套接字概述

  套接字(Socket)是由加利福尼亚大学伯克利分校首创的(University of California, Berkeley),它允许程序把网络连接看成一个(Stream),可以向这个流写入字节,也可以从这个流读取字节,也叫流套接字

  Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。(百度百科)
流套接字通讯过程
  Socket是网络上运行的两个程序间双向通信的一端,这既可以接受请求,也可以发送请求。可以认为Socket是应用程序创建的一个港口码头,应用程序只要把装在货物的集装箱(要发送的数据)放在码头上,就算完成了货物的运送,剩下的工作就由货运公司(驱动程序)去处理了。对接收方来说,应用程序也要创建一个码头,然后就一直等待该码头的货物到达,最后从码头上取走货物(数据)。

  java.net.Socket类是Java的基础类,用于执行客户端TCP(传输控制流协议)的操作。套接字有两种:一种套接字在服务器创建的,叫做服务器套接字(ServerSocket);还有一种在客户端被创建的,叫做客户端套接字(Socket)。

二 服务器套接字(ServerSocket)

2.1 ServerSocket的工程过程

  • 用ServerSocket()方法在指定端口创建一个新的ServerSocket对象。
  • ServerSocket对象调用accept()方法在指定的端口监听到来的连接。accept()一直处于阻塞状态,直到有客户端试图建立连接。这时,accept()方法返回连接客户端与服务器的Socket对象。
  • 调用getInputStream()方法或者getOutputStream()方法或者两者全调用建立与客户端交互的输入流和输出流。具体情况要看服务器的类型而定。
  • 服务器与客户端根据一定的协议交互,直到关闭连接。
  • 服务器、客户端或两者都要关闭连接。
  • 服务器回到第(2)步,继续监听下一次的连接。

2.2 ServerSocket构造方法

  • public ServerSocket(int port) throws IOException, BindException
  • public ServerSocket(int port, int queuelength) throws IOException, BindException
  • public ServerSocket(int port, int queuelength, InetAddress bindaddress) throws IOException, BindException

客户端指定的端口号一定要和这个port一样。这些构造方法允许指定端口,用来保存到来连接请求队列的长度,绑定本地网络的地址。默认最大连接数目queuelength为50。

2.3 ServerSocket常用方法

  • public Socket accept() throws IOException
    该方法采用阻塞方式监听,直到有信息传过来,它才会返回一个Socket对象。接下来,服务器就可以利用这个Socket对象与客户端进行通信了。
  • public Socket close() throws IOException
    实现了服务器套接字后要使用close()关闭它。

三 客户端套接字(Socket)

3.1 Socket的7种基本操作

  • 连接到远程服务器。
  • 绑定到端口。
  • 接收从远程服务器来的绑定端口上的连接。
  • 监听到达的数据。
  • 发送数据。
  • 接收数据。
  • 关闭连接。

3.2 Socket构造方法

(1) public Socket(String host, int port) throws unknowHostException, IOException
(2) public Socket(InetAddress host, int port) throws IOException
建立一个到服务器host、端口号port的套接字,连接到远程主机。

3.3 Socket常用方法

  • void connect(SocketAddress endpoint) :将套接字连接到服务器
  • void connect(SocketAddress endpoint, int timeout) :将套接字连接到服务器,timeout指定超时时间
  • InetAddress getLocalAddress() :获得套接字连接的本地地址
  • InetAddress getInetAddress() :获得套接字连接的远程地址
  • Int getPort() :获得套接字连接的远程端口
  • InputStream getInputStream() :获得套接字所用的输入流
  • OutputStream getOutputStream() :获得套接字所用的输出流
  • void close() //public synchronized void close()throws IOExption :关闭连接

四 Socket编程示例

4.1 一对一的通信

  在本程序中,客户端从命令行输入一个半径值并传送到服务器。服务器根据这个半径值,计算出圆面积发送给客户,客户端显示这个值;客户端输入“end”命令结束通信。
服务器端
客户端

4.1.1 服务器程序

//--------------- 文件名:Server.java ---------------------------------package _4_3Socket编程示例._4_4_1一对多的通信;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;/** * _4_3Socket编程示例._4_4_1一对多的通信 * Copyright ? 2016 Authors. All rights reserved. * * FileName: Server.java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-7/下午02:35:45 * Description: 服务器端接收客户端圆半径,计算圆的面积 */public class Server {    /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        System.out.println("S等待连接......");        try {            //创建端口号9955的服务器套接字            ServerSocket serverSocket = new ServerSocket(9955);            //监听来处客户的连接请求            Socket socket=serverSocket.accept();            System.out.println("S连接请求来自:"+socket.getInetAddress().getHostAddress());            //创建数据输入输出流            DataInputStream dis=new DataInputStream(socket.getInputStream());            DataOutputStream dos=new DataOutputStream(socket.getOutputStream());            boolean goon=true;            while(goon){                        String string=dis.readUTF();            //从socket中读取数据                if(string.equals("end")==false){                    string=dealWith(string);            //服务器执行特定功能                    dos.writeUTF(string);               //向socket dos写数据                    dos.flush();                        //清空缓冲区,立即发送                    System.out.println("S计算结果("+string+")已经发送");                }else{                    goon=false;                    dos.writeUTF("end");                    dos.flush();                }            }            //关闭socket和流            serverSocket.close();            dis.close();            dos.close();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 服务器执行特定功能函数,这里是个计算圆面积的功能     * @param string 圆的半径     * @return 圆的面积     */    public static String dealWith(String string){        System.out.print("S接收到的半径值("+string+");");          double radius=0.0;        try {            radius=Double.parseDouble(string);                  } catch (NumberFormatException e) {            return "输入数据格式不对!";        }        if(radius<0)return "数据数据不能小于0!";        double area=radius*radius*Math.PI;        return Double.toString(area);    }}

4.1.2 客户端程序

//--------------- 文件名:Client.java ---------------------------------package _4_3Socket编程示例._4_4_1一对多的通信;import java.io.BufferedReader;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.net.Socket;import java.net.UnknownHostException;/** * _4_3Socket编程示例._4_4_1一对多的通信 * Copyright ? 2016 Authors. All rights reserved. * * FileName: Client.java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-7/下午02:07:58 * Description: 客户端输入圆的半径,发送到用服务器计算,并收到结果 */public class Client {    public static void main(String[] args) {        try {            //创建连接到服务器的socket,服务器IP和端口如下            Socket socket = new Socket("localhost",9955);            //将数据输入输出流连接到socket上            DataInputStream dis = new DataInputStream(socket.getInputStream());            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());            System.out.println("C输入半径数值发送到服务器,输入end结束");            boolean goon=true;            //数据从终端输入            BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));            //反复读用户的数据并计算            while(goon){                String outString=bf.readLine();     //数据从终端输入                dos.writeUTF(outString);            //写到Socket dos中                dos.flush();                        //清空缓冲区,立即发送                String inString=dis.readUTF();      //从Socket dis中读数据                if(!inString.equals("end")){                    System.out.println("C从服务器返回的结果是:"+inString);                }else{                    goon=false;                    System.out.println("C服务结束!!!");                }            }            //关闭socket和流            socket.close();            dis.close();            dos.close();        } catch (UnknownHostException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }}

4.2 一对多的通信

  这个服务器同时能响应多个客户端请求。ServerSocket对象的accept()方法每当监听到一个连接请求时,就会产生一个Socket对象,所以只要此方法反复监听客户请求,就可以为每一个客户生成一个专用的Socket对象进行通信。服务器主程序和线程程序如下,客户端程序和一对一通信的Client.java程序一样。
服务器端
客户端1
客户端2

4.2.1 服务器主程序

//------------------- 文件名MultiServer.java -----------------------package _4_3Socket编程示例._4_4_2一对多的通信;import java.net.ServerSocket;import java.net.Socket;/** * _4_3Socket编程示例._4_4_2一对多的通信; * Copyright ? 2016 Authors. All rights reserved. * * FileName: MultiServer.java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-7/下午11:12:51 * Description: 这是主程序,它只要简单地启动线程就可以了。 */public class MultiServer {    /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        System.out.println("S启动......");        try {            ServerSocket serverSocket = new ServerSocket(9955);            Socket connectToClientSocket=null;            int i=0;            while(++i!=0){                //等待客户端的请求                connectToClientSocket=serverSocket.accept();                //每次请求都启动一个线程来处理                new ServerThread(connectToClientSocket,i);            }        }        catch (Exception e) {        }       }}

4.2.2 服务器线程程序

//------------------- 文件名ServerThread.java -----------------------package _4_3Socket编程示例._4_4_2一对多的通信;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.Socket;/** * _4_3Socket编程示例._4_4_2一对多的通信; * Copyright ? 2016 Authors. All rights reserved. * * FileName: ServerThread.java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-7/下午11:11:32 * Description: 利用本线程来完成服务器与客户端的通信工程 */public class ServerThread extends Thread {    private Socket connectToClientSocket;    private DataInputStream inFromClient;    private DataOutputStream outToClient;    private int clientname=1;   //不能用static,否则所用线程会共用clienti变量    /**     * 构造函数1     * @param socket     * @throws IOException     */    public ServerThread(Socket socket) throws IOException {        super();        connectToClientSocket=socket;        inFromClient=new DataInputStream(connectToClientSocket.getInputStream());        outToClient=new DataOutputStream(connectToClientSocket.getOutputStream());        System.out.println("S连接请求来自:"+connectToClientSocket.getInetAddress().getHostAddress());        start();        //启动run()方法    }    /**     * 构造函数2     * @param socket     * @param ci 连接的第i个客户端     * @throws IOException     */    public ServerThread(Socket socket,int cname) throws IOException {        super();        connectToClientSocket=socket;        clientname=cname;        inFromClient=new DataInputStream(connectToClientSocket.getInputStream());        outToClient=new DataOutputStream(connectToClientSocket.getOutputStream());        System.out.println("S连接请求来自"+clientname+":"+connectToClientSocket.getInetAddress().getHostAddress());        start();        //启动run()方法    }    /**     * 在run()方法与客户端通信     */    @Override    public void run() {        // TODO Auto-generated method stub        super.run();        System.out.println("S连接Clinet"+clientname+"......");        try {            boolean goon=true;            while(goon){                        String string=inFromClient.readUTF();           //从socket中读取数据                if(string.equals("end")==false){                    string=dealWith(string,clientname);                 //服务器执行特定功能                    outToClient.writeUTF(string);               //向socket dos写数据                    outToClient.flush();                        //清空缓冲区,立即发送                    System.out.println("SC"+clientname+"计算结果("+string+")已经发送");                }else{                    goon=false;                    outToClient.writeUTF("end");                    outToClient.flush();                    System.out.println("SC"+clientname+"服务结束!!!");                }            }            //关闭socket和流            connectToClientSocket.close();            inFromClient.close();            outToClient.close();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 服务器执行特定功能函数,这里是个计算圆面积的功能     * @param string 圆的半径     * @return 圆的面积     */    public static String dealWith(String string,int ci){        System.out.print("SC"+ci+"接收到的半径值("+string+");");           double radius=0.0;        try {            radius=Double.parseDouble(string);                  } catch (NumberFormatException e) {            return "输入数据格式不对!";        }        if(radius<0)return "数据数据不能小于0!";        double area=radius*radius*Math.PI;        return Double.toString(area);    }}

4.3 带GUI界面的聊天程序

  当用户输入文字时,程序如何接收对方发来的数据?解决的方法是将接收数据放到独立的线程中,它始终在后台运行,一旦对方发来了数据,就立即显示在界面上。而主界面负责输入文字和发送数据,这样发送和接收数据互不影响。
聊天程序

4.3.1 聊天服务器程程序

package _4_3Socket编程示例._4_4_4简单的聊天程序;import java.awt.BorderLayout;import java.awt.Container;import java.awt.FlowLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.JTextField;/** *  * Copyright ? 2016 Authors. All rights reserved. * * FileName: .java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-9/下午12:19:57 * Description: */public class ChatServer implements ActionListener, Runnable {    JTextField msgTextField;    JTextArea showTextArea;    JFrame mainJFrame;    JButton sentButton;    JScrollPane jSPane;    JPanel panel;// 嵌板    Container container;// 容器    Thread thread = null;    ServerSocket serverSocket;    Socket connectToClientSocket;    DataInputStream inFromClient;    DataOutputStream outToClient;    public ChatServer() {        // 设置界面,包含容器        mainJFrame = new JFrame("聊天——服务器端(黑猫顺:爱上你的专业,精通专业技能。)");        container = mainJFrame.getContentPane();        // 聊天信息展示框        showTextArea = new JTextArea();        showTextArea.setEditable(false); // 不可编辑        showTextArea.setLineWrap(true); // 自动换行        jSPane = new JScrollPane(showTextArea);        // 聊天信息输入框        msgTextField = new JTextField();        msgTextField.setColumns(30); // 输入框长度        msgTextField.addActionListener(this);/**/// ?        // 发送按键        sentButton = new JButton("发送");        sentButton.addActionListener(this);/**/        // 嵌板有聊天信息输入框和发送按键        panel = new JPanel();        panel.setLayout(new FlowLayout());        panel.add(msgTextField);        panel.add(sentButton);        // 容器包含聊天信息展示框和嵌板        container.add(jSPane, BorderLayout.CENTER);        container.add(panel, BorderLayout.SOUTH);        // 主界面,要定义在后面        mainJFrame.setSize(500, 400);        mainJFrame.setVisible(true);        mainJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        try {            // 创建服务器套接字            serverSocket = new ServerSocket(9955);            showTextArea.append("正在等待对话请求..." + getTime() + "\n");            // 监听客户端的连接            connectToClientSocket = serverSocket.accept();            inFromClient = new DataInputStream(connectToClientSocket                    .getInputStream());            outToClient = new DataOutputStream(connectToClientSocket                    .getOutputStream());            // 启动线程在后台来接收对方的消息            thread = new Thread(this);            thread.setPriority(Thread.MAX_PRIORITY);            thread.start();            /**             * public final static int MIN_PRIORITY = 1; public final static int             * NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;             * setPriority的参数在1 - 10 之间就可以, 否则会抛异常.             * setPriority不一定起作用的,在不同的操作系统不同的jvm上,效果也可能不同。             * 现在很多jvm的线程的实现都使用的操作系统线程,设置优先级也是使用的操作系统优先级,             * java层面有10个优先级别,假设操作系统只有3个优先级别, 那么jvm可能将1-4级映射到操作系统的1级,             * 5-7级映射到操作系统的2级, 剩下的映射到3级,这样的话,在java层面,将优先级设置为5,6,7,其实本质就是一样的了。             */        } catch (IOException e) {            showTextArea.append("对不起,不能创建服务器" + getTime() + "");            msgTextField.setEditable(false); // 不可编辑            msgTextField.setEnabled(false); // 不可见        }    }    /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        new ChatServer();    }    // 响应按钮事件,发送消息给对方    @Override    public void actionPerformed(ActionEvent e) {        String string = msgTextField.getText();        if (string.length() > 0) {            try {                outToClient.writeUTF(string);                outToClient.flush();                showTextArea.append("黑猫顺说(" + string + ")" + getTime() + "\n");                msgTextField.setText(null);            } catch (IOException e1) {                showTextArea.append("你的消息(" + string + ")未能发送出去" + getTime()                        + "\n");            }        }    }    @Override    public void run() {        try {            while (true) {                showTextArea.append("吴兵说(" + inFromClient.readUTF() + ")"                        + getTime() + "\n");                Thread.sleep(1000);            }        } catch (IOException e) {        } catch (InterruptedException e) {            thread.start();        }    }    /**     * Java代码中获得当前时间     *      * @return 当前时期时间     */    private String getTime() {        Date date = new Date();        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        String time = format.format(date);        return time;    }}

4.3.2 聊天客户端程程序

package _4_3Socket编程示例._4_4_4简单的聊天程序;import java.awt.BorderLayout;import java.awt.Container;import java.awt.FlowLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.JTextField;/** *  * Copyright ? 2016 Authors. All rights reserved. * * FileName: .java * @author : Wu_Being <1040003585@qq.com> * Date/Time: 2016-6-9/下午12:20:18 * Description: */public class ChatClient implements ActionListener, Runnable {    JTextField msgTextField;    JTextArea showTextArea;    JFrame mainJFrame;    JButton sentButton;    JScrollPane jSPane;    JPanel panel;// 嵌板    Container container;// 容器    Thread thread = null;    ServerSocket serverSocket;    Socket connectToClientSocket;    DataInputStream inFromClient;    DataOutputStream outToClient;    public ChatClient() {        // 设置界面,包含容器        mainJFrame = new JFrame("聊天——客户端(吴兵)");        container = mainJFrame.getContentPane();        // 聊天信息展示框        showTextArea = new JTextArea();        showTextArea.setEditable(false); // 不可编辑        showTextArea.setLineWrap(true); // 自动换行        jSPane = new JScrollPane(showTextArea);        // 聊天信息输入框        msgTextField = new JTextField();        msgTextField.setColumns(30); // 输入框长度        msgTextField.addActionListener(this);/**/// ?        // 发送按键        sentButton = new JButton("发送");        sentButton.addActionListener(this);/**/        // 嵌板有聊天信息输入框和发送按键        panel = new JPanel();        panel.setLayout(new FlowLayout());        panel.add(msgTextField);        panel.add(sentButton);        // 容器包含聊天信息展示框和嵌板        container.add(jSPane, BorderLayout.CENTER);        container.add(panel, BorderLayout.SOUTH);        // 主界面,要定义在后面        mainJFrame.setSize(500, 400);        mainJFrame.setVisible(true);        mainJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        try {            // 创建套接字连接到服务器            connectToClientSocket = new Socket("localhost", 9955);/**/            inFromClient = new DataInputStream(connectToClientSocket                    .getInputStream());            outToClient = new DataOutputStream(connectToClientSocket                    .getOutputStream());            showTextArea.append("连接成功,请说话..." + getTime() + "\n");            // 创建线程在后台来接收对方的消息            thread = new Thread(this);            thread.setPriority(Thread.MAX_PRIORITY);            thread.start();        } catch (IOException e) {            showTextArea.append("对不起,没能连接到服务器" + getTime() + "");            msgTextField.setEditable(false); // 不可编辑            msgTextField.setEnabled(false); // 不可见        }    }    /**     * @param args     */    public static void main(String[] args) {        // TODO Auto-generated method stub        new ChatClient();    }    // 响应按钮事件,发送消息给对方    @Override    public void actionPerformed(ActionEvent e) {        String string = msgTextField.getText();        if (string.length() > 0) {            try {                outToClient.writeUTF(string);                outToClient.flush();                showTextArea.append("吴兵说(" + string + ")" + getTime() + "\n");                msgTextField.setText(null);            } catch (IOException e1) {                showTextArea.append("你的消息(" + string + ")未能发送出去" + getTime()                        + "\n");            }        }    }    @Override    public void run() {        try {            while (true) {                showTextArea.append("黑猫顺说(" + inFromClient.readUTF() + ")"                        + getTime() + "\n");                Thread.sleep(1000);            }        } catch (IOException e) {        } catch (InterruptedException e) {            thread.start();        }    }    /**     * Java代码中获得当前时间     *      * @return 当前时期时间     */    private String getTime() {        Date date = new Date();        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        String time = format.format(date);        return time;    }}

文中所有源代码链接

Wu_Being博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
《Java套接字Socket》:
http://blog.csdn.net/u014134180/article/details/51615011

Wu_Being 吴兵博客接受赞助费二维码

如果你看完这篇博文,觉得对你有帮助,并且愿意付赞助费,那么我会更有动力写下去。


  1. 返回到目录
1 3
原创粉丝点击