您现在的位置是:网站首页 > 代码编程 > JAVA开发JAVA开发
【原】Java Socket编写基于UDP协议的简易聊天室
不忘初心 2019-03-29 围观() 评论() 点赞() 【JAVA开发】
简介:写完基于TCP协议的聊天室,再来尝试写一个UDP协议的聊天室,由于协议的不同,所以实现起来也是有很大的不同,先来简单的看一下这两个协议的区别:1、TCP是长连接
写完基于TCP协议的聊天室,再来尝试写一个UDP协议的聊天室,由于协议的不同,所以实现起来也是有很大的不同,先来简单的看一下这两个协议的区别:
1、TCP是长连接,UDP是无连接;
2、TCP能保证数据包的正确性,UDP会有丢包;
3、TCP能保证数据包的顺序性,UDP保证不了;
4、TCP对系统资源需求大,UDP不可靠,所以需求小;
5、TCP由于做了一系列的保证,所以速度慢,UDP无需这些保证,所以速度快,实时性高;
6、TCP会有粘包问题,UDP会有分包问题;
关于分包:
UDP受以太网限制,每一个包的最大体积是65507字节,约等于64K。
为什么最大是65507?
因为UDP包头有2个byte用于记录包体长度,2个byte可表示最大值为: 2^16-1 = 64K-1 = 65535,UDP包头占8字节, IP包头占20字节, 65535-28 = 65507。
言归正传:
就拿第一点来说,实现起来跟TCP已经是天壤之别了,如果说不是长连接,那么我们发送消息的形式就需要变通了,TCP中我们是经过服务器转发,也就是说有一个Server的概念,但是在UDP中,这个概念就不存在了,因为UDP擅长的是点对点,我知道了你的Address,那么我就可以直接给你发送数据,也就不需要Server了。
经过上面分析,UDP似乎不太适合做聊天室,如果非要做的话,那就需要硬生生的拉一个节点出来当做Server,先将消息发送到这个节点,然后再由这个节点进行转发(有点儿类似于消息队列的概念)。
仔细想想,如果说单纯的点对点聊天,也不做消息存储之类的功能,那么UDP看起来确实很别扭,还不如直接客户端发送消息,而如果想要做全面的功能:群聊、消息提醒等等,那还是需要一个Server出来主持大局的。。。
服务端代码:
package com.wolffy.socket.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Felix on 2019/3/26.
*/
public class Server {
/**
* socket套接字
*/
private DatagramSocket socket;
/**
* 服务开启的端口号
*/
private static final int port = 5555;
/**
* 缓冲区大小(UDP受以太网限制,每一个包的最大体积是65507字节,约等于64K),本案例中不会发送很长的内容,所以直接1024就够用了
* <p>
* 为什么最大是65507?
* 因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1 = 64K-1 = 65535
* udp包头占8字节, ip包头占20字节, 65535-28 = 65507
*/
private static final int buf_size = 1024;
/**
* 客户端集合
*/
private static List<String> clients = new ArrayList<>();
private void start() {
try {
// 创建套接字,打开端口
socket = new DatagramSocket(port);
System.out.println("服务开启[" + socket.getLocalSocketAddress() + "],等待客户端连接中...");
// 启动消息监听线程,这个跟TCP有点儿不一样了,这里无需针对每一个client做消息处理,而是点对点的统一处理
new MessageListener().start();
} catch (IOException e) {
// log
}
}
class MessageListener extends Thread {
@Override
public void run() {
try {
DatagramPacket packet;
String msg, client;
// 循环监听消息,后面可以考虑加上flag规避死循环
while (true) {
// 组包
packet = new DatagramPacket(new byte[buf_size], buf_size);
// 接收消息
msg = receiveMsg(packet);
// 解析出client
client = packet.getSocketAddress().toString();
// 不能再用TCP那一套了,只能通过消息来确定上线与否
// 因为UDP发了就不管了,所以默认只要发了消息就一定在线,下线只能通过心跳和主动退出
if ("online".equals(msg)) {
clients.add(client);
System.out.println("客户端[" + client + "]连接成功,当前在线客户端" + clients.size() + "个");
sendMsg(0, "[系统消息]:欢迎" + client + "来到聊天室,当前共有" + clients.size() + "人在聊天", client);
} else {
sendMsg(1, "[" + client + "]:" + msg, client);
}
}
} catch (IOException e) {
// log
}
}
}
/**
* 解析出client的IP
*
* @param address socket地址,如:/127.0.0.1:9524
* @return IP
*/
private String getIp(String address) {
String[] array = address.split(":");
String s = array[0];
return s.substring(1);
}
/**
* 解析出client的端口
*
* @param address socket地址,如:/127.0.0.1:9524
* @return 端口
*/
private int getPort(String address) {
String[] array = address.split(":");
String s = array[1];
return Integer.valueOf(s);
}
/**
* 发送消息
*
* @param type 消息类型(0、系统消息;1、用户消息)
* @param msg 消息内容
* @param client 客户端(用来作比对,是否跳过自己)
* @throws IOException
*/
private void sendMsg(int type, String msg, String client) throws IOException {
if (type != 0) {
System.out.println("处理消息:" + msg);
}
DatagramPacket send_packet;
byte[] bytes;
for (String address : clients) {
if (type != 0 && client.equals(address)) {
continue;
}
// 将需要发送的消息,转换成byte数组
bytes = msg.getBytes();
// 组装发送包,需要指定:包内容、包长度、目标IP、目标端口
send_packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName(getIp(address)), getPort(address));
// 发送
socket.send(send_packet);
}
}
/**
* 接收消息,由于receive是阻塞方法,所以可以直接将packet包当参数传进来
*
* @param packet 接收的数据包
* @return 消息内容
* @throws IOException
*/
private String receiveMsg(DatagramPacket packet) throws IOException {
// 组装接收包,需要指定:包内容、包长度,这里的包内容只是先声明一个空数组,等待数据的填充
// DatagramPacket packet = new DatagramPacket(new byte[buf_size], buf_size);
socket.receive(packet);
// 包内容都是字节数组,需要转换成字符串
return new String(packet.getData(), packet.getOffset(), packet.getLength());
}
public static void main(String[] args) {
new Server().start();
}
}
客户端代码:
package com.wolffy.socket.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Random;
import java.util.Scanner;
/**
* Created by Felix on 2019/3/26.
*/
public class Client {
/**
* socket套接字
*/
private DatagramSocket socket;
/**
* 缓冲区大小(UDP受以太网限制,每一个包的最大体积是65507字节,约等于64K),本案例中不会发送很长的内容,所以直接1024就够用了
* <p>
* 为什么最大是65507?
* 因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1 = 64K-1 = 65535
* udp包头占8字节, ip包头占20字节, 65535-28 = 65507
*/
private static final int buf_size = 1024;
/**
* 服务器的IP
*/
private static final String server_ip = "127.0.0.1";
/**
* 服务器的端口
*/
private static final int server_port = 5555;
private void start() {
try {
// 创建套接字,打开端口
socket = new DatagramSocket(getPort());
System.out.println("连接服务器成功,身份证:[" + socket.getLocalSocketAddress() + "]");
// 由于不是TCP,所以需要手动发送上线的消息通知服务端
sendMsg("online");
// 开启发送消息的线程
new SendMessageListener().start();
// 开启接收消息的线程
new ReceiveMessageListener().start();
} catch (IOException e) {
// log
}
}
/**
* UDP不会自动生成客户端的端口(确切来说,UDP是么有服务端和客户端的区分的),需要自己生成
*
* @return 4位数的端口
*/
private int getPort() {
Random random = new Random();
String s = "";
for (int i = 0; i < 4; i++) {
s += random.nextInt(9) + 1;
}
return Integer.valueOf(s);
}
/**
* 发送消息
*
* @param msg 消息内容
*/
private void sendMsg(String msg) {
try {
// 将需要发送的消息,转换成byte数组
byte[] bytes = msg.getBytes();
// 组装发送包,需要指定:包内容、包长度、目标IP、目标端口
DatagramPacket send_packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName(server_ip), server_port);
// 发送
socket.send(send_packet);
} catch (IOException e) {
// log
}
}
/**
* 接收消息
*
* @return 消息内容
*/
private String receiveMsg() {
try {
// 组装接收包,需要指定:包内容、包长度,这里的包内容只是先声明一个空数组,等待数据的填充
DatagramPacket receive_packet = new DatagramPacket(new byte[buf_size], buf_size);
socket.receive(receive_packet);
// 包内容都是字节数组,需要转换成字符串
return new String(receive_packet.getData(), receive_packet.getOffset(), receive_packet.getLength());
} catch (IOException e) {
// log
}
return null;
}
class SendMessageListener extends Thread {
@Override
public void run() {
// 监听console输入
Scanner scanner = new Scanner(System.in);
while (true) {
sendMsg(scanner.next());
}
}
}
class ReceiveMessageListener extends Thread {
@Override
public void run() {
// 监听消息的接收,死循环
while (true) {
System.out.println(receiveMsg());
}
}
}
public static void main(String[] args) {
new Client().start();
}
}
服务端效果图:
客户端效果图:
效果跟之前TCP实现的聊天室一模一样,至于心跳、主动退出这些功能,就不再重复编写了,实现思路都是一样的!
详细思路,见文章:Java Socket聊天室实现心跳
看完文章,有任何疑问,请加入群聊一起交流!!!
很赞哦! ()
相关文章
- Java Socket编写基于TCP协议的简易聊天室
- IntelliJ IDEA 2024.2发布之后强推新UI,如何恢复老的经典UI界面
- Uninstall hasn't detected folder of intelli] lDEA installation. Probablyuninstall.exe was moved from the installation folder.
- 公众号CPS变现新宠:微赚淘客查券返利机器人,开启智能省钱赚钱新时代
- 高返利优惠券公众号推荐
- 返利公众号可信吗安全吗?返利机器人哪个佣金高?
- 唯品会购物返利公众号大揭秘:哪些真正好用的返利公众号?
- 饿了么优惠券免费领取:哪些公众号值得推荐?
- 哪些有用可靠的微信公众号能领淘宝优惠券?
- 微信返利最高的微信号推荐——让你的购物更加实惠!
标签云
猜你喜欢
- IntelliJ IDEA 2019.2已经可以利用补丁永久破解激活了
- IntelliJ IDEA 2019.3利用补丁永久破解激活教程
- IntelliJ IDEA高版本最灵活的永久破解激活方法(含插件激活,时长你说了算)
- Jetbrains全家桶基于ja-netfilter的最新破解激活详细图文教程
- IntelliJ IDEA 2022.1永久破解激活教程(亲测可用,持续更新)
- 分享几个正版 IntelliJ IDEA 激活码(破解码、注册码),亲测可用,持续更新
- ja-netfilter到底需不需要mymap,2021.3.2版本激活失效?
- 如何激活idea2022.1及以上版本中的插件(亲测可用)
- 【史上最全】IntelliJ IDEA最新2022.1版本安装和激活视频教学(含插件)
- IntelliJ IDEA 2022.2 版本最新2099年永久激活方法,亲测可用,也可以开启新UI了。
站点信息
- 网站程序:spring + freemarker
- 主题模板:《今夕何夕》
- 文章统计:篇文章
- 标签管理:标签云
- 微信公众号:扫描二维码,关注我们