一、UDP
聊天室
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束。
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收。
(1)一个人发送多次数据
发送端代码:
public class sendMessage {public static void main(String[] args) throws IOException {//1.找到快递公司DatagramSocket ds = new DatagramSocket();//2.封装数据//键盘录入数据Scanner sc = new Scanner(System.in);while (true) {String s = sc.next();if (s.equals("886")) {break;}byte[] bytes = s.getBytes();InetAddress ip = InetAddress.getByName("127.0.0.1");int port = 10086;DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ip, port);//3.发送数据ds.send(dp);}//4.释放资源ds.close();}
}
接收端代码:
public class receiveMessage {public static void main(String[] args) throws IOException {//一定要绑定端口号//表示从哪个端口接收数据DatagramSocket ds = new DatagramSocket(10086);//创建一个包用来接收数据byte[] bytes = new byte[1024];DatagramPacket dp = new DatagramPacket(bytes, bytes.length);while (true) {//进行接收ds.receive(dp);//解析数据byte[] data = dp.getData();String str = new String(data);InetAddress address = dp.getAddress();int port = dp.getPort();System.out.println("接收到的数据为:" + str);System.out.println("由ip为" + address + ",端口号为" + port + "进行发送");}}
}
多个发送端发送数据: Edit Configurations --> Modify options -->Allow multiple instance,就可以一次运行多个发送端程序。
二、TCP
1、多发多收
客户端:多次发送数据
服务器:接收多次接收数据,并打印
(1)客户端代码:
public class Client {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1", 10086);OutputStream is = socket.getOutputStream();Scanner sc = new Scanner(System.in);while (true) {System.out.println("请输入信息:");String s = sc.next();is.write(s.getBytes());if (s.equals("886")) {break;}}socket.close();}
}
(2)服务器端代码:
public class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(10086);Socket socket = ss.accept();BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));String str;while ((str = br.readLine()) != null) {System.out.println(str);}socket.close();ss.close();}
}
①关于服务端这段代码,使用缓冲字符流读取,客户端循环输入数据,但是只有在客户端发送886结束循环之后服务端才能接收到数据。
运行结果:
不建议使用缓冲流的readLine方法,如果使用需要特意在后面加换行符才可以否则也会阻塞?不知道。
②但是如果使用字符流读取的话,就是一次输入结束之后就会进行读取。
2 、接收和反馈
客户端发送一条数据,服务器接收数据并打印,再给客户端回复消息,客户端接收服务端回复的消息并打印。
Tips:当客户端数据输入结束但并没有关闭连接时,其实服务端还一直在等待客户端的数据,就是阻塞在读取数据上;但是客户端关闭数据连接的通道也作为一种结束标记,所以服务端知道客户端数据发完了,就不会阻塞在读取数据上了。
总结:
在数据传输时,服务端会阻塞在读取数据上,只有看到结束标记时跳出(就是你客户端发完了,你要让我服务端知道你发完了啊)。客户端关闭数据连接的通道和socket.shutdownOutput()都作为一种结束标记。
服务端代码:
(1)当向客户端回复时,通过通道对象socket获取输出流。
public class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(10086);Socket socket = ss.accept();InputStreamReader isr = new InputStreamReader(socket.getInputStream());int b;while ((b = isr.read()) !=-1) {System.out.print((char)b);}OutputStream os = socket.getOutputStream();String s = "me too";os.write(s.getBytes());socket.close();ss.close();}
}
客户端代码:
(1)当客户端数据发送结束之后,但是又不能关闭连接时,可以有一个结束标志来告诉服务端我的数据已经发送完毕了,通过socket.shutdownOutput();。
(2)当客户端接收来自服务端的回复时,通过通道对象socket获取输入流。
public class Client {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1", 10086);OutputStream is = socket.getOutputStream();Scanner sc = new Scanner(System.in);while (true) {System.out.println("请输入信息:");String s = sc.next();is.write(s.getBytes());if (s.equals("886")) {break;}}//关闭输入流,表示数据已经传输完毕socket.shutdownOutput();InputStreamReader isr = new InputStreamReader(socket.getInputStream());int b;while ((b = isr.read()) !=-1) {System.out.print((char)b);}socket.close();}
}
3、上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
(1)客户端代码:
public class Client {public static void main(String[] args) throws IOException {//客户端连接Socket socket = new Socket("127.0.0.1", 10086);//输出数据OutputStream os = socket.getOutputStream();//读取文件FileInputStream fis = new FileInputStream("name_10.txt");int b1;while ((b1 = fis.read()) != -1) {os.write(b1);}fis.close();//给出数据输入结束标记socket.shutdownOutput();//接收服务端的回复InputStreamReader isr = new InputStreamReader(socket.getInputStream());int b2;while ((b2 = isr.read()) !=-1) {System.out.print((char)b2);}//关闭连接socket.close();}
}
(2)服务端代码:
public class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(10086);Socket socket = ss.accept();InputStream is = socket.getInputStream();FileOutputStream fos = new FileOutputStream("name_copy.txt");int b;while ((b = is.read()) != -1) {fos.write(b);}OutputStream os = socket.getOutputStream();os.write("收到".getBytes());socket.close();ss.close();}
}
4、文件名重复
在3的代码中,上传到服务器的文件名字都是相同的,这样很不方便啊。
在Java中有一个UUID类,提供了一个静态方法randomUUID(),输出是以下这种格式的数据。大概明白了为什么下载文件时名字总是一些类似的数字什么的。
对3的代码进行修改:
5 、上传文件(多线程版)
想要服务器不停止,能接收很多用户上传的图片,
该怎么做呢?
提示:可以用循环或当多线程。但是循环不合理,最优解法是(循环+多线程)改写。
首先是循环的写法:
但是这种情况下很容易造成一个用户的文件没上传完毕,下一个用户就来了,容易错过第二个用户的上传需求,有问题。
那么如何解决呢?可以使用线程。
每一个用户的需求都使用一个单独的线程进行解决。
线程的代码如下:
public class MyThread extends Thread{private Socket socket;public MyThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();String name = UUID.randomUUID().toString().replace("-", "");FileOutputStream fos = new FileOutputStream(name + ".txt");int b;while ((b = is.read()) != -1) {fos.write(b);}OutputStream os = socket.getOutputStream();os.write("收到".getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {if (socket != null) {try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
}
服务端的代码如下:
当服务器监测到有用户建立连接时,用创建一个线程去处理它的需求。
public class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(10086);while (true) {Socket socket = ss.accept();new MyThread(socket).start();}//ss.close();}
}
6、上传文件(线程池版)
代码如下:
7、BS(接收浏览器的消息并打印)
客户端:不需要写
服务器:接收数据并打印。
在浏览器的网址中输入127.0.0.1:10086
输出结果如下: