Java基础——网络编程(待续)

网络通讯基本三要素:
1、IP地址
2、端口号
3、传输协议(UDP、TCP等)

UDP
将数据及源和目的封装在数据包中,不需要建立连接。每个数据包的大小限制在64K内。因无连接,是不可靠协议。速度快。
应用:聊天工具、在线视频等

TCP
建立连接,形成传输数据的通道。
在连接中进行大数据量传输,通过三次握手完成连接,是可靠协议。效率稍低。
应用:FTP(文件传输协议)

TCP/IP模型:
应用层、传输层、网际层、主机至网络层

Socket编程

Socket是为网络服务提供的一种机制。通信的两端都有Socket。网络通信其实就是Socket之间的通信,数据在两个Socket间通过IO传输。
1、UDP建立Socket
DatagramSocket 发送和接收数据报包的套接字
DatagramPacket 数据报包

发送流程:
(1) 建立UDP的Socket服务。
(2) 将要发送的数据封装到数据包中。
(3) 通过UDP的Socket服务将数据包发送出去。
(4) 关闭Socket服务。

//1.
DatagramSocket ds = new DatagramSocket(8888);
//2.
String str = "UDP传输演示,哥们来了!";
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName(192.168.1.100), 10000);
//3.
ds.send(dp);
//4.
ds.close();

接收流程:
(1) 建立UDP的Socket服务,因为是要接收数据,必须明确一个端口号。
(2) 创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析这些数据。
(3) 使用Socket服务的receive方法将接收的数据存储到数据包中。
(4) 通过数据包的方法解析数据包中的数据。
(5) 关闭资源。

//1.
DatagramSocket ds = new DatagramSocket(10000);
//2.
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//3.
ds.receive(dp);//阻塞式
//4.
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + ":" + port + ":" + text);
//5.
ds.close();

聊天代码(群聊)
需求:编写一个聊天程序。有收数据和发数据的部分,需要同时执行,故要用到多线程。
因为收和发的动作是不一致的,所以要定义两个run方法,而且两个方法要封装到不同的类中。

—Send代码—

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Send implements Runnable {
    private DatagramSocket ds;
    public Send(DatagramSocket ds) {
        this.ds = ds;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            while((line = br.readLine()) != null) {
                if("886".equals(line))
                    break;
                byte[] buf = line.getBytes();
                DatagramPacket dp = new DatagramPacket(buf, line.length(), InetAddress.getByName("196.128.1.255"), 10000);
                ds.send(dp);
            }
        } catch (IOException e) {
            throw new RuntimeException("发送端失败");
        }
    }
}

—Receive代码—

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Receive implements Runnable {
    private DatagramSocket ds;
    public Receive(DatagramSocket ds) {
        this.ds = ds;
    }
    @Override
    public void run() {
        try {
            while(true) {
                byte[] buf = new byte[1024];
                DatagramPacket dp = new DatagramPacket(buf, buf.length);
                ds.receive(dp);
                String ip = dp.getAddress().getHostAddress();
                String text = new String(buf, 0, dp.getLength());
                System.out.println(ip + ":" + text);
                if(text.equals("886")) {
                    System.out.println(ip + "退出聊天室");
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("接受失败");
        }

    }
}

—主代码—

public class SocketChat {
    public static void main(String[] args) {

        DatagramSocket sendSocket;
        DatagramSocket receiveSocket;

        try {
            sendSocket = new DatagramSocket();
            receiveSocket = new DatagramSocket(10000);
            new Thread(new Send(sendSocket)).start();
            new Thread(new Receive(receiveSocket)).start();
        } catch (SocketException e) {
            e.printStackTrace();
        }   
    }
}

2、TCP建立Socket
TCP分客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。

客户端:
在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立socket服务时,就要有服务端存在并连接成功。形成通路后,在该通道进行数据的传输。

步骤:
1、创建TCP客户端Socket服务,并制定要连接的主机和端口。
2、通过getOutputStream()来获取输出字节流。
3、使用输出流,将数据写出。
4、关闭资源。

public class TcpClient {

    public static void main(String[] args) {
        try {
            //1.
            Socket s = new Socket("116.228.249.84", 10002);
            //2.
            OutputStream out = s.getOutputStream();
            //3.
            out.write("TCP 哥们来了!".getBytes());
            //4.
            s.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端:
1、创建服务端Socket服务,通过ServerSocket对象。
2、服务端必须对外提供一个端口,否则客户端无法连接。
3、获取连接过来的客户端对象。通过ServerSocket的accept()完成。
4、通过客户端对象获取Socket流读取客户端发来的信息,并打印在控制台上。
5、关闭资源,关客户端、服务端。

public class TcpServer {

    public static void main(String[] args) {
        try {
            //1.创建服务器端对象,提供一个端口
            ServerSocket ss = new ServerSocket(10002);
            //2.获取连接过来的客户端对象
            Socket s = ss.accept();
            String ip = s.getInetAddress().getHostAddress();
            //3.通过socket对象获取输入流,要读取客户端发来的数据
            InputStream in = s.getInputStream();
            byte[] buf = new byte[1024];
            int len = in.read(buf);
            String text = new String(buf, 0, len);
            System.out.println(ip + ":" + text);
            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端与服务端的交互:

客户端代码

public class TcpClient2 {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("116.228.249.84", 10003);
            OutputStream os = s.getOutputStream();
            os.write("服务端,你好!".getBytes());

            //读取服务端返回的数据
            InputStream is = s.getInputStream();
            byte[] buf = new byte[1024];
            int len = is.read(buf);
            String text = new String(buf, 0, len);
            System.out.println(text);
            s.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

服务端代码

public class TcpServer2 {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10003);
            Socket s = ss.accept();
            String ip = s.getInetAddress().getHostAddress();

            InputStream is = s.getInputStream();
            byte[] buf = new byte[1024];
            int len = is.read(buf);
            String text = new String(buf, 0, len);
            System.out.println(ip + ":" + text);

            OutputStream os = s.getOutputStream();
            os.write("哥们收到!".getBytes());

            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文本转换TCP客户端和服务端
客户端键盘输入文字发送至服务端,服务端打印出客户端的文字,并返回大写文字显示在客户端控制台。
Client端代码

public class TcpClient3 {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("116.228.249.84", 10004);
            //获取键盘录入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //Socket输出流
            //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
            //socket输入流,读取服务端返回的大写数据
            BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));

            String line = null;
            while((line = br.readLine()) != null) {
                if("over".equals(line))
                    break;
                pw.println(line);
                String upperStr = br1.readLine();
                System.out.println(upperStr);
            }

            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Server端代码

public class TcpServer3 {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10004);
            Socket s = ss.accept();

            String ip = s.getInetAddress().getHostAddress();
            System.out.println(ip + "......Connected");

            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));

            PrintWriter pw = new PrintWriter(s.getOutputStream(), true);

            String line = null;
            while((line = br.readLine()) != null) {
                System.out.println(line);
                pw.println(line.toUpperCase());
            }

            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

1、上面练习中之所以客户端结束后, 服务端也随之结束的原因在于: 客户端的socket关闭后, 服务端获取的客户端socket读取流也关闭了, 因此读取不到数据, line = bufIn.readLine()为null, 循环结束,ServerSocket的close方法也就执行关闭了。
2、 上面练习中的客户端和服务端的PrintWriter对象out获取到数据后, 一定要刷新, 否则对方( 服务端或客户端) 就获取不到数据, 程序便无法正常执行。 刷新操作可以通过PrintWriter类的println()方法实现, 也可以通过PrintWriter类的flush()方法实现。 但是, 由于获取数据的方法是BufferedReader对象bufIn的readLine()方法( 阻塞式方法) , 此方法只有遇到“\r\n”标记时, 才认为数据读取完毕, 赋值给String对象line。 所以, 使用PrintWriter类的flush()方法刷新数据时一定要记得追加“\r\n”!

上传文件至服务端(TCP复制文件)
客户端代码

public class TextClient {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("116.228.249.84", 10005);
            BufferedReader br = new BufferedReader(new FileReader("d:\\xxx.txt"));
            PrintWriter pw = new PrintWriter(s.getOutputStream(), true);

            String line = null;
            while((line = br.readLine()) != null) {
                pw.println(line);
            }

            //告诉服务端,客户端写完了
            s.shutdownOutput();

            BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));

            String str = br1.readLine();
            System.out.println(str);

            br.close();
            s.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端代码

public class TextServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10005);
            Socket s = ss.accept();
            System.out.println(s.getInetAddress().getHostAddress() + "...connected");

            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            BufferedWriter bw = new BufferedWriter(new FileWriter("d:\\server.txt"));

            String line = null;
            while((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
            PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
            pw.println("上传成功");

            bw.close();
            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

向服务器上传图片(单用户版本)

客户端:

public class PicClient {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("192.168.1.100", 10007);

            FileInputStream fis = new FileInputStream("e:\\1.jpg");
            OutputStream os = s.getOutputStream();

            byte[] buf = new byte[1024];
            int len = 0;
            while((len = fis.read(buf)) != -1) {
                os.write(buf, 0, len);
            }
            s.shutdownOutput(); //通知服务器数据已经写完

            InputStream is = s.getInputStream();
            byte[] buf1 = new byte[1024];
            int num = is.read(buf1);
            System.out.println(new String(buf1, 0, num));

            fis.close();
            s.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

服务端
局限性:无法并发访问。当A连接上后,B要连接只有等待。因为服务端还没有处理完A的请求,无法循环回来处理B的accept方法。

public class PicServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10007);
            Socket s = ss.accept();

            InputStream is = s.getInputStream();
            FileOutputStream fos = new FileOutputStream("f:\\server.jpg");
            byte[] buf =new byte[1024];
            int len = 0;
            while((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            OutputStream os = s.getOutputStream();
            os.write("上传成功".getBytes());

            s.close();
            ss.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

客户端并发上传图片
服务端:
将每个客户端封装到一个单独的线程,这样就可以处理多个客户端请求。
只要明确了每一个客户端要在服务端执行的代码即可,将该代码存入run()方法。

public class PicServer {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10007);

            while(true) {
                Socket s = ss.accept();

                new Thread(new PicThread(s)).start();
            }

            //ss.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


public class PicThread implements Runnable {
    private Socket s;
    public PicThread(Socket s) {
        this.s = s;
    }
    @Override
    public void run() {
        int count = 0;
        String ip = s.getInetAddress().getHostAddress();
        try {
            System.out.println(ip + "...connected");

            InputStream is = s.getInputStream();

            File file = new File(ip + "(" + count + ")" + ".jpg");
            while(file.exists()) {
                file = new File(ip + "(" + (count++) + ")" + ".jpg");
            }

            FileOutputStream fos = new FileOutputStream("f:\\server.jpg");
            byte[] buf =new byte[1024];
            int len = 0;
            while((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            OutputStream os = s.getOutputStream();
            os.write("上传成功!".getBytes());

            s.close();
        } catch (Exception e) {
            throw new RuntimeException(ip + "上传失败!");
        }
    }

}

客户端:

public class PicClient {

    public static void main(String[] args) {
        try {
            if(args.length != 1) {
                System.out.println("请选择一个jpg格式的图片");
                return;
            }

            File file = new File(args[0]);
            if(!(file.exists() && file.isFile())) {
                System.out.println("该文件有问题,要么不存在,要么不是文件");
                return;
            }
            if(file.getName().endsWith(".jpg")) {
                System.out.println("图片格式错误,请重新选择");
                return;
            }
            if(file.length() > 1024*1024*5) {
                System.out.println("文件过大,没安好心");
                return;
            }

            Socket s = new Socket("116.228.249.84", 10007);

            FileInputStream fis = new FileInputStream(file);
            OutputStream os = s.getOutputStream();

            byte[] buf = new byte[1024];
            int len = 0;
            while((len = fis.read(buf)) != -1) {
                os.write(buf, 0, len);
            }
            s.shutdownOutput(); //通知服务器数据已经写完

            InputStream is = s.getInputStream();
            byte[] buf1 = new byte[1024];
            int num = is.read(buf1);
            System.out.println(new String(buf1, 0, num));

            fis.close();
            s.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

客户端并发登录
客户端:

public class LoginClient {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("192.168.1.254", 10008);
            //读取输入的用户名
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //向socket写入用户名
            PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
            //读取服务端返回的信息
            BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));

            for(int i=0; i<3; i++) { //只能尝试登录3次
                String line = br.readLine();
                if(line == null) //如果停止访问,直接跳出循环
                    break;
                String info = br1.readLine();
                pw.println("info:" + info);
                if(info.contains("欢迎")) //如果登陆成功,直接跳出循环
                    break;
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

服务端:

public class LoginServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(10008);
            while(true) {
                Socket s = ss.accept();
                new Thread(new LoginThread(s)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


public class LoginThread implements Runnable {

    private Socket s;
    public LoginThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip + "...connected");

        try {
            for (int i=0; i<3; i++) { //限制访问3次
                //读取客户端发来的登录名
                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                String name = br.readLine();
                if(name == null) //如果客户端停止访问,结束循环
                    break;

                //读取.txt文件中存储的用户名
                BufferedReader br1 = new BufferedReader(new FileReader("user.txt"));
                //向客户端写出信息
                PrintWriter pw = new PrintWriter(s.getOutputStream(), true);

                String line = null;

                boolean flag = false; 
                while((line = br1.readLine()) != null) {
                    if(line.equals(name)) { //用户名正确
                        flag = true;
                        break;
                    }
                }
                if(flag) { //如果用户登录正确,打印信息并跳出循环
                    System.out.println(name + ",已登录");
                    pw.println("欢迎光临!");
                    break;
                }
            }
            s.close();

        } catch (Exception e) {
            throw new RuntimeException(ip + "校验失败!");
        }

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,泛型是一种强类型机制,它可以让你在编译时检查类型错误,从而提高代码的安全性和可读性。在使用泛型时,我们经常会遇到父类和子类的泛型转换问题。 首先,我们需要明确一点:子类泛型不能转换成父类泛型。这是因为Java中的泛型是不协变的。例如,如果有一个类A和它的子类B,那么List<A>和List<B>之间是不存在继承关系的。 下面我们来看一个例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 } } ``` 在这个例子中,我们定义了Animal类和它的子类Dog。然后我们定义了两个List,分别是List<Animal>和List<Dog>。如果将List<Dog>赋值给List<Animal>,会出现编译错误。这是因为List<Animal>和List<Dog>之间不存在继承关系。 那么,如果我们想要让子类泛型转换成父类泛型,应该怎么办呢?这时我们可以使用通配符来解决问题。通配符可以表示任意类型,包括父类和子类。例如,我们可以将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 下面我们来看一个使用通配符的例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 List<? extends Animal> list3 = new ArrayList<>(); list3 = list2; // 正确 } } ``` 在这个例子中,我们定义了List<? extends Animal>来表示任意继承自Animal的类型。然后我们将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 总结一下,Java中的泛型是不协变的,子类泛型不能转换成父类泛型。如果需要实现子类泛型转换成父类泛型,可以使用通配符来解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值