java编写一个简单api_[Java]Socket API编写一个简单的私聊和群聊

本文介绍了如何使用Java的Socket和ServerSocket实现一个简单的私聊和群聊API。通过定义消息格式为JSON,利用JSON工具进行解析,实现用户、消息对象,并在服务端使用管道类进行读写分离,处理收发消息。客户端则通过发送和接收管道实现消息交互。文章还提及了可能遇到的问题,如线程处理和用户标识,但未涉及消息确认、丢失和持久化机制。
摘要由CSDN通过智能技术生成

介绍

Socket,ServerSocket

Socket就是我们所说的套接字,主要由IP地址和端口来表示,IP即目标服务器的IP地址。ServerSocket主要用在服务端,作用是监听服务器的某一个端口。通过ServerSocket的accept()可以得到一个客户端Socket,再通过输入输出流可以对其进行读写,从而实现服务器和客户端之间的交互。

消息格式

既然这里是需要实现私聊和群聊,自然需要规定数据格式,这里我采用JSON的格式来进行数据传输,这样的目的是可以用JSON解析工具(如FastJSON)方便地对消息进行解析,降低代码编写难度提高效率。

其他的问题

1,因为需要实现收发信息,而且收和发两个动作是无序的,所以需要收和发两个动作需要单独的线程来进行单独处理。

2,实现私聊,服务器需要根据用户的唯一标识ID来转发信息,所以需要对Socket再次封装。

编写

用户对象

主要用来表示用户的基本信息。

/**

* @author yintianhao

* @createTime 2020/6/30 0:16

* @description 用户类

*/

public class User {

private String nickname;

private Integer id;

public User(String nickname, Integer id) {

this.nickname = nickname;

this.id = id;

}

public User(Integer id){

this.id = id;

}

//getter setter 略

}

消息对象

消息对象包括用户发出的正文内容,消息种类(群聊,私聊,初始化消息),消息来源(User对象),消息目的地(User对象)。通过对消息对象和JSON字符串相互转化来实现消息的解析和生成。

/**

* @author yintianhao

* @createTime 2020/6/30 0:15

* @description 消息对象

*/

public class Msg {

//信息内容

private String content;

//信息种类,1群聊,2私聊,3初始化消息

private Integer type;

private User from;

private User to;

public Msg(String content, Integer type, User from, User to){

this.content = content;

this.type = type;

this.from = from;

this.to = to;

}

//getter setter 略

}

服务端编写

管道类

之前说过需要在Socket的基础上进行再次封装,封装成管道类,管道用一个userId来唯一标识,也就是说一个管道可以看成一个用户,在服务器启动后,客户端连接到服务器之后会发送一条初始化信息到服务端,从而在管道内的构造函数以内对初始化信息进行解析,整个管道的初始化到此完成。管道类另外一个作用就是实现读写分离。

/**

* @author yintianhao

* @createTime 2020/6/28 23:42

* @description 管道类,实现读写分离。

*/

public class Channel implements Runnable{

//log

private static Logger logger = Logger.getLogger(Channel.class);

//输入输出流

private DataOutputStream dataOutputStream;

private DataInputStream dataInputStream;

//客户端套接字

private Socket client;

//运行标志

private boolean isRunning;

//用户列表

private CopyOnWriteArrayList all;

private Integer userId;

public Channel(Socket client){

//初始化

logger.info("A client has connected");

this.client = client;

this.isRunning = true;

try {

this.dataInputStream = new DataInputStream(client.getInputStream());

this.dataOutputStream = new DataOutputStream(client.getOutputStream());

}catch (IOException e){

logger.info(e.getMessage());

//异常释放资源

release();

}

//初始化管道id

String initMsg = receive();

logger.info("init message -- "+initMsg);

Msg msg = JSONUtil.getJsonObject(initMsg);

if (msg.getType() == 3) {

this.userId = Integer.parseInt(msg.getContent());

}

}

public void setChannelList(CopyOnWriteArrayList all){

this.all = all;

}

//接收消息

private String receive(){

String msg = "";

try {

msg = dataInputStream.readUTF();

logger.info("received msg -- "+msg);

}catch (IOException e){

logger.info(e.getMessage());

//异常释放资源

release();

}

return msg;

}

//发送单条信息

private void send(String content){

try {

logger.info("server transfer -- "+content);

dataOutputStream.writeUTF(content);

dataOutputStream.flush();

}catch (IOException e){

logger.info(e.getMessage());

//异常释放资源

release();

}

}

//群聊

private void sendOthers(String msg){

logger.info("Send msg to others");

logger.info("Channel list size -- "+all.size());

for (Channel c:all){

if(c!=this){

c.send(msg);

}

}

}

private void sendOne(String content){

logger.info("Send to someone");

//转成json对象

Msg msg = JSON.parseObject(content,Msg.class);

User from = msg.getFrom();

User to = msg.getTo();

logger.info("Message from "+from.getId());

logger.info("Message to "+to.getId());

for (Channel c:all){

try {

if (c.userId.intValue()==to.getId().intValue()){

c.send(content);

break;

}

}catch (NullPointerException e){

logger.error(e.getMessage());

}

}

}

//释放资源

public void release(){

//标志改变

isRunning = false;

//关闭socket 输入 输出流

StreamUtil.close(dataInputStream,dataOutputStream,client);

logger.info("A client has released");

}

@Override

public void run() {

while(isRunning){

String content = receive();

Msg msg = JSON.parseObject(content,Msg.class);

//通过Msg的type来判断是私聊还是群聊

if (msg.getType()==1){

//群聊

msg.setTo(null);

String publicMsg = JSONUtil.getJsonString(msg);

sendOthers(publicMsg);

logger.info("It is a public message");

}else if (msg.getType()==2){

//私聊

sendOne(content);

logger.info("It is a private message");

}else{

logger.info("It is an initial message");

}

}

}

}

启动服务器

/**

* @author yintianhao

* @createTime 2020/6/29 1:16

* @description

*/

public class Server {

//logger

private static Logger logger = Logger.getLogger(Server.class);

public static void main(String[] args){

logger.info("Server has started");

try {

ServerSocket server = new ServerSocket(8888);

//创建容器

CopyOnWriteArrayList channelList = new CopyOnWriteArrayList<>();

while(true){

//客户端套接字

Socket client = server.accept();

//新建一个管道

Channel channel = new Channel(client);

//加入管道列表

channelList.add(channel);

//管道设置自己的管道列表

channel.setChannelList(channelList);

//开启线程服务一个管道

Thread thread = new Thread(channel);

thread.start();

}

} catch (IOException e) {

logger.error(e.getMessage());

}

}

}

客户端编写

客户端的管道有两种,一种是发送管道,一种是接收管道。作用跟服务器的管道类似。由于这个demo是基于控制台的,这里用户的ID和需要发送的消息类型都是根据控制台输入的。

发送管道

/**

* @author yintianhao

* @createTime 2020/6/29 1:31

* @description 发送

*/

public class SendChannel implements Runnable{

//控制台输入

private BufferedReader console;

//数据输出流

private DataOutputStream dos;

//客户端套接字

private Socket client;

//自身身份信息

private User user;

private Integer to;

private boolean isRunning;

private static Logger logger = Logger.getLogger(SendChannel.class);

public SendChannel(Socket client,User user,Integer to){

//初始化

console = new BufferedReader(new InputStreamReader(System.in));

this.client = client;

this.isRunning = true;

this.user = user;

this.to = to;

try {

dos = new DataOutputStream(client.getOutputStream());

} catch (IOException e) {

release();

logger.error(e.getMessage());

}

//发送id初始化管道

Msg initMsg = new Msg(String.valueOf(user.getId()),3,user,null);

send(JSONUtil.getJsonString(initMsg));

logger.info("SendChannel has inited");

}

public void release(){

isRunning = false;

StreamUtil.close(console,dos,client);

}

private String getMsgFromConsole(){

try {

String content = console.readLine();

//通过消息内容分割,strs[0]是消息类型

String[] strs = content.split("-");

Msg msg = new Msg(content,Integer.parseInt(strs[0]),user,new User(to));

return JSON.toJSONString(msg);

}catch (IOException e){

logger.error(e.getMessage());

}

return "";

}

//发送消息

private void send(String content){

try {

dos.writeUTF(content);

dos.flush();

}catch (IOException e){

logger.error(e.getMessage());

//异常释放资源

release();

}

logger.info("Send -- "+content);

}

public User getUser(){

return user;

}

@Override

public void run() {

while(isRunning){

String msg = getMsgFromConsole();

if (!msg.equals("")){

send(msg);

}

}

}

}

接收管道

接收管道比较简单,就是接收然后解析。

/**

* @author yintianhao

* @createTime 2020/6/29 1:31

* @description 接收

*/

public class ReceiveChannel implements Runnable {

private DataInputStream dos;

private Socket client;

private boolean isRunning;

private static Logger logger = Logger.getLogger(ReceiveChannel.class);

public ReceiveChannel(Socket client){

this.client = client;

isRunning = true;

try {

dos = new DataInputStream(client.getInputStream());

} catch (IOException e) {

logger.error(e.getMessage());

release();

}

logger.info("ReceiveChannel has inited");

}

private void release(){

isRunning = false;

StreamUtil.close(dos,client);

}

private String getMsgFromChannel(){

try {

String msg = dos.readUTF();

return msg;

} catch (IOException e) {

logger.error(e.getMessage());

release();

//e.printStackTrace();

}

return "";

}

@Override

public void run() {

while (isRunning){

String content = getMsgFromChannel();

Msg msg = JSON.parseObject(content,Msg.class);

if (msg!=null){

logger.info("Message from:"+msg.getFrom().getNickname()+"--"+msg.getContent());

}

}

}

}

启动客户端

由于这里我没有选择从CMD输入用户昵称,所以我用了三个Client类来模拟客户端,另外两个交Client1,Client2,三个类的区别只是User对象的昵称不同。

/**

* @author yintianhao

* @createTime 2020/6/28 23:38

* @description 读写分离,封装

*/

public class Client0 {

private static Logger logger = Logger.getLogger(Client0.class);

public static void main(String[] args){

logger.info("Client0 start,Input your id and other id");

try {

Scanner scanner = new Scanner(System.in);

String str = scanner.next();

int from = Integer.parseInt(str.split("-")[0]);

int to = Integer.parseInt(str.split("-")[1]);

//连接

Socket client = new Socket("127.0.0.1",8888);

//发送信息的线程

User user = new User("Yintianhao",from);

SendChannel sendChannel = new SendChannel(client,user,to);

Thread sendThread = new Thread(sendChannel);

sendThread.start();

ReceiveChannel receiveChannel = new ReceiveChannel(client);

Thread receiveThread = new Thread(receiveChannel);

receiveThread.start();

} catch (IOException e) {

e.printStackTrace();

}

}

}

演示

总结

其实这个demo虽然私聊群聊是实现了,但是其实是存在许多不足的,比如消息的确认到达机制,消息丢失怎么办,消息的持久化,这些我都没有考虑进去,这阵子我也还在学习这方面的内容,希望以这个demo为开始继续完善吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值