开始学习网络游戏编程啦,但是开始还是要打个基础,因此就从多人聊天室开始啦,这里做个笔记方便自己将来回顾和学习,当然里面有哪些不对的或者需要改进的,希望大神看到后能够多多指教!这里由于代码里面的注释写得很详细我就不再添加多余的文字说明了(我这里使用的是C#)。
一.搭建服务器,直接在VS编辑器中新建C#控制台项目,其代码如下两个脚本:
1.Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
///
/// 使用socket_tcp编写Unity聊天室的服务器端
///
namespace Chatroom_Server
{
class Program
{
///
/// 服务器计算机所在的ip和端口
///
public const string m_ipAddress = "192.168.1.108";
public const int m_port = 2345;
///
/// 服务器Socket对象
///
static Socket m_tcpServerServer;
///
/// 存储连接上服务器的客户端
///
static List m_clientList = new List();
///
/// 线程:专门用来处理客户端的连接
///
static Thread m_ServerThread;
static void Main(string[] args)
{
//1.创建一个Socket对象作为服务器,可以监听客户端的连接和发送的消息
//构造函数参数:联网类型(互联网网络,可以内网也可以外网)、数据传输方式(流)、协议(Tcp)
m_tcpServerServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.设置网络连接地址和端口
//Ip地址是本机的联网地址,让别的计算机找到本计算机
//端口随便设置,最好4位数,数字越长越不容易被占用,端口是本计算机中程序的识别标记
//m_tcpServerServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.108"), 2345));
m_tcpServerServer.Bind(new IPEndPoint(IPAddress.Parse(m_ipAddress), m_port));
//3.设置监听数量上限
m_tcpServerServer.Listen(100);
Console.WriteLine("server running...");
//创建一个线程专门用来监听客户端的连接从而不影响下面要执行的代码
m_ServerThread = new Thread(AcceptClientConnect);
//4.开始监听
m_ServerThread.Start();
}
static void AcceptClientConnect()
{
while (true)
{
//监听客户端连接,每连接上一个客户端,就创建一个Socket对象与之对应,将Socket对象和连接上来的客户端联系起来
//如果没有客户端连接上来,那么程序会一直停在这里等待
Console.WriteLine("waiting client connect!");
Socket tcpClientSocket = m_tcpServerServer.Accept();
Console.WriteLine("a client is connected!");
//把与每个客户端通信的逻辑(收发消息)放到Client类里面进行处理
Client client = new Client(tcpClientSocket);
m_clientList.Add(client);
}
}
///
/// 将接收的消息广播出去,将消息显示在每个客户端的聊天屏幕上
///
///
public static void BroadCastMessage(string message)
{
if (m_clientList == null || m_clientList.Count == 0)
return;
byte[] data = Encoding.UTF8.GetBytes(message);
//用来存储已经断开连接的客户端
List notConnected = new List();
foreach (var client in m_clientList)
{
//广播
if( client.IsConnected)
{
client.SendMessageToClient(data);
}
else
{
notConnected.Add(client);
}
}
//从连接列表中删除已经断开的客户端
foreach (var item in notConnected)
{
m_clientList.Remove(item);
}
}
}
}
2.Client.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Chatroom_Server
{
///
/// 用来和客户端通信的单元,每一个客户端对应一个单元,每个单元都会开启一个单独的线程去接收和处理消息
///
class Client
{
///
/// 保存与连接上来的客户端对应的Socket对象
///
private Socket m_clientSocket;
///
/// 获取Socket是否连接
///
public bool IsConnected
{
get { return m_clientSocket.Connected; }
}
private Thread m_curThread;
private byte[] m_data = new byte[1024];
public Client(Socket s)
{
//保存
m_clientSocket = s;
//创建一个线程并设置线程执行的函数,然后开启线程
m_curThread = new Thread(ReceiveMessage);
m_curThread.Start();
}
void ReceiveMessage()
{
while (true)
{
//在接收消息之前要判断Socket是否连接
if(m_clientSocket.Poll(10,SelectMode.SelectRead))//试图读取客户端数据,如果10毫秒内读取不到,那么判断已经断开连接,返回true
{
m_clientSocket.Shutdown(SocketShutdown.Both);
m_clientSocket.Close();
break;
}
//接收消息
int length = m_clientSocket.Receive(m_data);//如果没有接收到消息,程序会一直在这里等待
//Console.WriteLine(m_data.Length);//会输出1024
string message = Encoding.UTF8.GetString(m_data, 0, length);
//将接收的消息广播出去,将消息显示在每个客户端的聊天屏幕上
Program.BroadCastMessage(message);
//居然发送的消息都是byte数组,为什么不把接收到的数组不转换直接广播出去呢?而是要转换成字符串再转换成byte数组?
//原因:1.直接发送出去会是一个1024字节大小的数组,每次其实都没有这么大的数据传输,直接这样浪费流量
//原因:2.直接发送出去会是一个1024字节大小的数组,客户端那边接收的时候也会直接接收一个1024字节的数组,哪怕数组后面没有数据
//原因:3.直接发送出去会是一个1024字节大小的数组,这个数组由于没有清理过,在接收消息后,数组后面的元素里面可能存储有上一次的数据,导致客户端接收消息混乱(比如说第一条消息比第二条消息长的时候)
//原因:4.转换一次后传输的数据就变小了,而且客户端获得的数据就会刚好是有用的那一段
}
}
/////
///// 发送消息给对应的客户端()
/////
/////
//public void SendMessageToClient(string message)
//{
// byte[] data = Encoding.UTF8.GetBytes(message);
// m_clientSocket.Send(data);
//}
///
/// 将上面的函数再做一次优化,将字符串转换为byte数组的过程移到广播那边去,这样局只需要转换一次就能发送给所有的客户端了
///
///
public void SendMessageToClient(byte[] messageData)
{
m_clientSocket.Send(messageData);
}
}
}
二.客户端,由于聊天室界面很简单,相信做过Unity一段时间的都做得出来,所以这里就不做详解了,就截个屏吧(我这里使用的是NGUI),然后送上代码:
ChatManager.cs:
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class ChatManager : MonoBehaviour {
///
/// 服务器计算机所在的ip和端口
///
public const string m_ipAddress = "192.168.1.108";
public const int m_port = 2345;
private Socket m_clientSocket;
private Thread m_reCeiveThread;
private byte[] m_msgData = new byte[1024];//消息数据容器
private string m_message;//保存消息,因为在线程里面不允许直接操作Unity组件
public UIInput m_input;
public UILabel m_label;
// Use this for initialization
void Start () {
m_label.text = "";
ConnentToServer();
//开启一个线程专门用于接收消息
m_reCeiveThread = new Thread(ReceiveMessage);
m_reCeiveThread.Start();
}
// Update is called once per frame
void Update () {
if (!string.IsNullOrEmpty(m_message))
{
m_label.text += "\n" + m_message;
Debug.Log("m_message:" + m_message);
m_message = "";
}
}
///
/// 自定义的函数,连接到服务器
///
public void ConnentToServer()
{
m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//跟服务器建立连接
Debug.Log("开始连接服务器");
//clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.1.108"), 2345));
m_clientSocket.Connect(new IPEndPoint(IPAddress.Parse(m_ipAddress), m_port));
Debug.Log("连接服务器执行完毕");
}
///
/// 向服务器发送消息
///
///
void SendMessageToServer(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
m_clientSocket.Send(data);
}
///
/// 点击发送按钮事件
///
public void OnClickBtnSend()
{
string message = m_input.value;
if(string.IsNullOrEmpty(message))
{
Debug.LogWarning("发送消息不能为空");
return;
}
SendMessageToServer(message);
m_input.value = "";
}
void ReceiveMessage()
{
while (true)
{
//在接受消息之前判断Socket是否连接
if (m_clientSocket.Connected == false)
break;
int length = m_clientSocket.Receive(m_msgData);//这里会等待接收消息,程序暂停,只有接收到消息后才会继续执行
m_message = Encoding.UTF8.GetString(m_msgData, 0, length);
}
}
void OnDestroy()
{
//禁用Socket的发送和接收功能
m_clientSocket.Shutdown(SocketShutdown.Both);
//关闭Socket
m_clientSocket.Close();
}
}
哈哈,学习之后发现其实也不难,当然这只是最简单的学习例子啦!望今后天天进步!