一、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;
}
@Override
public 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
输出结果如下: