Java高级特性 第9节 Socket机制

一、Socket简介

   1.Socket概述

  Java最初是作为网络编程语言出现的,它对网络的高度支持,使得客户端和服务器流畅的沟通变成现实。而在网络编程中,使用最多的就是Socket,每一个实用的网络程序都少不了它的身影。

  在计算机网络编程技术中,两个进程,或者说两台计算机可以通过一个网络通信实现数据的交换,这种通信链路的端点就被称为“套接字”(即Socket),Socket是网络驱动层提供给应用程序的接口或者说是一种机制。举一个物流快递的例子来说明Socket,发件人将写有收货地址信息的货物送到快递站,发件人不用关心物流是如何进行的,货物被送到收货人所在地区的快递点,进行配送,收货人等待收货就可以了,这个过程很形象地说明了信息在网络中传递的过程。其中货物就是信息,2个快递点就是2个端点Socket。信息在网络中寻址传递,应用程序并不关心,只负责准备发送数据和接收数据。

  

  2.Socket通信原理

  对于编程人员来说,无需了解Socket底层机制是如何传送数据的,而是直接将数据提交给Socket,Socket会根据应用程序提供相关的信息。通过一系列计算,绑定IP及信息数据,将数据交给驱动程序向网络上发送出去。

  Socket的底层机制非常复杂,Java平台提供了一些虽然简单但相当强大的类,可以简单地有效地使用Socket开发通信程序而无需了解底层机制。

  

  3. java.net包

  java.net包提供了若干支持基于套接字的客户端/服务器通信的类。

  java.net包中常用的类有Socket、ServerSocket、DatagramPacket、DatagramSocket、InetAddress、URL、URLConnection和URLEncoder等。

  为了监听客户端的连接请求,可以使用ServerSocket类。Socket类实现用于网络上进程通信的套接字。DtatagramSocket类使用UDP协议实现客户端和服务器套接字。DatagramPacket类使用DatagramSocket类的对象封装设置和收到的数据报。InetAddress类表示Internet地址。在创建数据报文和Socket对象时,可以使用InetAdress类

二、基于TCP协议的Socket编程

   java.net包的两个类Socket和ServerSocket,分贝用来实现双向安全连接的客户端和服务器端,他们是基于TCP协议进行工作的,它的工作过程如同打电话的过程,只有双方都接通了,才能开始通话。

  进行通信时,Socket需要借助数据流来完成数据的传递工作。如果一个应用程序要通过网络向另一个应用程序发送数据,只要简单的创建Socket,然后将数据写入到与该Socket关联的输出流即可。对应的,接受方的应用程序创建Socket,从相关的输入流读取数据即可。

  

  1. Socket类

  Socket类在客户端和服务器端之间建立连接。可用Socket类的构造方法创建套接字,并将此套接字连接至制定的主机和端口。

  • 构造方法
    • Socket s = new Socket(hostName,port);  hostName:主机名,port:端口号。创建对象时可能会抛出UnknownHostException或IOException异常,必须捕获它们。
    • Socket s = new Socket(address,port);   address:InetAddress对象。创建对象时可能会抛出UnknownHostException或IOException异常,必须捕获它们。
  • 常用方法
方法名 说明
InetAddress getInetAddress() 返回与Socket对象关联的InetAddress
int getPort() 返回此Socket对象所连接的远程端口
int getLocalPort 返回此Socket对象所连接的本地端口
InputStream getInputStream() 返回与此套接字关联的InputStream
OutputStream getOutputStream() 返回与此套接字关联的OutputStream
void  close() 关闭该Socket

  2.ServerSocket类

  ServerSocket对象等待客户端建立连接,连接建立后进行通信。

  • 构造方法
    • ServerSocket s = new ServerSocket(port);  port:端口号。创建对象时可能会抛出IOException异常,必须捕获它们。
    • ServerSocket s = new ServerSocket(port,maxqu);   maxqu:最大队列长度队列长度表示系统在拒绝连接前可以拥有的最大客户端连接数。
  • 常用方法

  Socket类中列出的方法同样适用于ServerSocket,此外,ServerSocket类具有accept()方法,此方法用于等待客户端发起通信,这样Sockt对象就可以用于进一步的数据传输

  下面是一个简单例子:

package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
    /**
   * 服务器端 * @param args * @throws IOException
*/ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //使用ServerSocket ServerSocket server = new ServerSocket(8000); //每个用户在程序中就是一个Socket Socket client = null; //等待客户端连接 client = server.accept(); //像客户端打印信息 PrintWriter out = null; //准被向客户端打印信息 out = new PrintWriter(client.getOutputStream()); out.println("我是服务器端,Hello World");
//关闭流和Socket对象
out.close(); client.close(); server.close(); } }
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPClient {
    /**
   * 客户端
* @param args * @throws IOException * @throws UnknownHostException */ public static void main(String[] args) throws UnknownHostException, IOException { // TODO Auto-generated method stub //表示一个客户端的Socket Socket client = null; //表示一个客户端的输入信息 BufferedReader buf = null; client = new Socket("localhost",8000); buf = new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println(buf.readLine());
//关闭流和Socket对象 buf.close(); client.close(); } }  

  

  注意,先执行服务器端代码,在执行客户端代码。

三、使用Socket编程实现用户登录

  1.实现单用户登陆  

  Socket编程一般分为4个步骤:

  • 建立连接
  • 打开Socket关联的输入/输出流
  • 从数据流中读写信息
  • 关闭所有的数据流和Socket
package cn.yu.SocketDemo;
import java.io.Serializable;

/*
 * 用户类:要传递的信息
 */
public class User implements Serializable {
    private String loginName;   //用户名
    private String pwd;  //用户密码
    public User(String loginName,String pwd){
        super();
        this.loginName = loginName;
        this.pwd = pwd;
    }
    //getter/setter方法
    public String getLoginName() {
        return loginName;
    }
    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;

public class TCPClient {

    /**
     * 客户端
     * @param args
     * @throws IOException
     * @throws UnknownHostException
     */
    public static void main(String[] args) throws UnknownHostException, IOException {
        // TODO Auto-generated method stub
        //表示一个客户端的Socket,制定服务器的位置及端口
        Socket client = new Socket("localhost",8000);;

        //打开输入/输出流
        OutputStream os = client.getOutputStream();
        InputStream is = client.getInputStream();

        //对象序列化
        ObjectOutputStream oos = new ObjectOutputStream(os);

        //发送客户端登陆信息,即向输出流中写入信息
        User user = new User();
        user.setLoginName("lipengfei");
        user.setPwd("123456");
        oos.writeObject(user);
        client.shutdownOutput();

        //表示一个客户端的输入信息
        BufferedReader  buf = new BufferedReader(new InputStreamReader(is));
        String reply = null;
        while((reply = buf.readLine())!=null){ 
            System.out.println("我是客户端,服务器的响应是:"+reply);
        }
        buf.close();
        oos.close();
        is.close();
        os.close();
        client.close();
    }
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
    /**
     * 服务器端
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        // TODO Auto-generated method stub
        //表示一个客户端的Socket(ServerSocket),指定端口并开始监听
        ServerSocket serverSocket = new ServerSocket(8000);

        //使用accept()方法等待客户端连接,每个用户在程序中就是一个Socket
        Socket socket = serverSocket.accept();

        //打开输入/输出流
        InputStream inputStream = socket.getInputStream();
        OutputStream outputStream = socket.getOutputStream();

        //反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

        //从客户端获取信息,即从输入流读取信息
        User user = (User)objectInputStream.readObject();
        if(!(user==null)){
            System.out.println("我是服务器,客户登录信息为:"+user.getLoginName()+","+user.getPwd());
        }

        //给客户端一个响应,即向输出流中写入信息
        String reply = "欢迎你"+user.getLoginName()+",登陆成功!";
        outputStream.write(reply.getBytes());

        //关闭资源
        outputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }

}

  

  这个例子采用一问一答的模式,先启动服务器进入监听状态,等待客户端的连接请求,连接成功后,客户端先“发言”,服务器给予“回应”

  2.实现多客户端用户登录

   一个服务器不可能只针对一个客户端服务,一般是面向很多的客户端同时提供服务,但是单线程实现必然是这样的结果。解决这个问题的办法是采用多线程的方式,可以在"服务器端"创建一个专门负责监听的应用主服务程序、一个专门负责响应的线程程序。这样就可以利用多线程处理多个请求。

  

package cn.yu.SocketDemo;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
 * 线程类
 */
public class LoginThread extends Thread{
    Socket socket = null;
    //每启动一个线程,连接对应的Socket
    public LoginThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            //打开输入/输出流
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            //反序列化
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

            //从客户端获取信息,即从输入流读取信息
            User user = (User)objectInputStream.readObject();
            if(!(user==null)){
                System.out.println("我是服务器,客户登录信息为:"+user.getLoginName()+","+user.getPwd());
            }

            //给客户端一个响应,即向输出流中写入信息
            String reply = "欢迎你"+user.getLoginName()+",登陆成功!";
            outputStream.write(reply.getBytes());

            //关闭资源
            outputStream.close();
            inputStream.close();
            socket.close();
            //serverSocket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
    /**
     * 服务器端
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        // TODO Auto-generated method stub
        //表示一个客户端的Socket(ServerSocket),指定端口并开始监听
        ServerSocket serverSocket = new ServerSocket(8000);

        //使用accept()方法等待客户端连接,每个用户在程序中就是一个Socket
        Socket socket = null;

        //监听一直进行中
        while(true){
            socket = serverSocket.accept();
            LoginThread loginThread = new LoginThread(socket);
            loginThread.start();
        }
    }
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
import java.util.*;
public class TCPClient {
    /**
     * 客户端
     * @param args
     * @throws IOException
     * @throws UnknownHostException
     */
    public static void main(String[] args) throws UnknownHostException, IOException {
        // TODO Auto-generated method stub
        for (int i = 0; i < 5; i++) {
            //表示一个客户端的Socket,指定服务器的位置及端口
            Socket client = new Socket("localhost",8000);

            //打开输入/输出流
            OutputStream os = client.getOutputStream();
            InputStream is = client.getInputStream();

            //对象序列化
            ObjectOutputStream oos = new ObjectOutputStream(os);

            //发送客户端登陆信息,即向输出流中写入信息
            User user = new User();
            user.setLoginName("lipengfei"+i);
            String pwdRandom = "@yu"+(new Random().nextInt(100)+1);
            user.setPwd(pwdRandom);
            oos.writeObject(user);
            client.shutdownOutput();

            //接收服务器端的响应,即从输入流中读取数据
            BufferedReader  buf = new BufferedReader(new InputStreamReader(is));
            String reply = null;
            while((reply = buf.readLine())!=null){
                System.out.println("我是客户端,服务器的响应是:"+reply);
            }
            //关闭资源
            buf.close();
            oos.close();
            is.close();
            os.close();
            client.close();
        }
    }
}

  

  3.InetAddress类

   java.net包中的InetAddress类用于封装IP和DNS。要创建InetAddress类的实例,可以使用工厂方法,因为此类没有可用的构造方法

方法名 说明
static InetAddress getLocalHost() 返回表示本地主机的InetAddress对象
static InetAddress getByName(String hostName) 返回指定主机名为hostName的InetAddress对象
static InetAddress[]  getAllByName(String hostName) 返回指定主机名为hostName的所有可能的InetAddress对象组

  如果找不到主机,两种方法都将抛出UnknowHostNameException异常。

package cn.yu.SocketDemo;
import java.net.InetAddress;
import java.net.UnknownHostException;
/*
 * 输出本地主机的地址
 */
public class InetAddressTest {
    public static void main(String[] args) {
        try{
            InetAddress inetAddress = InetAddress.getLocalHost();
            System.out.println("本地主机的地址是:"+inetAddress);   //输出结果:域名/IP
        }catch (UnknownHostException e){
            e.printStackTrace();
        }
    }
}

  

四、基于UDP协议的Socket编程

  基于TCP协议的通信是安全的,是双向的,如同拨打10086服务电话,需要现有服务端,建立双向连接后,才开始数据通信,而UDP的网络通信就不一样了,只需要指明对方地址,然后将数据送出去,并不会事先连接。这样的网络通信是不安全的,所以应用在如聊天系统、咨询系统等场合下。

  先了解下"数据报",是表示通信的一种报文类型,使用数据报进行通信时无须事先建立连接,它是基于UDP协议进行的

  Java中有2个类可以使用数据报实现通信的类,即DatagramPacket和DatagramSocket类。DatagramPacket起到数据容器的作用,DatagramSocket用于发送或接收DataPacket

  DatagramPacket不提供发送或接收数据的方法,而DatagramSocket类提供send()和recieve()方法,用于通过套接字发送和接收数据报。

  1. DatagramPacket类

  • 构造方法

  客户端向外发送数据,必须首先创建一个DatagramPacket对象,在使用DatagramSocket对象发送。

构造方法 说明
DatagramPacket(byte[] data,int size) 构造DatagramPacket对象,封装长度为size的数据包
DatagramPacket(byte[] buf,int length,InetAddress address,int port) 构造DatagramPacket对象,封装长度为length的数据包并发送到指定的主机,端口号
  • 常用方法
方法 说明
byte[] getData() 返回字节数组,该数组包含接收到或要发送的数据报中的数据
int  getLength() 返回发送或接收到的数据的长度
InetAddress  getAddress() 返回发送或接收此数据报的主机的IP地址
int  getPort() 返回发送或接收此数据报的主机的端口号


  2. DatagramSocket类

  • 构造方法

  DatagramSocket类不维护连接状态,不产生输入/输出数据流,它的唯一作用就是接收和发送DatagramPacket对象封装好的数据报。

构造方法 说明
DatagramSocket() 创建一个DatagramSocket对象,并将其与本机地址上任何可用的端口绑定
DatagramSocket(int  port) 创建一个DatagramSocket对象,并将其与本机地址上指定的端口绑定
  • 常用方法
方法 说明
void  connect(InetAddress address,int  port) 将当前DatagramSocket对象连接到远程地址的指定端口
void close() 关闭当前DatagramSocket对象
void  disconnect() 断开当前DatagramSocket对象的连接
int  getLocalPort() 返回当前DatagramSocket对象绑定的本地主机的端口号
void  send(DatagramPacket p) 发送指定的数据报
void  receive(DatagramPacket  p) 接收数据报。收到数据报以后,存放在指定的DatagramPacket对象中

  3. 使用Socket编程实现客户咨询  

  利用UDP通信的两个端点是平等的,也就是说通信的两个程序关系是对等的,没有主次之分,甚至他们的代码都可以完全一样,这一点要与基于TCP协议的Socket程序区分开来。

  基于UDP协议的Socket网络编程一般可以分为4步:

  • 利用DatagramPacket对象封装数据包;
  • 利用Datagramsocket对象发送数据包;
  • 利用DatagramSocket对象接收数据包;
  • 利用DatagramPacket对象处理数据包;
package cn.yu.SocketDemo;
import java.io.IOException;
import java.net.*;
public class Send {

    public static void main(String[] args) {
        InetAddress ia=null;
        DatagramSocket ds=null;
        try{
            String mess="你好,我想咨询一个问题。";
            //显示与本地对话框
            System.out.println("我  说:"+mess);

            //获取本地主机地址
            ia=InetAddress.getByName("localhost");

            //创建DatagramPacket对象,封装数据
            DatagramPacket dp=new DatagramPacket(mess.getBytes(),mess.getBytes().length ,ia,8800);

            //创建DatagramSocket对象,向服务器发送数据
            ds=new DatagramSocket();
            ds.send(dp);

            byte[] buf=new byte[1024];
            DatagramPacket dpre=new DatagramPacket(buf,buf.length);
            //创建DatagramSocket对象,接收数据
            //ds=new DatagramSocket(8800);
            ds.receive(dpre);   //接收数据报,存放在指定的DatagramPacket对象中

            //显示接收到的信息
            String reply=new String(dpre.getData(),0,dpre.getLength());
            System.out.println(dpre.getAddress().getHostAddress()+"说:"+reply);

        }catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //关闭DatagramSocket对象
            ds.close();
        }
    }
}
package cn.yu.SocketDemo;
import java.io.IOException;
import java.net.*;
public class Receive {
    public static void main(String[] args) {
        DatagramPacket dp=null;
        DatagramSocket ds=null;
        DatagramPacket dpto=null;
        try{
            //创建DatagramPacket对象,用来准备接收数据包
            byte[] buf=new byte[1024];
            dp=new DatagramPacket(buf,buf.length);
            //创建DatagramSocket对象,接收数据
            ds=new DatagramSocket(8800);
            ds.receive(dp);

            //显示接收到的信息
            String mess=new String(dp.getData(),0,dp.getLength());
            System.out.println(dp.getAddress().getHostAddress()+"说:"+mess);

            String reply="你好,我在,请咨询!";
            //显示与本地对话框
            System.out.println("我  说:"+reply);

            //创建DatagramPacket对象,封装数据
            SocketAddress sa=dp.getSocketAddress();
            dpto=new DatagramPacket(reply.getBytes(),reply.getBytes().length ,sa);
            ds.send(dpto);

        }catch (IOException e) {
            e.printStackTrace();
        }finally{
            ds.close();
        }
    }
}

  

五、两种通信方式比较

 

  

原文地址:https://www.cnblogs.com/yutianbao/p/10713828.html