简易聊天室app实现:Unity+C# 客户端,Java 服务器端

Posted 百彦子烨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易聊天室app实现:Unity+C# 客户端,Java 服务器端相关的知识,希望对你有一定的参考价值。

Unity+C# 客户端

UI界面根据个人喜好排版

                        图1 进入界面                                                        图2 聊天界面

C#脚本代码如下:

Client_dxc.cs

using UnityEngine;
using System.Net.Sockets;
using System.Text;
using System;
using UnityEngine.UI;

public class Client_dxc : MonoBehaviour

    public Text GetText;
    public InputField InputText;
    public InputField ZhuCeInputText;
    public Text chatText;
    public ScrollRect scrollRect;
    public GameObject DengLuJieMian;

    private string SetText;

    private Socket socket;
    private byte[] buffer = new byte[1024];

    void Start()
    
        //AddressFamily 寻址类型  SocketType 套接字类型  ProtocolType 协议类型
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //连接服务器
        socket.Connect("127.0.0.1", 9999);

        Debug.Log("连接成功!");

        //开始接收信息
        readServer();
    

    void Update()
    
        huoqu();
    

    //接收信息
    void readServer()
    
        //Socket异步接收数据
        //buffer存储接收到的数据,offset从零开始计数,size要接收的字节数,SocketFlags 值的按位组合
        //ReceiveCallback一个用户定义的对象,其中包含接收操作的相关信息。当操作完成时,此对象会被传递给EndReceive(IAsyncResult)委托
        socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, null); 
    
    void ReceiveCallback(IAsyncResult iar)
    
        int len = socket.EndReceive(iar);
        if (len == 0)
        
            return;
        
        string str = Encoding.UTF8.GetString(buffer, 0, len);

        SetText = str;
        //循环接收数据
        readServer();
    

    //发送信息
    void sendServer(string msg)
    
        //将字符串转换为UTF-8编码的字节数组
        var outputBuffer = Encoding.UTF8.GetBytes(msg);

        //将数据异步发送到连接的 Socket
        //BoutputBuffer包含要发送的数据,SocketFlags 值的按位组合,callback:AsyncCallback 委托,state:Object 包含此请求的状态信息的对象
        socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
    

    //获取信息输出到信息面板上
    public void huoqu()
    
        if (SetText != "")
        
            string addText = "\\n  " + SetText;
            //将获取的信息添加到信息面板中
            chatText.text += addText;
            SetText = "";

            //强制画布更新内容
            Canvas.ForceUpdateCanvases();
            //垂直滚动位置,以 0 到 1 之间的值表示,0 表示位于底部
            scrollRect.verticalNormalizedPosition = 0f;
            Canvas.ForceUpdateCanvases();
        
    

    //绑定“发送”按钮(群发消息)
    public void qunInput()
    
        if (InputText.text != "")
        
            string msg = "Mass:" + InputText.text + "\\r\\n";
            sendServer(msg);
            InputText.text = "";
        
            
    

    //绑定“进入”按钮(进入群聊注册用户名)
    public void ZhuCe()
    
        if (ZhuCeInputText.text != "")
        
            string msg = "userName:" + ZhuCeInputText.text + "\\r\\n";
            sendServer(msg);
            DengLuJieMian.SetActive(false);
        
        
    


将脚本挂在Camera上,绑定对应组件及按钮

 

 

 

Java 服务器端

Main.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Main 
    public static void main(String[] args) 
        try 
            //创建ServerSocket对象,监听9999端口
            ServerSocket serversocket = new ServerSocket(9999);
            System.out.println("----------服务器启动----------");
            System.out.println("开始监听:");
            //创建一个循环,使主线程持续监听
            while (true)
                //做一个阻塞,监听客户端
                Socket socket = serversocket.accept();
                //创建一个新的线程,将客户端socket传入
                Server server = new Server(socket);
                //启动线程
                server.start();
            
         catch (IOException e) 
            throw new RuntimeException(e);
        
    

Server.java

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Server extends Thread
    //创建一个Map集合
    private static Map<String,Socket> map = new ConcurrentHashMap<>();
    //创建Socket对象
    private Socket socket;

    public Server(Socket socket)
        this.socket = socket;
    

    //重写run方法
    public void run()

        try 
            //获取客户端的输入流
            Scanner scanner=new Scanner(socket.getInputStream());
            String msg=null;
            //写一个循环来持续处理获取到的信息
            while (true) 
                //判断是否接收到了字符串
                if(scanner.hasNextLine())
                    //获取信息
                    msg=scanner.nextLine();
                    //处理客户端输入的字符串
                    Pattern pattern=Pattern.compile("\\r");
                    Matcher matcher=pattern.matcher(msg);
                    msg=matcher.replaceAll("");
                    //判断字符串是不是以userName:为前缀的
                    if(msg.startsWith("userName:"))
                    
                        //将字符串从:拆分开,后半部分存入userName中
                        String userName=msg.split(":")[1];
                        //注册该用户
                        userEnroll(userName,socket);
                        //将Map集合转换为Set集合
                        Set<Map.Entry<String,Socket>> set=map.entrySet();
                        //遍历集合,向所有用户发送消息
                        for(Map.Entry<String,Socket> entry:set)
                        
                            //取得客户端的Socket对象
                            Socket client=entry.getValue();
                            //取得client客户端的输出流
                            PrintStream printstream=new PrintStream(client.getOutputStream());
                            //向客户端发送信息
                            printstream.println(userName + "加入了群聊!");
                        

                        continue;
                    
                    //判断字符串是不是以Mass:为前缀的
                    if(msg.startsWith("Mass:"))
                        //拆分字符串,获取信息内容
                        String str=msg.split(":")[1];
                        //发送群聊信息
                        MassSend(socket,str);

                    
                

            
         catch (IOException e) 
            throw new RuntimeException(e);
        
    

    //注册用户
    private void userEnroll(String userName,Socket socket)
        //将键值对添加入Map集合中
        map.put(userName,socket);
        //打印日志
        System.out.println("[用户名为"+userName+"][客户端为"+socket+"]上线了!");
        System.out.println("积累进入人数为:"+map.size()+"人");
    

    //发送消息
    private void MassSend(Socket socket,String msg) 
        try 
            //将Map集合转换为Set集合
            Set<Map.Entry<String, Socket>> set = map.entrySet();
            String SendName = null;
            //遍历Set集合找到发起群聊信息的用户
            for (Map.Entry<String, Socket> entry : set) 
                //判断集合中的值是否与发起群聊的用户相同
                if (entry.getValue().equals(socket)) 
                    //获取键名
                    SendName = entry.getKey();
                    break;
                
            
            //遍历Set集合将群聊信息发给每一个客户端
            for (Map.Entry<String, Socket> entry : set) 
                //取得客户端的Socket对象
                Socket client = entry.getValue();
                //取得client客户端的输出流
                PrintStream printStream = new PrintStream(client.getOutputStream());
                //发送消息
                printStream.println(SendName + ":" + msg);
            
         catch (IOException e) 
            e.printStackTrace();
        
    

应用测试

本次测试为本机测试,模拟多客户端环境

先启动服务器

然后双开Unity,进入两个用户

服务器端界面如下:

 客户端聊天界面如下:

 

测试通过,可正常运行

将Java服务器端代码打包成jar包,发送到服务器上运行即可

将C#代码中的目标ip地址改为服务器ip,Unity客户端打包apk文件,发送到各个手机端安装运行即可

linux C语言 TCP 多线程 简易聊天室

只有一个客户端的话,接收发送都没什么问题,但是有第二个客户端参与的话,第一个客户端就不能发送了,但是可以接收,仔细看了下,问题应该在 服务器端的recv函数的第一个参数id,如果用进程做,可以复制数据,有2个客户端就有两个id,但是用线程做,线程之间数据共用,第二个客户端来了之后,第一个id就被第二个id覆盖了,这个问题要怎么解决呢?求指教

参考技术A 你accept得到一个新的连接后,再创建线程(把连接socket传给线程),用这个线程专门接收这个连接的数据,就不会有问题了。追问

你的意思是 每对话一次,客户端都close,然后再不停的connect吗?

追答

和客户端通讯的时候线程就活着,客户端断开了,线程就结束。

没有要客户端主动断掉,但是客户端总有断的时候吧?

如果永远不断,那服务端线程也一直存在。

本回答被提问者和网友采纳
参考技术B 第一个不能发送的原因在与你使用fork产生了第一个新进程后,再次fork产生新进程,但是第二个新进程中的id第一个fork产生的时候并没有,所以第一个是没有办法给第二个来发送消息的,而第二个fork的进程在fork之前就有了第一个fork进程的ID,所以第二个可以给第一个发送信息追问

不是啊,我用线程

以上是关于简易聊天室app实现:Unity+C# 客户端,Java 服务器端的主要内容,如果未能解决你的问题,请参考以下文章

Unity 简易聊天室(基于TCP)

linux C语言 TCP 多线程 简易聊天室

基于C/S模式的简易聊天室

网络编程之java简易聊天室实现

用运GUI实现简易的聊天室客户端

C 基于UDP实现一个简易的聊天室