《简述网络编程中的socket》阐述了socket的基本概念,从这一篇的Echo开始逐步打造一个完整的网络应用程序。
Echo是一个简单的回显程序,客户端向服务端发送消息,然后服务端将该消息完整地返回,我们在这里基于TCP协议实现该程序,代码如下:
服务端代码:
usingSystem;
usingSystem.Linq;
usingSystem.Net;
usingSystem.Net.Sockets;
usingSystem.Text;
namespaceEcho.Server
{
publicclassTcpServer
{
publicvoidStart(intport)
{
//创建服务端的Socket
m_socketServer=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//创建IP和端口节点对象
IPEndPointendPoint=newIPEndPoint(IPAddress.Any,port);
//为监听socket绑定IP和端口
m_socketServer.Bind(endPoint);
//把监听socket至于被动监听状态
m_socketServer.Listen(10);
//accept函数会在此处阻塞,直到有客户端的连接请求,根据该请求的创建新的socket
Console.WriteLine("开始监听,等待客户端连接...");
SocketclientSocket=m_socketServer.Accept();
Console.WriteLine("客户端已连接,开始接收数据...");
while(true)
{
//初始化一个用于接收数据的字节数组
byte[]buf=newbyte[];
//receive会阻塞程序,直到接收到数据
intrcvLen=clientSocket.Receive(buf);
stringmsg=Encoding.UTF8.GetString(buf.Take(rcvLen).ToArray());
Console.WriteLine($"来自客户端的数据:{msg}");
clientSocket.Send(buf.Take(rcvLen).ToArray());
}
}
privateSocketm_socketServer;
}
}
main函数调用代码:
staticvoidMain(string[]args)
{TcpServerserver=newTcpServer();server.Start();}
客户端代码:
usingSystem;
usingSystem.Linq;
usingSystem.Net;
usingSystem.Net.Sockets;
usingSystem.Text;
namespaceEcho.Client
{
publicclassTcpClient
{
publicSocketConnect(stringip,intport)
{
Sockets=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPointendPoint=newIPEndPoint(IPAddress.Parse(ip),port);
s.Connect(endPoint);
returns;
}
publicvoidInputLoop(Sockets)
{
while(true)
{
Console.Write("等待输入:");
stringmsg=Console.ReadLine();
byte[]buf=Encoding.UTF8.GetBytes(msg);
s.Send(buf);
byte[]bufServer=newbyte[];
intrcvlen=s.Receive(bufServer);
Console.WriteLine($"接收到服务端的回写:{Encoding.UTF8.GetString(bufServer.Take(rcvlen).ToArray())}");
}
}
}
}
main函数调用代码:
staticvoidMain(string[]args)
{
TcpClientclient=newTcpClient();
Console.Write("按任意键发起连接请求");
Console.ReadKey();
Sockets=client.Connect(".0.0.1",);
Console.WriteLine("成功连接到服务端");
client.InputLoop(s);
}
为了模拟服务端和客户端两个进程,分别创建了两个C#的控制台项目,Echo.Server和Echo.Client,这两个程序都在本机进行调试,具体的工作过程如下:
服务端会创建监听套接字,该套接字绑定的终结点IP地址为--IPAddress.Any,该值表示服务端会接受本机任何IP地址收到的请求;端口可以在合法值范围内任选一个,笔者选取的是,在同一台设备上,端口不能重用,如果出现端口重用的问题,可以换一个可用端口再次尝试。
接下来是打开被动监听,即代码中的对Listen函数的调用,其中参数值的选取不属于本篇范围,后续章节会展开说明。再往后的Accept函数会阻塞主线程,等待客户端连入,该函数结束阻塞后返回新的Socket对象,利用这个新的Socket对象可以在服务端和客户端两个端点间进行数据交互。
新的Socket对象调用接收函数Receive向系统发起IO请求,等待客户端数据,如果没有数据,该函数也会阻塞直到数据接收完成。此处,我们不知道要接收多少个字节,给定了一个固定值。Receive成功返回后会将接收到的数据写入到我们提供的缓存中,即代码中的字节数组buf,并且返回实际接收到的字节数rcvLen。我们给定字节数组和字符串转换的编码方式为UTF8,所以此处通过UTF8将字节数组转换为字符串输出到控制台,并且调用Send函数将接收到的字节发送回客户端,完成数据回写操作。
客户端相对来说会简单一些,首先创建Socket对象,该对象调用Connect函数向指定IP地址和端口的节点发起连接请求,在Connect未完成前该函数会一直阻塞,服务端接收连接后,该函数返回,现在就可以使用开始创建的Socket对象进行数据收发操作了。
在代码中可以看到,InputLoop函数是一个死循环,该循环开始会等待用户向控制台输入数据,输入的数据会通过UTF8的编码方式转换为字节数组,socket对象调用Send函数将该字节数组发送出去,发送完成后,调用Receive,等待服务的回写数据,Receive返回,将转换后的字符串输出到控制台,完成回写,如此循环往复。
这两个模拟程序虽然简单,却包含了TCPSocket通信的全部流程,适合初尝网络编程的小伙伴玩味理解。