欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Socket编程快速入门实战

程序员文章站 2022-05-28 18:50:02
...

1. Socket编程(网络编程)

在Java中,Socket为java.net包下的一个类;

中文翻译为套接字套接字使用TCP提供了两台计算机之间的通信机制;客户端创建一个套接字,并尝试连接服务器的套接字;

表示为网络中两个机器的对接

Socket编程快速入门实战

连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流;

该通信是一个全双工通信方式,即TCP是一个双向的通信协议,意思就是可以双向同时读写数据(读的同时也能写);

具体步骤

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信;
  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待(阻塞方法),直到客户端连接到服务器上给定的端口;
  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket;

1. ServerSocket

这是java.net包下的一个类;

1. 构造方法

它有如下几个构造方法:

  • public ServerSocket(int port) throws IOException
    • 创建绑定到特定端口的服务器套接字 ,一个端口代表着一个唯一对应的进程;
  • public ServerSocket(int port, int backlog) throws IOException
    • backlog就是代表客户端的个数,这个参数就是用来限制客户端的连接最大数量;
  • public ServerSocket(int port, int backlog, InetAddress address) throws IOException
    • 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
      前面两个构造方法没有传入IP地址,因为上面两个构造方法用了默认的IP地址(即localhost:127.0.0)
      那为什么还需要传入指定的IP地址呢?因为有的性能良好的服务器上有多个网卡(一个网卡对应一个MAC地址),所以对应多个IP地址,所以得指定哪一个IP地址来创建服务器;
      就我们的笔记本就有两个网卡(一个有线网卡和一个无线网卡);
  • public ServerSocket() throws IOException
    • 创建非绑定服务器套接字;

服务端socket处理客户端socket连接是需要一定时间的。ServerSocket有一个队列,存放还没有来得及处理的客户端Socket,这个队列的容量就是backlog的含义。如果队列已经被客户端socket占满了,如果还有新的连接过来 ,那么ServerSocket会拒绝新的连接。也就是说backlog提供了容量限制功能,避免太多的客户端socket占用太多服务器资源;

2. accept方法

Serversoket的一个最常用的方法就是accept了;

public Socket accept() throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!isBound())
        throw new SocketException("Socket is not bound yet");
    Socket s = new Socket((SocketImpl) null);
    implAccept(s);
    return s;
}

可以看到,这个方法的返回值是一个Socket对象,这个对象正是与当前连接到我这个服务端的客户端Socket对等的一个Socket,不要搞混了,这个Socket不是连接到这的客户端的Socket,而是新建的一个对等的Socket,⭐这也体现了我开始画的图中:客户端和服务端各持一个Socket;

2. Socket

这也是java.net包下的一个类;

1. 构造方法

  • public Socket(String host, int port) throws UnknownHostException, IOException

    • 创建一个流套接字并将其连接到指定主机上的指定端口号。
  • public Socket(InetAddress host, int port) throws IOException

    • 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  • public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.

    • 创建一个套接字并将其连接到指定远程主机上的指定远程端口。
      并指定自己这个Socket使用本地的哪一个IP地址和端口号,如果指定的本地端口号被占用,就会抛出异常,而上面的是随机选择本地的端口号(0~65535),如果被占用则再选择直到找到可用的端口号为止;
      一般用不到这个方法,因为没必须要指定本地哪个端口号来进行连接;
  • public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.

    • 创建一个套接字并将其连接到指定远程地址上的指定远程端口。
  • public Socket()

    • 通过系统默认类型的 SocketImpl 创建未连接套接字

2. 常用方法

  • public void connect(SocketAddress endpoint) throws IOException
    

    将此套接字连接到指定服务器,他还有一个重载方法,还包含了一个时间参数,用来指定一个超时值;

  • public InetAddress getInetAddress() 
    

    返回套接字连接的地址;(若还没连接则返回空)

  • public SocketAddress getRemoteSocketAddress()
    

    返回套接字连接的端点地址;

  • public InputStream getInputStream() throws IOException 
    

    返回套接字的输入流;

  • public OutputStream getOutputStream() throws IOException
    

    返回套接字的输出流;⭐

  • public synchronized void close() throws IOException
    

    关闭此套接字;

2. 单线程的聊天室

1. 代码

创建服务端:

public class Server {
    public static void main(String[] args) throws Exception{
        try {
            //没有指定IP地址,默认在本地(127.0.0.0)
            ServerSocket serverSocket = new ServerSocket(8000);
            System.out.println("等待客户端的连接......");
            //这是一个阻塞操作,没有连接会一直阻塞在这;
            Socket socket = serverSocket.accept();
            System.out.println("有客户连接上啦,信息如下:");
            System.out.println(socket.getInetAddress()+"  " + socket.getLocalPort());

            //获取客户端的输入流,相当于服务端从这读取客户端发过来的信息
            Scanner scanner = new Scanner(socket.getInputStream());
            //scanner.useDelimiter("\n");
            //获取客户端的输出流,相当于服务端从这向客户端发送的信息
            PrintStream printStream = new PrintStream(socket.getOutputStream(), true);
            printStream.print("hello, i am Server!!!");
            if(scanner.hasNext()) {
                System.out.println(scanner.next());
            }

            //关闭资源
            scanner.close();
            //printWriter.close();
            serverSocket.close();
        }catch (Exception e) {
            System.out.println("服务端出现异常......");
        }

    }
}

创建客户端:

public class Client {
    public static void main(String[] args) throws Exception{
        Socket socket = new Socket("127.0.0.1", 8000);
        System.out.println("连接上服务器了,服务器地址为:"+socket.getInetAddress());
        PrintStream printStream = new PrintStream(socket.getOutputStream(), true);
        printStream.print("你好呀服务器,我是客户端!!!");
        //获取输入流,即读取服务器的输入

        Scanner scanner = new Scanner(socket.getInputStream());
        if(scanner.hasNext()) {
            System.out.println("服务器说:" + scanner.next());
        }
    }
}

先运行服务端: (可以看到阻塞在这)
等待客户端的连接…

再运行客户端:
连接上服务器了,服务器地址为:/127.0.0.1
服务器说:hello,

Process finished with exit code 0

再看服务端:
等待客户端的连接…
有客户连接上啦,信息如下:
/127.0.0.1 8000
你好呀服务器,我是客户端!!!

Process finished with exit code 0

2. 注意点

  • 在客户端和服务端中,我们使用的打印流用的是字节打印流PrintStream,不是字符打印流PrintWriter,我刚开始就是用的PrintWriter,但是发现不能使用它(运行会阻塞在获取流那里,一直卡在那里),**我分析原因的是: **
    • 我们一端使用PrintWriter进行传送给另一端信息,而另一端使用Scanner接收信息,Scanner中需要传入一个输入流,而这个输入流是InputStream,是个字节输入流,与上面的字符流并不对等,所以不行;
  • 一再强调,客户端的socket和服务端通过accept得到的socket是对等的,但不是同一个对象;