首先,我们先新建一个C#项目,并生成一个对话框一个按钮。可以通过这个对话框把接收到的消息显示出来。然后可以通过这个按钮来部署服务器与建立连接:
就先使用这样一个朴实无华的界面,然后按钮下面的逻辑代码其实和前面介绍的就很像了,以下是包含注释的代码实现:
服务器:
namespace SocketServer
{
public partial class Form1 : Form
{
Socket server;
Socket accept;
public Form1()
{
InitializeComponent();
}
private void btnBind_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
if (btn.Text == "Bind")
{
//绑定,启动异步接受连接
try
{
int port = int.Parse(tbPort.Text); //输入端口号
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port); //注意服务器不用输入IP,直接用IPAddress.Any即可
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //建立套接字
server.Bind(endpoint); //服务器socket进行绑定
server.Listen(10); //开启监听队列
server.BeginAccept(AcceptCallback, server); //用异步方法来开始接受连接请求,并用回调函数
btn.Text = "Close";
}
catch
{ }
}
else if (btn.Text == "Close")
{
server.Close();
btn.Text = "Bind";
}
}
private void AcceptCallback(IAsyncResult ar)
{
Socket sock = ar.AsyncState as Socket;
try
{
accept = sock.EndAccept(ar);
//得到连接套接字accept,这是发起请求的套接字,用于往回发消息
StateObject so = new StateObject(accept);
this.BeginInvoke(new ThreadStart(() => //通过代理来访问对话框,由于对话框是在主线程建立的,因此需要通过代理访问。
{ //有兴趣的读着可以自行了解有关线程的知识
this.tbMsg.AppendText("\t收到连接:" + so.endpoint + "\n");
}));
accept.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so); //开始接受消息
server.BeginAccept(AcceptCallback, server); //重新调用Accept,在回调函数中调用本身,形成死循环,一直在等待请求
}
}
private void ReceiveCallback(IAsyncResult ar)
{
StateObject so = ar.AsyncState as StateObject; //存储连接信息的结构体,定义在下面
try
{
int length = so.sock.EndReceive(ar); //接受信息长度
string recvstr = System.Text.Encoding.UTF8.GetString(so.buffer, 0, length); //反编码,从流数据编码回字符串形式
this.BeginInvoke(new ThreadStart(() => //同上
{
this.tbMsg.AppendText(so.endpoint + ":" + recvstr + "\n"); //输出信息
}));
so.buffer = new byte[1024]; //接受数据的存储信息
so.sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so); //在回调函数再次调用,形成死循环
}
}
private void btnSend_Click(object sender, EventArgs e) //服务器主动发送聊天信息
{
if (server != null)
{
byte[] sendbytes = System.Text.Encoding.UTF8.GetBytes(this.tbMsgSend.Text);
accept.Send(sendbytes);
this.tbMsg.AppendText("我:" + this.tbMsgSend.Text + "\n");
this.tbMsgSend.Clear();
}
}
}
public class StateObject
{
public byte[] buffer = new byte[1024];
public Socket sock = null;
public string endpoint;
public StateObject(Socket sock)
{
this.sock = sock;
endpoint = sock.RemoteEndPoint.ToString();
}
}
}
客户端
namespace SocketClient
{
public partial class Form1 : Form
{
Socket client;
public Form1()
{
InitializeComponent();
}
private void btnConnect_Click(object sender, EventArgs e)
{
int port = int.Parse(tbPort.Text); //输入端口号
IPAddress ipaddress = IPAddress.Parse(tbIP.Text); //输入IP地址
IPEndPoint endpoint = new IPEndPoint(ipaddress, port); //IP地址与端口号唯一标识主机
if (client == null)
{
//开始异步连接
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.BeginConnect(endpoint, ConnectCallback, client);
}
}
private void ConnectCallback(IAsyncResult ar) //一部连接回调函数
{
Socket sock = ar.AsyncState as Socket;
try
{
sock.EndConnect(ar);
this.BeginInvoke(new ThreadStart(() => //通过代理来访问对话框,由于对话框是在主线程建立的,因此需要通过代理访问。
{ //有兴趣的读着可以自行了解有关线程的知识
this.tbMsg.AppendText("\t连接成功:)\n");
}));
StateObject so = new StateObject(sock); //记录服务器信息,类的定义如下
sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so); //开始接受信息的异步方法
}
}
private void ReceiveCallback(IAsyncResult ar) //接收消息的回调函数
{
StateObject so = ar.AsyncState as StateObject;
try
{
int length = so.sock.EndReceive(ar); //取得接受信息长度
string recvmsg = System.Text.Encoding.UTF8.GetString(so.buffer, 0, length); //对发送消息进行反编码,从流数据编码回字符串
this.BeginInvoke(new ThreadStart(() =>
{
this.tbMsg.AppendText(so.remotepoint + ":" + recvmsg + "\n"); //接收消息提示信息
}));
so.buffer = new byte[1024]; //接收消息的存储空间
so.sock.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, ReceiveCallback, so); //再次开始接收消息,在回到函数调用产生死循环
}
}
private void btnSend_Click(object sender, EventArgs e) //用户发送信息给服务器
{
if (client != null)
{
byte[] sendbytes = System.Text.Encoding.UTF8.GetBytes(this.tbMsgSend.Text); //编码,从字符串编码为流数据
client.Send(sendbytes); //发送流数据
this.tbMsg.AppendText("我:" + this.tbMsgSend.Text + "\n"); //写入文本框
this.tbMsgSend.Clear();
}
}
public class StateObject
{
public byte[] buffer = new byte[1024];
public Socket sock;
public string remotepoint;
public StateObject(Socket sock)
{
this.sock = sock;
this.remotepoint = sock.RemoteEndPoint.ToString();
}
}
}
这两个简单的程序即可生成相应的exe文件,我们可以打开刚才准备好的腾讯云服务器。而window系统的好处就是可以很自由的使用,例如我可以下载一个QQ然后用QQ把我们的程序发过去,事实上我一直都是这样做的。
程序在本机测试时就可以使用127.0.0.1这个虚拟网络IP地址,这个地址是单机模拟接入互联网的,可以得到如下的运行结果:
可见上面简单的程序的确可以实现两个程序进行交流。现在我们尝试把服务器程序放到我们真正的服务器上,看看效果如何。
如此通过QQ把程序发给自己的小号,然后在服务器上进行部署,同时用客户端程序在本地计算机进行连接:
上图中,左边是远程服务器虚拟机,右边是本地,可以看到通过输入对应服务器的IP地址以及端口号:129.204.252.166:8000可以很好的连接服务器,从而可以进行聊天室功能的简单通话。而在服务器端可以看到在笔者家中的外网IP地址是120.230.131.229,不再是之前的127.0.0.1了,说明我们成功通过外网实现了程序的连接。一个简单的聊天室功能就可以实现了。