Back

网络编程基础入门

看了狂神说 Java 的网络编程课程后进行总结

网络编程

看了狂神说Java的网络编程课程后进行总结。 但是关于 网络编程的知识不止这些,各种I/O的深入了解,不同需要的具体实现等等都需要后续的深入学习。

基本概念

  1. 基于TCP协议实现C/S架构的通信。(打电话)
  2. 基于UDP协议实现B/S架构的通信。(发短信)

**计算机网络:**把地理位置不同的多台设备,通过通信协议实现资源共享。

目的:数据交换、通信

需要解决的问题

  1. 如何定位电脑主机? —IP+port
  2. 如何传输数据 ? — socket + I/O 流+ 通信协议

下面就基于这两个问题进行学习。


网络通信的要素

通过分析:

  1. 需要知道通信双方的IP地址以及提供通信的端口
  2. 需要明确通信协议 (TCP、UDP)

我们选择的编程语言为Java,需要理解 **Java“万物皆对象”**的特征。

IP

Java 提供了 一个 InetAddress类,表示Internet协议(IP)地址。

从官方文档中,InetAddress类并没有构造器,因此无法使用new 创建对象。(但是又静态方法。)

以下为 构造InetAddress对象以及其常用方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class IpTest {
    public static void main(String[] args) throws UnknownHostException {
		    // 使用静态方法  getByName() 构造对象,参数为 域名 或 IP地址
        InetAddress inetaddress1 = InetAddress.getByName("www.baidu.com");
        System.out.println(inetaddress1);
        //打印主机地址
        System.out.println(inetaddress1.getHostAddress());
        //获取主机名
        System.out.println(inetaddress1.getHostName());

    }
 

port

端口可以认为是设备与外界通讯交流的出口。也表示进程占用。

大致分类:

  1. 0-1023 公有端口。 如:
    1. HTTP 80
    2. HTTPS 443
    3. FTP 21
    4. Telent 23
    5. SSH 22
  2. 1024-19151 程序
  3. 19152-65535 动态私有

查看相关端口的命令:

1
2
3
4
//查看端口占用
netsata -ano |find "xxx"
//任务管理器
tasklist 

通信协议:

为传输层协议,网络编程中使用实时连接TCP和无连接UDP。

详细的就不在此赘述了。

实现TCP实时连接

需要构造客户端、服务端。

涉及到的类:

InetAddress获取IP对象。

socket套接字:双方进行通信的一个抽象端口。

I/O流:用于数据传输。

getBytes() 方法:将字符串转化为Byte序列,供以进行流传输。

客户端

  1. 明确服务端的IP及端口
  2. 创建一个套接字地址
  3. 通过I/O传输数据(byte)
  4. 关闭资源
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//客户端
public class tcpclinet {
    public static void main(String[] args) throws IOException {
        // id
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        //port
        int port = 9999;
        //创建一个scoket地址
        Socket socket = new Socket(inet, port);
        //发送信息,输出流
        OutputStream os = socket.getOutputStream();
        //这是是写出 btye 数据
        os.write("你好".getBytes());
        
        os.close();
    }
}

服务端

  1. 创建serverSocket套接字
  2. 等待客户端连接
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tcpserver {
    public static void main(String[] args) throws IOException {
        // serversocket
        ServerSocket serverSocket = new ServerSocket(9999);
        // 等待连接
        Socket accept = serverSocket.accept();

        System.out.println("连接成功");
        // 读取客户端信息
        InputStream is = accept.getInputStream();
        // 管道流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int len ;
        while((len= is.read(buffer))!= -1 )
        {
            baos.write(buffer,0,len);
        }
        System.out.println(baos.toString());
    }
}

实现TCP 发送文件

客户端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ClientTest {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1",8888);
        System.out.println("客户端启动!准备接受文件!");

        //数据输入流
        DataInputStream dis = new DataInputStream(s.getInputStream());

        //接收文件数量
        int fileCount = dis.readInt();
        System.out.println("需要接收的文件个数:"+fileCount);
        //下载多少个文件
        for (int i = 0; i < fileCount; i++) {
            //名字和长度
            String fileName = dis.readUTF();
            long fileSize = dis.readLong();
            //利用文件输出流,创建文件
            FileOutputStream fos = new FileOutputStream("received_" + fileName);

            byte[] buffer = new byte[4096];
            int bytesRead;
            long totalBytesRead = 0;
            while ((bytesRead = dis.read(buffer)) != -1)
            {
                fos.write(buffer,0,bytesRead);
                totalBytesRead +=bytesRead;
                if(totalBytesRead == fileSize)
                {
                    break;
                }
            }
            System.out.println("已接收文件:"+fileName);
            fos.close();

         }
        dis.close();
        s.close();
    }
}

服务端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ServerTest {
    public static void main(String[] args) throws IOException {
        //开放8888端口
        ServerSocket ss = new ServerSocket(8888);
        System.out.println("服务端等待连接。。。。");

        //客服端连接
        Socket s = ss.accept();
        OutputStream os =  s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(s.getOutputStream());
        System.out.println("连接成功!传输文件");

        //读取本地的多个文件 ,存放在一个数组里。
        File File = new File("./files");
        File[] files = File.listFiles();

        assert files != null;
        int len = files.length;

        //需要先发送文件的信息,文件数量
        dos.writeInt(len);
        dos.flush();

        //发送每个文件
        for (java.io.File file : files) {

            //文件输入流。
            FileInputStream fis = new FileInputStream(file);

            //获取文件的名字和长度
            String fileName = file.getName();
            long fileSize = file.length();

            //把名字和长度传过去
            dos.writeUTF(fileName);
            dos.writeLong(fileSize);
            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) != -1) {
                dos.write(buffer, 0, bytesRead);
            }
            fis.close();

            System.out.println("已发送文件:"+fileName);

        }

        //现在有思路是服务端就这样写,然后客户端创建对应的名字进行存储,应该考虑大小问题。
        dos.close();
        //多个文件传输
				//FileInputStream file = new FileInputStream(files[0]);

    }
}

实现UDP发送信息

UDP 协议则需要使用到 DatagramSocket 套接字 和 DatagramPacket 类。

基本框架是:

  1. 创建DatagramSocket套接字
  2. 创建packet
  3. 发送packet

发送端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Udpclient {
    public static void main(String[] args) throws IOException {
        // socket
        DatagramSocket socket = new DatagramSocket();
        // creat packet
        String msg = "你好啊,服务器!";
        InetAddress serverip = InetAddress.getByName("127.0.0.1");
        int port = 9999;
        //数据 ,发送给谁  5个参数
        //将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中
        DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, serverip, port);

        // 发送包
        socket.send(packet);
        socket.close();
    }
}

接受端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

public class udpserver {
    public static void main(String[] args) throws IOException {
        // 开放端口
        DatagramSocket socket = new DatagramSocket(9999);
        // 接受数据
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
        socket.receive(packet);

        System.out.println(packet.getAddress().getHostAddress());

        System.out.println(new String(packet.getData(), 0, packet.getLength()));
    }
}

至此实现了基础的发送信息。也可以优化成连续不断发送信息直到 ,发送方停止。

只需要把对应的代码放入 while循环中,再通过添加终止条件,达到目的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
ublic class UdpSender {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(6666);
        BufferedReader render = new BufferedReader(new InputStreamReader(System.in));
        while(true)
        {

            String data  = render.readLine();
            //处理字符串封装成包
            byte[] datas = data.getBytes();
            DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress("127.0.0.1",8888));
            socket.send(packet);
            if(data.equals("bye"))
            {
                break;
            }
        }
        //发送的数据,从键盘获取
        socket.close();

    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UdpReceive {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8888);

        while(true)
        {
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
            socket.receive(packet);

            byte[] data = packet.getData();
           
            
            String recevedata = new String(data,0, packet.getLength());
            System.out.println(recevedata);

            if (recevedata.equals("bye"))
            {
                break;
            }
        }
    }
}

在实现该功能时,遇到了一些问题:

Q:在接受端,接受的数据长度很长。

1
2
3
4
5
6
7
//错误代码
byte[] data = packet.getData();
String recevedata = new String(data,0, data.Lenth);

//但是 data.Lenth的值为 1024 (发送端的数据长度),并不是发送过来的实际长度。
//实际长度应为 packet.getLenth()
String recevedata = new String(data,0, packet.getLength());

实现UDP聊天实现

要实现聊天的实现,就得学习Java 多线程,使得双方可以互相发送信息。

需要 将 发送端以及接受端写入线程中。

发送端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class TalkSent implements Runnable{
		// 成员
    DatagramSocket socket =null;
    BufferedReader render = null;
    private int fromprot;
    private String toip;
    private int toport;
		// 初始化进程
    public TalkSent(int fromprot, String toip, int toport) {
        this.fromprot = fromprot;
        this.toip = toip;
        this.toport = toport;
    }
		
		// 子线程运行的内容
    @Override
    public void run() {
        try {
            socket = new DatagramSocket(fromprot);
            render = new BufferedReader(new InputStreamReader(System.in));
            while(true)
            {

                String data  = render.readLine();
                //处理字符串封装成包
                byte[] datas = data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toip,this.toport));
                socket.send(packet);
                if(data.equals("bye"))
                {
                    break;
                }
            }
       
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        socket.close();

    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TalkReceive implements Runnable{
    DatagramSocket socket=null;
    private int fromPort;
    private String msgFrom;
    public TalkReceive(int fromPort,String msgFrom) throws SocketException {
        this.fromPort = fromPort;
        this.msgFrom = msgFrom;
        socket = new DatagramSocket(this.fromPort);
    }

    @Override
    public void run() {
        try {
            while(true)
            {
                byte[] buffer = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
                socket.receive(packet);

                byte[] data = packet.getData();
                // 为什么这里出错了,长度为为 1024 .
                String recevedata = new String(data,0, packet.getLength());
                System.out.println(msgFrom+":"+recevedata);

                if (recevedata.equals("bye"))
                {
                    break;
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

实现了这个对于 通信双方就容易了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class TalkStudent {
    public static void main(String[] args) throws SocketException {
        //开启两个线程
        //发送到目标主机的9999端口
        new Thread(new TalkSent(7777,"localhost",9999)).start();
        //接受来自 8888 端口的信息 。
        new Thread(new TalkReceive(8888,"老师")).start();
    }
}

public class TalkTecher {
    public static void main(String[] args) throws SocketException {
        //发送到目标主机的8888端口
        new Thread(new TalkSent(5555,"127.0.0.1",8888)).start();
        //接收9999端口的信息
        new Thread(new TalkReceive(9999,"学生")).start();
    }
}

URL资源下载

需要用到 URL 类 以及 进行HttpURLconnection 网络连接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class urlTest {
    public static void main(String[] args) throws IOException {
        URL url = new URL("https://img3.doubanio.com/dae/accounts/resources/ded47ae/movie/assets/annual_2023.png");
        // 进行 http连接
        URLConnection connection = url.openConnection();
        //HttpURLconnection urlconnection =(HttpURLconnection)url.openconnection();
        // 输入流
        InputStream is = connection.getInputStream();
        System.out.println(connection);

        // 文件输出流,把文件保留在本地
        FileOutputStream fos = new FileOutputStream("a.png");
        byte[] buffer = new byte[1024];
        int len ;
        while((len=is.read(buffer))!= -1)
        {
            fos.write(buffer,0,len);
        }

        fos.close();
        is.close();
    }
}
author: rose
Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0