服务器端
需要的引用包括:
using System.Net;
using System.Net.Sockets;
using System.Threading;
首先要创建Socket,将其绑定到本机的ip地址,给它一个端口号。然后开始监听
什么是ip地址和端口号?
每一个计算机都有一个ip地址。通过ip地址可以在网络中访问到计算机。
注:计算机通过不同的连接方式上网会获得不同的ip,这是因为计算机的每一个网卡都有一个ip,插网线和连接wifi以及蓝牙连接网络都会使用不同的网卡,因此也会有不同的ip。
但是仅仅通过ip找到指定的计算机还不行,还需要和指定的程序进行通信,这是就需要用到端口号。端口号就像是每个程序的门牌号。端口号有65,536个,一般选用一个较大的四位数来避免和其他程序的端口号发生冲突。
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(new IPEndPoint(IPAddress.Parse(GetIP()), 8099));//人为设定一个端口号
tcpServer.Listen(100);//最大连接数为100
Console.WriteLine("Waiting for connection...");
其中有一个GetIP()函数,用来获取本机IP
static public string GetIP()
{
try
{
string HostName = Dns.GetHostName(); //得到主机名
IPHostEntry IpEntry = Dns.GetHostEntry(HostName);
for (int i = 0; i < IpEntry.AddressList.Length; i++)
{
//从IP地址列表中筛选出IPv4类型的IP地址
//AddressFamily.InterNetwork表示此IP为IPv4,
//AddressFamily.InterNetworkV6表示此地址为IPv6类型
if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
return IpEntry.AddressList[i].ToString();
}
}
return "";
}
catch (Exception ex)
{
Console.WriteLine("Error getting local IP: " + ex.Message);
return "";
}
}
通过调用Socket.Accept()函数来接收一个来自客户端的连接请求,若没有连接请求,则程序会暂停在这里,直到有客户端连过来。Socket.Accept()函数会返回一个Socket对象,用来表示那个连过来的客户端。我们用一个while死循环来不断处理连接请求。并创建一个类Client来方便处理客户端的一些其他操作。由于会有多个客户端连接过来,创建一个List来管理折现Client对象。
while(true)
{
Socket cs = tcpServer.Accept();
clientList.Add(new Client(cs));
Console.WriteLine("Accept a client");
}
创建客户端对象链表
static private List<Client> clientList = new List<Client>();
客户端类Client
class Client
{
private Socket cSocket;
private Thread cThread;
private byte[] data = new byte[1024];
public Client(Socket s)
{
cSocket = s;
cThread = new Thread(ReceiveMsg);
cThread.Start();
}
private void ReceiveMsg()//接收来自客户端的消息
{
while(!cSocket.Poll(10, SelectMode.SelectRead))
{
try
{
int len = cSocket.Receive(data);
//Console.WriteLine("Receive a message:");
string Message = Encoding.UTF8.GetString(data, 0, len);
//Console.WriteLine(Message);
Program.BroadcastMsg(Message);
}
catch(Exception e)
{
}
}
cSocket.Close();
}
public void SendMsg(string msg)//向客户端发送消息
{
cSocket.Send(Encoding.UTF8.GetBytes(msg));
}
public bool isConnected
{
get { return cSocket.Connected;}
}
}
在Client的构造函数里我们开启一个线程,用来不断接收来自客户端发来的消息。
使用函数ReceiveMsg()函数来开启这个线程。这个函数中包括一个while循环,只要连接没有断开,就接收来自客户端的消息。
Socket.Receive(…)函数和Socket.Accept()一样,如果客户端没有发来消息就会卡在那里,知道客户端发来消息。
这里使用Socket.Poll(10, SelectMode.SelectRead)来判断有没有断开连接。它的意思是:如果连续10ms都无法读取就返回true。
客户端传过来的消息我们要用一个byte数组去接。然后将其转码成String以便输出查看。如果不查看的话这里是没必要转码成String的,因为我们马上就要把这个客户端发来的消息再广播出去。我们要把一个客户端发来的消息再传递给所有客户端,包括它自己。这里需要我们在主类里写一个静态成员函数BroadcastMsg(string msg)
static public void BroadcastMsg(string msg)
{
List<Client> unConnected = new List<Client>();
foreach(var c in clientList)
{
if (c.isConnected)//只向保持连接的客户端发送消息
{
//Console.WriteLine("Broadcasting a message");
c.SendMsg(msg);
//Console.WriteLine("Broadcasting succeed");
}
else//若没有连接则将它放入一个临时队列准备剔除
unConnected.Add(c);
}
foreach(var u in unConnected)//剔除断开连接的客户端
{
clientList.Remove(u);
Console.WriteLine("A client is disconnected");
}
}
在BroadcastMsg(string msg)函数中我们遍历客户端对象链表中的每一个对象,调用他们的Client.SendMsg(string msg)函数。如果不需要测试的话,这里传一个byte数组就好了。在Client.SendMsg(string msg)函数中我们会调用Socket.Send(byte[] data)函数,将消息发送给客户端。
自此,服务器端大功告成。
总结,其实总体的流程就是
1.创建一个socket对象ServerSocket
2.将Server绑定一个ip和端口号
3.调用ServerSocket.Listen(…)函数开始监听
4.调用ServerSocket.Accept()函数接收一个客户端的连接请求,建立连接,获得客户端Socket对象ClientSocket
5.调用ClientSocket.receive(byte[])函数接收客户端发来的消息
6.调用ClientSocket.send(byte[])函数向客户端发送消息。