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