何为Socket
Socket——套接字,从Socket的起源(基于Unix开发的传输工具)可以看出,Socket类似一个“文件”,在网络传输中扮演着重要角色,可以套用当初的思想——万物皆是Socket,即万物皆是文件(数据的传输者),在笔者看来,Socket更像是一个传递消息的媒介,我们在进行网络编程时,负责的工作职责是编写需要传输的内容和指派传输的目的地,而我们不需要去考虑怎么去传输,因为这些工作都是Socket底层完成的,每当我们实例化一个Socke对象时,一个传输媒介就已经准备待命了。
如何使用Socket
在网络协议中有诸如Tcp(面向连接)、Udp(不面向连接)等,其实这些都是Socket的封装。根据其构造函数的参数的组合达到需要的通信协议效果。
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);
Tcp(ProtocolType.Tcp)协议对应流套接字——SocketType.Stream
Udp(ProtocolType.Udp)协议对应数据包套接字——SocketType.Dgram
还有更底层的原始套接字的组合(SocketType.Raw)
这里我们便以Tcp协议作为程序例子
服务器端的实现
public class SyncTcpServer
{
public Socket server;
public Thread receiveThread;
public SyncTcpServer(int port)
{
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, port);
server.Bind(endPoint);
server.Listen(5);
Console.WriteLine("Sync server is open ,wait for connect...");
//Open receive thread
Receive();
Console.WriteLine("Receive thread is action...");
Console.WriteLine("————————————————————————————————————————");
}
/// <summary>
/// Receive message thread(only get any msg from special client)
/// </summary>
private void Receive()
{
byte[] buffer = new byte[1024];
receiveThread = new Thread(() =>
{
//Only get one client's request
Socket s = server.Accept();
//Accept msg from this client
while (true)
{
try
{
int length = s.Receive(buffer);
if (length > 0)
{
//Length 0 --> msg.lehgth
string msg = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine("{0}:\n{1}", s.RemoteEndPoint, msg);
//Response client when client send msg
byte[] send = Encoding.ASCII.GetBytes(string.Format("Hello,{0}", s.RemoteEndPoint));
s.Send(send);
}
//Get msg but length is '0' when client close
else if (length == 0) Console.WriteLine("{0} is close!", s.LocalEndPoint);
//Clear buffer
Array.Clear(buffer, 0, buffer.Length);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
});
receiveThread.Start();
receiveThread.IsBackground = true;
}
/// <summary>
/// Close socket&thread
/// </summary>
public void Close()
{
server.Close();
receiveThread.Abort();
}
}
- 构造函数绑定本地所有活动IP和端口号,设置监听队列长度为5,并启动同步接收信息线程(笔者这里服务器和客户机共用一个控制台,所以开线程来防止阻塞)。
- 阻塞式接收连接请求server.Accept()(无连接时一直等待,不会执行接下来代码),当连接成功时,循环接收客户机的消息并每次回复“Hello xxxx”(注意点:当客户机中的消息流释放时,s.Receive(buffer)会持续返回0,所以要进行if判断,如果客户机直接断开调用Close方法时,Receive方法会阻塞报错“远程主机强迫关闭了一个现有连接。。。”,所以需要加上try catch 语句)。
- 实现Close()方法,关闭线程和服务器。
客户端的实现
public class SyncTcpClient
{
public Socket client;
public Thread receiveThread;
public SyncTcpClient(IPEndPoint Iport)
{
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(Iport);
Receive();
Send(string.Format("{0} wish to connect!", client.LocalEndPoint));
}
/// <summary>
/// Send msg to server
/// </summary>
/// <param name="msg">msg</param>
public void Send(string msg)
{
byte[] str = Encoding.ASCII.GetBytes(msg);
client.Send(str);
}
/// <summary>
/// Receive thread
/// </summary>
private void Receive()
{
receiveThread = new Thread(() =>
{
byte[] buffer = new byte[1024];
//Receive msg by continue
while (true)
{
try
{
int length = client.Receive(buffer);
if (length > 0)
{
string msg = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine("Server:\n" + msg);
}
//Get msg but length is '0' when server close
else if (length == 0) Console.WriteLine("Server is close!");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
);
receiveThread.Start();
receiveThread.IsBackground = true;
}
/// <summary>
/// Close client & server
/// </summary>
public void Close()
{
client.Close();
receiveThread.Abort();
}
}
- 连接指定IP和端口号(连接服务器),并发送连接指示。
- 同时开启循环接收信息的线程,方法和注意项与服务器相同
Main函数调用
static void SyncTcpOneClientModel()
{
SyncTcpServer server = new SyncTcpServer(8080);
IPAddress ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[1];
if (Console.ReadLine() == "j")
{
SyncTcpClient client = new SyncTcpClient(new IPEndPoint(ip, 8080));
do
{
string msg = Console.ReadLine();
if (msg.ToLower() == "close")
{
server.Close();
client.Close();
}
else client.Send(msg);
} while (true);
}
}
输出结果
当按下J时客户端便请求连接并实现连续收发送消息,当然这里在转码时用的ASCII码格式,不支持中文,有兴趣的可以修改完善。
总结与反馈
- 同步方法及阻塞式方法,所以套用与While中并不会消耗太多资源,因为大部分时间都在等待 。
- Accept()、Receive()方法只会处理一次连接、接收效果,所以该程序只能实现一个客户端的连接和收发任意数量消息的效果,因次想要实现多客户连接并且多客户的收发消息可以再开一个子线程:
/// <summary>
/// Receive message thread(get any msg from any client)
/// </summary>
private void ReceiveFromAnyClient()
{
receiveThread = new Thread(() =>
{
while (true)
{
Socket s = server.Accept();
Client client = new Client(s);
client.Receive();
pool.Add(client);
}
});
receiveThread.Start();
receiveThread.IsBackground = true;
}
这里的Client简单的做了一个子线程处理客户的封装
public class Client
{
private Socket s;
public Client(Socket s)
{
this.s=s;
}
public void Receive()
{
byte[] buffer = new byte[1024];
Thread receiveThread = new Thread(() =>
{
while (true)
{
try
{
int length = s.Receive(buffer);
if (length > 0)
{
//Length 0 --> msg.lehgth
string msg = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine("{0}:\n{1}", s.RemoteEndPoint, msg);
//Response client when client send msg
byte[] send = Encoding.ASCII.GetBytes(string.Format("Hello,{0}", s.RemoteEndPoint));
s.Send(send);
}
//Get msg but length is '0' when client close
else if (length == 0) Console.WriteLine("{0} is close!", s.LocalEndPoint);
//Clear buffer
Array.Clear(buffer, 0, buffer.Length);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
});
receiveThread.Start();
receiveThread.IsBackground = true;
}
}
这里Main函数中创建3个客户进行测试
static void SyncTcpAnyClientModel()
{
int index = -1;
SyncTcpServer server = new SyncTcpServer(8080);
IPAddress ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[1];
if (Console.ReadLine() == "j")
{
List<SyncTcpClient> token = new List<SyncTcpClient>()
{
new SyncTcpClient(new IPEndPoint(ip,8080)),
new SyncTcpClient(new IPEndPoint(ip,8080)),
new SyncTcpClient(new IPEndPoint(ip,8080))
};
Thread.Sleep(100);
while (true)
{
int count = token.Count;
if (count > 0)
{
index++;
index %= count;
Console.WriteLine("\n{0}'s turn:", token[index].client.LocalEndPoint);
string msg = Console.ReadLine();
if (msg.ToLower() == "close")
{
token[index].Close();
token.Remove(token[index]);
}
else token[index].Send(msg);
Thread.Sleep(100);
}
else break;
}
}
}
输出结果如图:
这就是Socket同步实现单一客户和多客户(区别在于Accept方法的嵌套位置——循环与否)的通信交流。
当然,实际开发中这样的阻塞模式和线程的启用在处理效率和消耗上是不大合适的,更多的时候我们采用异步通信。