Java入门系列-24-实现网络通信

互联网上那么多设备,java 是如何与其他设备通信的呢?这次的内容是网络通信的基础,有了它咱们才能上网页、玩游戏、视频聊天。

Socket 客户端套接字

Socket 客户端套接字,用于连接互联网提供服务的设备。

Socket 构造方法

构造方法 说明
Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号

常用方法

方法名称 说明
getOutputStream() 返回此套接字的输出流
getInputStream() 返回此套接字的输入流

下面示例模拟了一个 HTTP 请求

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class TestSocket {

    public static void main(String[] args) {
        
        //创建套接字
        try(Socket s=new Socket("www.baidu.com", 80);){
            //创建向服务器发送数据的输出流
            OutputStream os=s.getOutputStream();
            StringBuffer sb=new StringBuffer();
            //HTTP协议 请求报文
            sb.append("GET / HTTP/1.1\r\n");
            sb.append("Host: www.baidu.com:80\r\n");
            sb.append("Connection: Keep-Alive\r\n");
            //这里一定要一个回车换行,表示消息头完,不然服务器会等待
            sb.append("\r\n");
            //发送
            os.write(sb.toString().getBytes());
            //获取服务器相应内容 
            InputStream is=s.getInputStream();
            //通过输入流创建扫描器,并指定编码为utf-8防止中文乱码
            Scanner scanner=new Scanner(is,"utf-8");
            
            while(scanner.hasNextLine()) {
                String line=scanner.nextLine();
                System.out.println(line);
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
            System.out.println("未知主机");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}

ServerSocket

ServerSocket:实现服务器套接字,服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TestServerSocket {

    public static void main(String[] args) {
        //创建 ServerSocket 监听1666端口
        try(ServerSocket server=new ServerSocket(1666)){
            //阻塞方法,当有客户端连入,获取客户端Socket
            try(Socket client=server.accept()){
                //获取客户端发送的数据
                InputStream is=client.getInputStream();
                //获取向客户端发送数据的流
                OutputStream os=client.getOutputStream();
                //通过输入流创建扫描器
                try(Scanner scanner=new Scanner(is)){
                    
                    PrintWriter pw=new PrintWriter(os,true/*自动刷新*/);
                    //向客户端发送消息
                    pw.println("Hello,enter bye to exit.");
                    boolean done=false;
                    //客户端有输入数据并且没有发送 bye 
                    while(!done&&scanner.hasNextLine()) {
                        //接收客户端发送的数据
                        String line=scanner.nextLine();
                        //将客户端发送的数据发回客户端
                        pw.println("Echo:"+line);
                        //如果客户端输入bye 结束通信
                        if(line.trim().equalsIgnoreCase("bye")) {done=true;}
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试方式:
在DOS 中输入命令:telnet 127.0.0.1 1666

telnet 不是内部或外部命令的读者,需要在 Windows 功能中启用 Telnet 客户端。

上面的代码如果有多个客户端连入就不行了,如果希望服务能被多个客户端连接,可以使用线程。

多线程服务器

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TestMultiServerSocket {

    public static void main(String[] args) {
        //创建 ServerSocket 监听 1666端口
        try(ServerSocket server=new ServerSocket(1666)){
            while(true) {
                //accept() 是一个阻塞方法
                Socket client=server.accept();
                InputStream is=client.getInputStream();
                OutputStream os=client.getOutputStream();
                //开启新的线程处理,传入当前客户端
                Thread t=new Thread(new ThreadEchoHandler(client));
                t.start();              
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ThreadEchoHandler implements Runnable{
    private Socket socket=null;

    public ThreadEchoHandler(Socket socket) {
        this.socket=socket;
    }
    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            OutputStream os=socket.getOutputStream();
            try(Scanner scanner=new Scanner(is)){
                PrintWriter pw=new PrintWriter(os,true);
                pw.println("Hello,enter bye to exit.");
                boolean done=false;
                while(!done&&scanner.hasNextLine()) {
                    String line=scanner.nextLine();
                    pw.println("Echo:"+line);
                    if(line.trim().equalsIgnoreCase("bye")) {done=true;}
                }
            }
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

URLConnection

抽象类 URLConnection 是所有类的超类,它代表应用程序和 URL 之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源。

Socket 可以默认任意类型的网络通信,URLConnection 更适合 HTTP 请求,使用 URLConnection 进行HTTP操作更方便,模拟请求报文,获取响应报文和内容。

URLConnection 常用方法

方法 说明
connect() 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)
getContentEncoding() 返回 content-encoding 头字段的值
getContentType() 返回 content-type 头字段的值
getHeaderFields() 返回头字段的不可修改的 Map
getInputStream() 返回从此打开的连接读取的输入流
setRequestProperty(String key, String value) 设置一般请求属性

获取 URLConnection 需要先创建 URL 对象:
URL url=new URL(host);

使用 URLConnection 获取网页的内容

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

public class TestURLConnection {
    public static void main(String[] args) {
        
        try {
            //创建URL对象
            URL url=new URL("http://www.baidu.com");
            //创建 URLConnection对象
            URLConnection connection=url.openConnection();
            //设置请求属性
            //connection.setRequestProperty("", "");
            //连接
            connection.connect();
            //获取输入流
            InputStream is=connection.getInputStream();
            //通过输入流构建一个扫描器
            Scanner scanner=new Scanner(is,"utf-8");
            while(scanner.hasNextLine()) {
                String line=scanner.nextLine();
                System.out.println(line);
            }
            System.out.println("===响应头===");
            Map<String,List<String>> headers=connection.getHeaderFields();
            for (Entry<String, List<String>> entry: headers.entrySet()) {
                String key=entry.getKey();
                System.out.print(key+":");
                for (String string : entry.getValue()) {
                    System.out.print(string);
                }
                System.out.println();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}