您的位置:首页 > 理论基础 > 计算机网络

Photon服务器引擎(二)socket/TCP/UDP基础及Unity聊天室的实现

2017-03-07 17:15 495 查看


Photon服务器引擎(二)socket/TCP/UDP基础及Unity聊天室的实现

我们平时说的最多的socket是什么呢,实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。

通过Socket,我们才能使用TCP/IP协议。实际上,Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、connect、accept、send、read和write等等。

网络有一段关于socket和TCP/IP协议关系的说法比较容易理解:

“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。

这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,

TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”


1、TCP协议实现原理

TCP数据包主要包括:

1、SYN包:请求建立连接的数据包

2、ACK包:回应数据包,表示接收到了对方的某个数据包

3、PSH包:正常数据包

4、FIN包:通讯结束包

5、RST包:重置连接

6、URG包:紧急指针

一次完成的TCP通讯包括:建立连接、数据传输、关闭连接

建立连接(三次握手):

1、客户端通过向服务器端发送一个SYN来建立一个主动打开,作为三路握手的一部分。

2、服务器端应当为一个合法的SYN回送一个SYN/ACK。

3、最后,客户端再发送一个ACK。这样就完成了三路握手,并进入了连接建立状态。

数据传输:

1、发送数据端传输PSH数据包

2、接收数据端回复ACK数据包

关闭连接(四次分手):

1、一端主动关闭连接。向另一端发送FIN包。

2、接收到FIN包的另一端回应一个ACK数据包。

3、另一端发送一个FIN包。

4、接收到FIN包的原发送方发送ACK对它进行确认。

下面为使用TCP协议实现一个简单服务器端:

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading.Tasks;

class Program {

static void Main(string[] args) {

// 1,创建socket

Socket tcpServer = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

//2,绑定ip跟端口号 222.20.30.68

IPAddress ipaddress = new IPAddress(new byte[]{222,20,30,68});

EndPoint point = new IPEndPoint(ipaddress,7788);//ipendpoint是对ip+端口做了一层封装的类

tcpServer.Bind(point);//向操作系统申请一个可用的ip跟端口号 用来做通信

//3,开始监听 (等待客户端连接)

tcpServer.Listen(100);//参数是最大连接数

Console.WriteLine("开始监听");

Socket clientSocket = tcpServer.Accept();//暂停当前线程,直到有一个客户端连接过来,之后进行下面的代码

Console.WriteLine("一个客户端连接过来了");

//使用返回的socket跟客户端做通信

string message = "hello 欢迎你";

byte[] data = Encoding.UTF8.GetBytes(message);//对字符串做编码,得到一个字符串的字节数组

clientSocket.Send(data);

Console.WriteLine("向客户端发送了一跳数据");

byte[] data2 = new byte[1024];//创建一个字节数组用来当做容器,去承接客户端发送过来的数据

int length = clientSocket.Receive(data2);

string message2 = Encoding.UTF8.GetString(data2, 0, length);//把字节数据转化成 一个字符串

Console.WriteLine("接收到了一个从客户端发送过来的消息:"+message2);

Console.ReadKey();

}

}

TcpListener的使用:

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Runtime.InteropServices;

using System.Text;

using System.Threading.Tasks;

class Program {

static void Main(string[] args) {

//1,TcpListener对socket进行了一层封装,这个类里面自己会去创建socket对象

TcpListener listener = new TcpListener(IPAddress.Parse("222.20.30.68"), 7788);

//2,开始进行监听

listener.Start();

//3,等待客户端连接过来

TcpClient client = listener.AcceptTcpClient();

//4,取得客户端发送过来的数据

NetworkStream stream = client.GetStream();//得到了一个网络流 从这个网络流可以取得客户端发送过来的数据

byte[] data = new byte[1024];//创建一个数据的容器,用来承接数据

while (true)

{

//0 表示从数组的哪个索引开始存放数据

//1024表示最大读取的字节数

int length = stream.Read(data, 0, 1024);//读取数据

string message = Encoding.UTF8.GetString(data, 0, length);

Console.WriteLine("收到了消息:" + message);

}

stream.Close();

client.Close();

listener.Stop();//停止监听

Console.ReadKey();

}

}


2、UDP协议实现原理

UDP协议在IP协议上增加了复用、分用和差错检测功能。UDP的特点:

1、是无连接的。相比于TCP协议,UDP协议在传送数据前不需要建立连接,当然也就没有释放连接。

2、是尽最大努力交付的。也就是说UDP协议无法保证数据能够准确的交付到目的主机。也不需要对接收到的UDP报文进行确认。

3、是面向报文的。也就是说UDP协议将应用层传输下来的数据封装在一个UDP包中,不进行拆分或合并。因此,运输层在收到对方的UDP包后,会去掉首部后,将数据原封不动的交给应用进程。

4、没有拥塞控制。因此UDP协议的发送速率不送网络的拥塞度影响。

5、UDP支持一对一、一对多、多对一和多对多的交互通信。

6、UDP的头部占用较小,只占用8个字节。

下面为使用UDP协议实现一个简单服务器端:

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

class Program

{

private static Socket udpServer;

static void Main(string[] args) {

//1,创建socket

udpServer = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);

//2,绑定ip跟端口号

udpServer.Bind( new IPEndPoint( IPAddress.Parse("192.168.0.112"),7788 ) );

//3,接收数据

new Thread(ReceiveMessage){ IsBackground = true}.Start();

//udpServer.Close();

Console.ReadKey();

}

static void ReceiveMessage()

{

while (true)

{

EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

byte[] data = new byte[1024];

int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);//这个方法会把数据的来源(ip:port)放到第二个参数上

string message = Encoding.UTF8.GetString(data, 0, length);

Console.WriteLine("从ip:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" + (remoteEndPoint as IPEndPoint).Port + "收到了数据:" + message);

}

}

}

udpClient的使用:

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading.Tasks;

class Program {

static void Main(string[] args) {

//创建udpclient 绑定ip跟端口号

UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.0.112"),7788));

while (true)

{

//接收数据

IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);

byte[] data = udpClient.Receive(ref point);//通过point确定数据来自哪个ip的哪个端口号 返回值是一个字节数组,就是我们的数据

string message = Encoding.UTF8.GetString(data);

Console.WriteLine("收到了消息:" + message);

}

udpClient.Close();

Console.ReadKey();

}

}


3、Unity实现聊天室功能

客户端的实现代码:

[csharp] view
plain copy

print?

using UnityEngine;

using System.Collections;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading;

public class ChatManager : MonoBehaviour

{

public string ipaddress = "222.20.30.68";

public int port = 7788;

public UIInput textInput;

public UILabel chatLabel;

private Socket clientSocket;

private Thread t;

private byte[] data = new byte[1024];//数据容器

private string message = "";//消息容器

// Use this for initialization

void Start () {

ConnectToServer();

}

// Update is called once per frame

void Update () {

if (message != null && message != "")

{

chatLabel.text += "\n" + message;

message = "";//清空消息

}

}

void ConnectToServer()

{

clientSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);

//跟服务器端建立连接

clientSocket.Connect(new IPEndPoint(IPAddress.Parse("222.20.30.68"),7788));

//创建一个新的线程 用来接收消息

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);

//chatLabel.text += "\n" + message;

}

}

void SendMessage(string message)

{

byte[] data = Encoding.UTF8.GetBytes(message);

clientSocket.Send(data);

}

public void OnSendButtonClick()

{

string value = textInput.value;

SendMessage(value);

textInput.value = "";

}

void OnDestroy()

{

clientSocket.Shutdown(SocketShutdown.Both);

clientSocket.Close();//关闭连接

}

}

服务器端的实现代码:

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net.Sockets;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

namespace _022_聊天室_socket_tcp服务器端 {

/// <summary>

/// 用来跟客户端做通信

/// </summary>

class Client

{

public string name=null;

private Socket clientSocket;

private Thread t;

private byte[] data = new byte[1024];//这个是一个数据容器

public Client(Socket s,string i)

{

clientSocket = s;

name = i;

//启动一个线程 处理客户端的数据接收

t = new Thread(ReceiveMessage);

t.Start();

}

private void ReceiveMessage()

{

//一直接收客户端的数据

while (true)

{

//在接收数据之前 判断一下socket连接是否断开

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,name);

Console.WriteLine("收到 "+name +" 的消息:" + message);

}

}

public void SendMessage(string message)

{

byte[] data = Encoding.UTF8.GetBytes(message);

clientSocket.Send(data);

}

public bool Connected

{

get { return clientSocket.Connected; }

}

}

}

[csharp] view
plain copy

print?

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Net.WebSockets;

using System.Text;

using System.Threading.Tasks;

namespace _022_聊天室_socket_tcp服务器端 {

class Program {

static List<Client> clientList = new List<Client>();

/// <summary>

/// 广播消息

/// </summary>

/// <param name="message"></param>

public static void BroadcastMessage(string message,string name)

{

var notConnectedList = new List<Client>();

foreach (var client in clientList)

{

if (client.Connected)

client.SendMessage(name+" : "+message);

else

{

notConnectedList.Add(client);

}

}

foreach (var temp in notConnectedList)

{

clientList.Remove(temp);

}

}

static void Main(string[] args) {

int i = 0;

string name = null;

Socket tcpServer = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);//create tcpserver

tcpServer.Bind(new IPEndPoint(IPAddress.Parse("222.20.30.68"),7788));//set ip and port

tcpServer.Listen(100);//listen port

Console.WriteLine("server running...");

while (true)

{

i = i + 1;

if (i == 1) { name = "Tom"; }

else if (i == 2) { name = "Alice";}

else { name = "Other"; }

Socket clientSocket = tcpServer.Accept();//accept client request

Console.WriteLine("用户 "+name+" 已连接 !");

Client client = new Client(clientSocket,name);//把与每个客户端通信的逻辑(收发消息)放到client类里面进行处理

clientList.Add(client);

}

}

}

}

打开两个客户端。记得代码中的IP地址要改成自己的PC机的IP地址。

实现效果:





===================================================================================

结束。

祝大家国庆节快乐!

下一节直接上手Photon服务器引擎吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: