Java:网络编程

1、网络编程三要素

  • IP地址
    要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识

  • 端口
    网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识

  • 协议
    通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

  • 端口号

    • 用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
  • 获取当前主机IP

InetAddress address = InetAddress.getByName("localhost");
String hostName = address.getHostName();
System.out.println("主机名为" + hostName);
String ip = address.getHostAddress();
System.out.println("IP为" + ip);

2、UDP通信

  • UDP发送数据

    方法名 说明
    DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口
    DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口
    void send(DatagramPacket p) 发送数据报包
    void close() 关闭数据报套接字
    void receive(DatagramPacket p) 从此套接字接受数据报包
  • UDP接收数据

    方法名 说明
    DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包
    byte[] getData() 返回数据缓冲区
    int getLength() 返回要发送的数据的长度或接收的数据的长度
  • 客户端

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        //创建接收端的Socket对象
        DatagramSocket ds = new DatagramSocket();

        while (true) {
            String s = sc.nextLine();
            byte[] bytes = s.getBytes();
            // 构造数据报套接字并将其绑定到本地主机上的任何可用端口
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10000;

            //调用DatagramSocket对象的方法接收数据
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            ds.send(dp);

            // 结束
            if("404".equals(s)){
                break;
            }
        }
        //关闭此数据报套接字
        ds.close();
    }
}
  • 服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建接收端的Socket对象
        DatagramSocket ds = new DatagramSocket(10000);

        while (true) {
            //创建一个数据包,用于接收数据
            byte [] bytes = new byte[1024];
            //调用DatagramSocket对象的方法接收数据
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
            ds.receive(dp);

            byte[] data = dp.getData();
            int length = dp.getLength();
            String s = new String(data, 0, length);
            System.out.println(s);

             // 结束
            if("404".equals(s)){
                break;
            }
        }
        ds.close();
    }
}

3、UDP三种通讯方式

  • 单播
    单播用于两个主机之间的端对端通信
  • 组播
    组播用于对一组特定的主机进行通信
  • 广播
    广播用于一个主机对整个局域网上所有主机上的数据通信
  • 组播
  // 发送端
  public class ClinetDemo {
      public static void main(String[] args) throws IOException {
          // 1. 创建发送端的Socket对象(DatagramSocket)
          DatagramSocket ds = new DatagramSocket();
          String s = "hello 组播";
          byte[] bytes = s.getBytes();
          InetAddress address = InetAddress.getByName("224.0.1.0");
          int port = 10000;
          // 2. 创建数据,并把数据打包(DatagramPacket)
          DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
          // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
          ds.send(dp);
          // 4. 释放资源
          ds.close();
      }
  }
  // 接收端
  public class ServerDemo {
      public static void main(String[] args) throws IOException {
          // 1. 创建接收端Socket对象(MulticastSocket)
          MulticastSocket ms = new MulticastSocket(10000);
          // 2. 创建一个箱子,用于接收数据
          DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
          // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
          ms.joinGroup(InetAddress.getByName("224.0.1.0"));
          // 4. 将数据接收到箱子中
          ms.receive(dp);
          // 5. 解析数据包,并打印数据
          byte[] data = dp.getData();
          int length = dp.getLength();
          System.out.println(new String(data,0,length));
          // 6. 释放资源
          ms.close();
      }
  }
  • 广播
    // 发送端
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
              // 1. 创建发送端Socket对象(DatagramSocket)
            DatagramSocket ds = new DatagramSocket();
          // 2. 创建存储数据的箱子,将广播地址封装进去
            String s = "广播 hello";
            byte[] bytes = s.getBytes();
            InetAddress address = InetAddress.getByName("255.255.255.255");
            int port = 10000;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
          // 3. 发送数据
            ds.send(dp);
          // 4. 释放资源
            ds.close();
        }
    }
    // 接收端
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 1. 创建接收端的Socket对象(DatagramSocket)
            DatagramSocket ds = new DatagramSocket(10000);
            // 2. 创建一个数据包,用于接收数据
            DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
            // 3. 调用DatagramSocket对象的方法接收数据
            ds.receive(dp);
            // 4. 解析数据包,并把数据在控制台显示
            byte[] data = dp.getData();
            int length = dp.getLength();
            System.out.println(new String(data,0,length));
            // 5. 关闭接收端
            ds.close();
        }
    }
    

4.TCP通信程序

  • TCP发送数据

    方法名 说明
    Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号
    Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
    InputStream getInputStream() 返回此套接字的输入流
    OutputStream getOutputStream() 返回此套接字的输出流
  • TCP接收数据

    方法名 说明
    ServletSocket(int port) 创建绑定到指定端口的服务器套接字
    Socket accept() 监听要连接到此的套接字并接受它
  • 注意事项

    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,是往外写的,所以是输出流
      针对服务器来讲,是往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止

一次TCP通信

  • 客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建socket 绑定IP和端口
        Socket socket = new Socket("127.0.0.1",10000);

        // 创建socket发送流
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        
        //仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
        socket.shutdownOutput();
        
        // 创建socket接收流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        // 关闭资源
        br.close();
        os.close();
        socket.close();
    }
}
  • 服务器
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建socServerSocketket 监听端口
        ServerSocket ss = new ServerSocket(10000);
        //等待客户端连接
        Socket accept = ss.accept();
        // 输入流
        InputStream is = accept.getInputStream();
        int b;
        while((b = is.read())!=-1){
            System.out.println((char) b);
        }
        // 输出流
        System.out.println("看看我执行了吗?");
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("你谁啊?");
        bw.newLine();
        bw.flush();

        // 关闭资源
        bw.close();
        is.close();
        accept.close();
        ss.close();
    }
}

TCP上传文件

方法名 说明
void shutdownInput() 将此套接字的输入流放置在“流的末尾”
void shutdownOutput() 禁止用此套接字的输出流
  • 客户端

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10000);

        //是本地的流,用来读取本地文件的.
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));

        //写到服务器 --- 网络中的流
        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);
        int b;
        while((b = bis.read())!=-1){
            bos.write(b);//通过网络写到服务器中
        }
        bos.flush();
        //给服务器一个结束标记,告诉服务器文件已经传输完毕
        socket.shutdownOutput();

        //接收流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine()) !=null){
            System.out.println(line);
        }

        bis.close();
        socket.close();
    }
}
  • 服务器

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();

        //网络中的流,从客户端读取数据的
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());

        //本地的IO流,把数据写到本地中,实现永久化存储
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.jpg"));
        int b;
        while((b = bis.read()) !=-1){
            bos.write(b);
        }
        //将字节输入流FileInputStream   转成     字符输入流  Fliereader
        //通过转换流
        InputStreamReader inputStreamReader = new InputStreamReader(accept.getInputStream());
        //通过缓冲输入字符流
        BufferedReader br = new BufferedReader(inputStreamReader);


        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(accept.getOutputStream());
        BufferedWriter bw = new BufferedWriter(outputStreamWriter);
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        bos.close();
        accept.close();
        ss.close();
    }
}

TCP服务端优化

  • 异步线程完成socket接收流程

import java.io.*;
import java.net.Socket;
import java.util.UUID;

// 线程任务类
public class ThreadSocket implements Runnable {
    private Socket acceptSocket;

    public ThreadSocket(Socket accept) {
        this.acceptSocket = accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos = null;
        try {
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            bos = new BufferedOutputStream(new FileOutputStream(  UUID.randomUUID().toString() + ".jpg"));

            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  • 线程池控制socket创建

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ServerDemo {
    public static void main(String[] args) throws  IOException {
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,   //线程池的总数量
                60,   //临时线程空闲时间
                TimeUnit.SECONDS, //临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );

        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
        //ss.close();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“即可领取 Jav...
    程序员黄小斜阅读 670评论 0 0
  • 网络编程概述 Java是Internet上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见...
    zqyadam阅读 300评论 0 0
  • 1 网络通信协议   通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一...
    圣堂刺客_x阅读 261评论 0 0
  • 1.UDP 协议: 发送端不会确认接收端是否存在,就会发出数据,同样在接受到数据是也不会反馈。面向无连接协议,资...
    随心者随心行阅读 130评论 0 0
  • 大家好,我是乐字节小乐,上次给大家讲述了Java中的IO流之输出流|乐字节,本文将会给大家讲述网络编程。 主要内容...
    乐字节阅读 319评论 0 0