服务端的主要职责是处理各个客户端发送来的数据,因此在客户端的Socket编程中需要使用两个线程来循环处理客户端的请求,一个线程用于监听客户端的连接情况,一个线程用于监听客户端的消息发送,当服务端接收到客户端的消息后需要将消息处理后再分发给各个客户端。
基本流程
- 创建套接字
- 绑定套接字的IP和端口号——Bind()
- 将套接字处于监听状态等待客户端的连接请求——Listen()
- 当请求到来后,接受请求并返回本次会话的套接字——Accept()
- 使用返回的套接字和客户端通信——Send()/Receive()
- 返回,再次等待新的连接请求
- 关闭套接字
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace TCPLib {
public class TCPServer {
private byte[] result = new byte[1024];
private int maxClientCount; //最大的监听数量
public int MaxClientCount {
get {
return maxClientCount;
}
set {
maxClientCount = value;
}
}
//IP地址
private string ip;
public string IP {
get {
return ip;
}
set {
ip = value;
}
}
//端口号
private int port;
public int Port {
get {
return port;
}
set {
port = value;
}
}
//客户端列表
private List < Socket > mClientSockets;
public List < Socket > ClientSockets {
get {
return mClientSockets;
}
}
//IP终端
private IPEndPoint iPEndPoint;
//服务端Socket
private Socket mServerSocket;
//当前客户端Socket
private Socket mClientSocket;
public Socket ClientSocket {
get {
return mClientSocket;
}
set {
mClientSocket = value;
}
}
///
///
///
/// 端口号
/// 监听的最大数量
public TCPServer(int port, int count) {
this.ip = IPAddress.Any.ToString();
this.port = port;
this.maxClientCount = count;
this.mClientSockets = new List < Socket > ();
//初始化IP终端
this.iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
//初始化服务端Socket
this.mServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//端口绑定
this.mServerSocket.Bind(this.iPEndPoint);
//设置监听数目
this.mServerSocket.Listen(maxClientCount);
}
///
/// 构造函数
///
/// ip地址
/// 端口号
/// 监听的最大数目
public TCPServer(string ip, int port, int count) {
this.ip = ip;
this.port = port;
this.maxClientCount = count;
this.mClientSockets = new List < Socket > ();
//初始化IP终端
this.ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
//初始化服务端Socket
this.mServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//端口绑定
this.mServerSocket.Bind(this.ipEndPoint);
//设置监听数目
this.mServerSocket.Listen(maxClientCount);
}
public void Start() {
//创建服务端线程,实现客户端连接请求的循环监听
var mServerThread = new Thread(this.ListenClientConnect);
mServerThread.Start();
}
//监听客户端连接
private void ListenClientConnect() {
//设置循环标志位
bool flag = true;
while (flag) {
//获取连接服务端的客户端
this.ClientSocket = this.mServerSocket.Accept();
//将获取的客户端添加到客户端列表
this.mClientSockets.Add(this.ClientSocket);
this.SendMessage(string.Format("客户端{0}已成功连接到服务器", this.ClientSocket.RemoteEndPoint));
//创建客户端消息线程,实现客户端消息的循环监听
var mReceiveThread = new Thread(this.ReceiveClient);
mReceiveThread.Start(this.ClientSocket);
}
}
//接受客户端消息
private void ReceiveClient(object obj) {
//获取当前客户端
var mClientSocket = (Socket) obj;
bool flag = true;
while (flag) {
try {
//获取数据的长度
int receiveLength = mClientSocket.Receive(result);
//获取客户端消息
string clientMessage = Encoding.UTF8.GetString(result, 0, receiveLength);
//服务端负责将客户端的消息分发给各个客户端
this.SendMessage(string.Format("客户端{0}发来消息:{1}", mClientSocket.RemoteEndPoint, clientMessage));
} catch (Exception e) {
//从客户端列表移除此客户端
this.mClientSockets.Remove(mClientSocket);
this.SendMessage(string.Format("服务器发来消息:客户端{0}从服务器断开,断开原因:{1}", mClientSocket.RemoteEndPoint, e.Message));
//断开连接
mClientSocket.Shutdown(SocketShutdown.Both);
mClientSocket.Close();
break;
}
}
}
//向所有的客户端群发消息
public void SendMessage(string msg) {
if (msg == string.Empty || this.mClientSockets.Count <= 0) return;
foreach(Socket s in this.mClientSockets) {
(s as Socket).Send(Encoding.UTF8.GetBytes(msg));
}
}
///
/// 向指定的客户端发送消息
///
/// ip
/// port
/// message
public void SendMessage(string ip, int port, string msg) {
//构造出一个终端地址
IPEndPoint _IPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
//遍历所有客户端
foreach(Socket s in mClientSockets) {
if (_IPEndPoint == (IPEndPoint) s.RemoteEndPoint) {
s.Send(Encoding.UTF8.GetBytes(msg));
}
}
}
}
}
服务端示列:
using System;
using System.Collections.Generic;
using System.Text;
using TCPLib;
using System.Net;
using System.Net.Sockets;
namespace TCPLib.Test
{
class Program
{
static void Main(string[] args)
{
//指定IP和端口号及最大监听数目的方式
TCPLib.TCPServer s1 = new TCPServer(“127.0.0.1”, 6001, 10);
//指定端口号及最大监听数目的方式
TCPLib.TCPServer s2 = new TCPServer(6001, 10);
//执行Start方法
s1.Start();
}
}
}
客户端相对于服务端来说任务要轻许多,因为客户端仅仅需要和服务端通信即可,可是因为在和服务器通信的过程中,需要时刻保持连接通畅,因此同样需要两个线程来分别处理连接情况的监听和消息发送的监听。
基本流程
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace TCPLib
{
public class TCPClient
{
private byte[] result = new byte[1024];
///
/// 客户端IP
///
private string ip;
public string IP
{
get { return ip; }
set { ip = value; }
}
///
/// 客户端端口号
///
private int port;
public int Port
{
get { return port; }
set { port = value; }
}
///
/// IP终端
///
private IPEndPoint ipEndPoint;
///
/// 客户端Socket
///
private Socket mClientSocket;
//是否连接到服务器
private bool isConnected = false;
///
///
///
/// IP地址
/// 端口号
public TCPClient(string ip,int port)
{
this.ip = ip;
this.port = port;
//初始化IP终端
this.ipEndPoint= new IPEndPoint(IPAddress.Parse(this.ip), this.port);
//初始化客户端
mClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public void Start()
{
//创建一个线程以不断连接服务器
var mConnectThread = new Thread(this.ConnectToServer);
//开启线程
mConnectThread.Start();
}
//连接服务器
private void ConnectToServer()
{
while(!isConnected)
{
try
{
mClientSocket.Connect(this.ipEndPoint);
this.isConnected = true;
}
catch(Exception e)
{
Console.WriteLine(string.Format("因为一个错误的发生,暂时无法连接到服务器,错误信息为:{0}", e.Message));
this.isConnected = false;
}
//等待5秒后尝试再次连接
Thread.Sleep(5000);
Console.WriteLine("正在尝试重新连接...");
}
Console.WriteLine("连接服务器成功,现在可以和服务器进行会话了");
//开启线程监听数据接收
var mReceiveThread = new Thread(this.ReceiveMessage);
mReceiveThread.Start();
}
private void ReceiveMessage()
{
bool flag = true;
while(flag)
{
try
{
//获取数据长度
int receiveLength = this.mClientSocket.Receive(result);
//获取服务器消息
string serverMessage = Encoding.UTF8.GetString(result, 0, receiveLength);
//输出服务器消息
Console.WriteLine(serverMessage);
}
catch(Exception e)
{
flag = false;
//断开服务器
this.mClientSocket.Shutdown(SocketShutdown.Both);
this.mClientSocket.Close();
//重新尝试连接服务器
this.isConnected = false;
ConnectToServer();
}
}
}
private void SendMessage(string msg)
{
if (msg == string.Empty || this.mClientSocket == null) return;
mClientSocket.Send(Encoding.UTF8.GetBytes(msg));
}
}
}
客户端示列:
using System;
using System.Collections.Generic;
using System.Text;
using TCPLib;
using System.Net;
using System.Net.Sockets;
namespace TCPLib.Test
{
class Program
{
static void Main(string[] args)
{
//保证端口号和服务端一致
TCPLib.TCPClient c = new TCPClient("127.0.0.1", 6001);
//执行Start方法
c.Start();
while (true)
{
//读取客户端输入的消息
string msg = Console.ReadLine();
//发送消息到服务端
c.SendMessage(msg);
}
}
}
}
总结:
这是一个基本用例,不过这个例子目前还存在以下问题:
* 这里仅仅实现了发送字符串的功能,如何让这个程序支持更多的类型,从基础的int、float、double、string、single等类型到structure、class甚至是二进制文件的类型?
* 如何让这个用例更具有扩展性,我们发现所有的Socket编程流程都是一样的,唯一不同就是在接收到数据以后该如何去处理,因为能不能将核心功能和自定义功能分离开来?
* 在今天的这个用例中,数据传输的缓冲区大小我们人为设定为1024,那么如果碰到比这个设定更大的数据类型,这个用例还需修改。
本篇unity教程关于socket同步通信的到此结束。