第三季 SIKIC#高级教程 (2015版)_603~606
在应用上似乎没什么技术含量…就是调用正确的方法。
XX
看到的几段话。
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了
要标识进程,需要地址、协议和端口!所以下面的Bind是这样。
主要
建立连接
服务端:
第一步,创建socket。这里的参数是–寻址方案.内网 ,套接字类型.流,协议.TCP
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
第二步,绑定ip和端口号【win+r:cmd打开控制台,输入ipconfig查看】
绑定本地终结点->终结点地址和端口号->这里是传入本地地址,随便一个端口号
tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.3"), 5678));
第三步,开始监听(等待客户端连接)tcpServer.Listen(100);
第四步,暂停(阻塞?)当前线程,直到有客户端接入Socket clientSocket= tcpServer.Accept();
这样就连接成功,可以交互啦。
客户端:
第一步:创建socket。
第二步:发起连接请求。【地址和端口号要对的上】
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(ipAddress), port));
信息交互
这里注意字符串的和编码之间的转换就好。
接收,靠Reveive()。发送,靠Send()。唉,这也太周全了。
int length = clientSocket.Receive(data);//暂停线程等待信息
string message = Encoding.UTF8.GetString(data,0,length);
public void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
}
在服务端,专门建立一个 Client类,处理连接进来的客户端。每个不同的客户端连接到服务端,都会有新的Socket和client实例与该客户端对应,这样便于集中处理逻辑。【存入List】
一个服务端会对应多个客户端,这里服务端在while true死循环里接收新接入的客户端。因为Accept会暂停线程等待接入,这边也没什么好多想的。
while (true)
{
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("A client is connected!");
Client client = new Client(clientSocket);
clientList.Add(client);
}
因为receive暂停线程接收信息,所以给接收消息开启独立的线程。
public Client(Socket s)
{
clientSocket = s;
//启动一个线程处理客户端的数据接收
t = new Thread(ReceiveMessage);
t.Start();
}
然后才实现前面的receive。
Unity那的客户端也再开线程接收。
针对这个聊天室,服务端接收到任何客户端消息后,要广播出去,让所有客户端看到刚才的消息。Client类里需要一个SendMessage方法,这样启动广播就能批量的让Client实例发送消息给各自对应的客户端。
所以是:接收信息->触发广播->向所有客户端发送信息。
然后客户端Receive,更新文本。这里更新文本不直接放在单独线程的接收信息方法里,而是放在update里。因为文本是unity的组件,不能被单独的线程操作。不知道现在还是否如此。
另外稍微注意一下连接状态就行。
private void OnDestroy()
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
代码
服务端的Program类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace ChatRoom_socket_tcpserver
{
class Program
{
static List<Client> clientList = new List<Client>();
public static void BroadcastMessage(string message)
{
var UnconnectedList = new List<Client>();
foreach (var client in clientList)
{
if (client.Connected)
client.SendMessage(message);
else
{
UnconnectedList.Add(client);
}
}
foreach (var item in UnconnectedList)
{
clientList.Remove(item);
}
}
static void Main(string[] args)
{
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.3"), 5678));
tcpServer.Listen(100);
Console.WriteLine("Server running...");
while (true)
{
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("A client is connected!");
Client client = new Client(clientSocket);//把与每个客户端通信的逻辑放到client类里进行处理
clientList.Add(client);
}
}
}
}
服务端的Client类
using System;
using System.Text;
using System.Threading;
using System.Net.Sockets;
namespace ChatRoom_socket_tcpserver
{
class Client
{
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024];
public Client(Socket s)
{
clientSocket = s;
//启动一个线程处理客户端的数据接收
t = new Thread(ReceiveMessage);
t.Start();
}
private void ReceiveMessage()
{
while (true)
{
if (clientSocket.Poll(10, SelectMode.SelectRead))
{
clientSocket.Close();
break;//跳出循环,方法结束线程结束
}
int length = clientSocket.Receive(data);//暂停线程等待信息
string message = Encoding.UTF8.GetString(data,0,length);
//接收到数据的时候 把这个数据分发到客户端
//广播这个消息
Program.BroadcastMessage(message);
Console.WriteLine("收到了消息:"+message);
}
}
public void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
}
public bool Connected
{
get { return clientSocket.Connected; }
}
}
}
Unity的客户端部分
using System.Threading;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using System.Net;
using System.Net.Sockets;
public class ChatManager : MonoBehaviour
{
public string ipAddress = "192.168.1.3";
public int port = 5678;
public InputField inputField;
public Text chatText;
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024];
private string message="";
void Start()
{
ConnectToServer();
}
void Update()
{
if(message!=null&&message!="")
{
chatText.text+="\n"+message;//不允许在单独线程操作unity的组件,所以不放在ReceiveMessage
message="";//清空
}
}
void ConnectToServer()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//跟服务器端建立连接
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(ipAddress), port));
t = new Thread(ReceiveMessage);
t.Start();
}
/// <summary>
/// 循环接收消息
/// </summary>
void ReceiveMessage()
{
while (true)
{
if (clientSocket.Connected == false)
break;
int length = clientSocket.Receive(data);
message=Encoding.UTF8.GetString(data,0,length);
}
}
void SendChatMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
}
public void OnSendButtonClick()
{
string value = inputField.text;
SendChatMessage(value);
inputField.text = null;
}
private void OnDestroy()
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
}
这只是连到本地,不过这些功能封装的这么好,随便改改就能拓展了吧。
另外,unity打包的时候出现问题,改了路径就完事了。
"Unity打包到对应Assets文件夹下会失败。"好久没用喽。