上篇文章主要是简单介绍一下通信的过程以及机制。但实际上这中间简单的TCP的通信在实际应用中是比较的,利用C# TCP多线程的应用案例。
这边一起来分析一下多线程的代码,大家也可以在文章附带的Demo进行尝试。
我们点击启动服务按钮,服务器:
// 创建负责监听的套接字,注意其中的参数;
socketWatch =newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
// 获得文本框中的IP对象;
IPAddress address=IPAddress.Parse(txtIp.Text.Trim());
// 创建包含ip和端口号的网络节点对象;
IPEndPointendPoint =newIPEndPoint(address,int.Parse(txtPort.Text.Trim()));
try
{
// 将负责监听的套接字绑定到唯一的ip和端口上;
socketWatch.Bind(endPoint);
}
catch(SocketExceptionse)
{
MessageBox.Show("异常:"+se.Message);
return;
}
首先我们创建负责监听的套接字, 在Bind绑定后,我们创建了负责监听的线程。代码如下:
// 设置监听队列的长度;
socketWatch.Listen(10);
// 创建负责监听的线程;
threadWatch =newThread(WatchConnecting);
threadWatch.IsBackground =true;
threadWatch.Start();
ShowMsg("服务器启动监听成功!");
btnBeginListen.Enabled =false;
其中 WatchConnecting方法是负责监听新客户端请求的。然后让我们看一下WatchConnecting的代码。
///
///监听客户端请求的方法;
///
voidWatchConnecting()
{
while(true)// 持续不断的监听客户端的连接请求;
{
//开始监听客户端连接请求,Accept方法会阻断当前的线程;
SocketsokConnection = socketWatch.Accept();// 一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字;
// 想列表控件中添加客户端的IP信息;
lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString());
// 将与客户端连接的 套接字 对象添加到集合中;
dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);
ShowMsg("客户端连接成功!");
Threadthr =newThread(RecMsg);
thr.IsBackground =true;
thr.Start(sokConnection);
dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr);// 将新建的线程 添加 到线程的集合中去。
}
}
这个线程是一直存在的,主要的任务就是监听是否有Client与Server端进行连接,如果连接成功则会另开一个线程”RecMsg”。在该线程中则主要是得到字符数据的处理,包括接受数据以及发送数据。
voidRecMsg(objectsokConnectionparn)
{
SocketsokClient = sokConnectionparnasSocket;
while(true)
{
// 定义一个2M的缓存区;
byte[] arrMsgRec =new byte[1024 * 1024 * 2];
// 将接受到的数据存入到输入 arrMsgRec中;
intlength = -1;
try
{
length = sokClient.Receive(arrMsgRec);// 接收数据,并返回数据的长度;
}
catch(SocketExceptionse)
{
ShowMsg("异常:"+ se.Message);
// 从 通信套接字 集合中删除被中断连接的通信套接字;
dict.Remove(sokClient.RemoteEndPoint.ToString());
// 从通信线程集合中删除被中断连接的通信线程对象;
dictThread.Remove(sokClient.RemoteEndPoint.ToString());
// 从列表中移除被中断的连接IP
lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());
break;
}
catch(Exceptione)
{
ShowMsg("异常:"+ e.Message);
// 从 通信套接字 集合中删除被中断连接的通信套接字;
dict.Remove(sokClient.RemoteEndPoint.ToString());
// 从通信线程集合中删除被中断连接的通信线程对象;
dictThread.Remove(sokClient.RemoteEndPoint.ToString());
// 从列表中移除被中断的连接IP
lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString());
break;
}
if(arrMsgRec[0] == 0)// 表示接收到的是数据;
{
stringstrMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec,1, length-1);// 将接受到的字节数据转化成字符串;
ShowMsg(strMsg);
}
if(arrMsgRec[0] == 1)// 表示接收到的是文件;
{
SaveFileDialogsfd =newSaveFileDialog();
if(sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
{// 在上边的 sfd.ShowDialog() 的括号里边一定要加上 this 否则就不会弹出 另存为 的对话框,而弹出的是本类的其他窗口,,这个一定要注意!!!【解释:加了this的sfd.ShowDialog(this),“另存为”窗口的指针才能被SaveFileDialog的对象调用,若不加thisSaveFileDialog 的对象调用的是本类的其他窗口了,当然不弹出“另存为”窗口。】
stringfileSavePath = sfd.FileName;// 获得文件保存的路径;
// 创建文件流,然后根据路径创建文件;
using(FileStreamfs =newFileStream(fileSavePath,FileMode.Create))
{
fs.Write(arrMsgRec, 1, length - 1);
ShowMsg("文件保存成功:"+ fileSavePath);
其实这就是建立多线程TCP通信的主要过程,这里值得注意的就是其实监听线程监听的一直都是一个固定的端口,在应用层如果建立建立连接了,则连接不会使用监听的端口号,而会使用另一个空闲的端口号,这样才能保证一直连接监听一直使用一个固定端口号,从而使得连接也变得更加容易。Demo有数据交换的,也有类似于通信的,大家都可以参考。
领取专属 10元无门槛券
私享最新 技术干货