一、线程
在认识socket之前,先来看看线程,线程是进程的一部分(一个进程可能对应着一个线程和多个线程)。线程可以比作是工人,当有人事情做不完的,需要他做事的时间,就把他叫来,如果是这样的话,肯定是在叫人之前把事情先安排好。还是先用一个例子来说明吧。举个多线程和单线程的比较的例子。
在vs2010新建一个winform项目:如下面图。
后台代码如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace ThreadCode { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //单击此按钮窗体不能被移动 Conculate(); } void Conculate() { DateTime begin = DateTime.Now; for (int i = 0; i < 1000000000; i++) { } TimeSpan ts = begin.Subtract(DateTime.Now); MessageBox.Show("计算完成所用时间"+ts.TotalMilliseconds.ToString()); } private void button2_Click(object sender, EventArgs e) { //单击此按钮窗体可以被移动,但是计算完成之前关闭,同样会看到弹出的计算时间 //这个不足为奇,因为是多线程,程序被运行时调动了操作系统的线程,这部分有操作系统完成, //当窗体关闭了,操作系统的线程还没有被关闭。 // Thread thread = new Thread(Conculate); thread.Start(); } } }
按单线程和多线程计算按钮效果分别如下:
后面的图是窗体已经被关闭显示出来的,因为没有winform运行的图标,还可以从图上看出多线程运行时间更长。在上面还遗留了一个问题是怎么让关闭后,计算的线程也关闭了,那就是把计算的线程变为后台线程。后台线程是当前台线程结束时,后台线程也跟着结束。与之对应的就是我们上面的例子:前台线程,只有所有的线程都运行完了,才能关闭程序。所以上面的问题解决方法是把thread.IsBackgroud=true;加上去就ok了。我们的关键是理解线程,上面已经说过,线程是工人,做其他人做不完的事情,我们看代码中的new Thread里面的参数为
public Thread(ThreadStart start);点击ThreadStart转到定义,会看到其实ThreadStart是个委托,其实也就是方法。总后总结一句:线程就是别人请他来干活的,这个活一定是开始安排好的。
二、socket通信原理
至于什么是socket,此处省略n个字。主要记录客户端和服务端之间的通信:一个客户端和一个服务端通信,那么至少需要三个socket,服务端要两个,客服端一个,当服务端的接待socket1,通常只有一个,接到另外一个客户端socket2的请求时,就会产生一个通信的socket2。下图说明的很清楚。
三、通信代码:
基本思路一
/// 1.1、客户端要本地的ip和一个socket相关联--通过socketWatch.Bind(endpoint);完成
/// 1.2、完成关联之后就开始用socketWatch 监听(监听的是socket,来自客户端,当监听成功时,可以记下客户端socket的ip和端口号,)
/// 用来区分不同的客户端)
/// 1.3、监听到客户端之后,服务端会再创建一个负责与客户端通信的socket----socketConnetion
/// 1.4、客户端与服务端的通信都通过socketConnetion来实现 包括send receive
需要注意的是使用线程解决冲突
/// <summary> /// 基本思路一、关于套接字二、关于线程 /// 1.1、客户端要本地的ip和一个socket相关联--通过socketWatch.Bind(endpoint);完成 /// 1.2、完成关联之后就开始用socketWatch 监听(监听的是socket,来自客户端,当监听成功时,可以记下客户端socket的ip和端口号,) /// 用来区分不同的客户端) /// 1.3、监听到客户端之后,服务端会再创建一个负责与客户端通信的socket----socketConnetion /// 1.4、客户端与服务端的通信都通过socketConnetion来实现 包括send receive /// </summary> public partial class Form1 : Form { public Form1() { InitializeComponent(); TextBox.CheckForIllegalCrossThreadCalls = false; } Thread watchTread=null;//创建一个线程,防止线程“死等”事件的发生 Thread recieveTread = null;//创建一个接收线程, Socket socketWatch=null;//创建一个套接字用来获取服务器的ip和端口 Socket socketConnetion = null;//链接成功后返回一个套接字 //创建一个字段,用来实现服务端向多个客户端发送信息 Dictionary<string,Socket> dtSocket=new Dictionary<string,Socket>(); //创建一个字段,用来实现服务端向多个客户端发送信息 Dictionary<string, Thread> dtThread = new Dictionary<string, Thread>(); /// <summary> /// 启动服务端 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BtnBeginServer_Click(object sender, EventArgs e) { //创建服务器线程 负责监听的套接字 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress address = IPAddress.Parse(txtIP.Text.ToString()); IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); //========================================================= socketWatch.Bind(endpoint);//将负责监听的套接字绑定到指定的ip和端口上 socketWatch.Listen(10); //======================================================= //创建监听线程,防止UI线程与监听线程冲突 watchTread = new Thread(WatchConnection); watchTread.IsBackground = true; watchTread.Start(); //======================================================== //创建客户端向客户端发送消息进程 //clientToClientThread = new Thread(recieveAndSend); //clientToClientThread.IsBackground = false; //clientToClientThread.Start(); MsgShow("服务器监听成功!"); } /// <summary> /// 链接服务端 /// </summary> void WatchConnection() { while(true) { socketConnetion = socketWatch.Accept();//一旦监听成功将返回一个链接套接字 string strClient = socketConnetion.RemoteEndPoint.ToString();//获取远程客户端的ip和port信息 lbClientList.Items.Add(strClient); dtSocket.Add(strClient, socketConnetion); //======================================================= //创建接收消息进程 recieveTread = new Thread(recievInfo);//当服务端接收消息时会和ui线程冲突,新建线程 recieveTread.IsBackground = false; recieveTread.Start(socketConnetion); dtThread.Add(strClient,recieveTread); MsgShow("连接服务器成功!!" + strClient); } } #region 接受消息委托--recievInfo(Object socketpara) /// <summary> /// 接收消息 /// </summary> void recievInfo(Object socketpara) { Socket socketRecieve = socketpara as Socket; while (true) { Byte[] arrMsgRec = new byte[1024 * 1024 * 2]; //用来储存接收服务端的二进制数据 //接收到文件的长度 int length = socketRecieve.Receive(arrMsgRec);//完成了接受传过来的二进制数据,并且返回一个整形数值 if (arrMsgRec[0] == 0) { //把接收到的消息转化成string类型 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length); MsgShow(strMsgRec); } else if (arrMsgRec[0] == 1) { SaveFileDialog sfd = new SaveFileDialog(); if (sfd.ShowDialog(this) == DialogResult.OK) { string fileSavePath = sfd.FileName; using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) { fs.Write(arrMsgRec, 1, length - 1); MsgShow("文件已经保存在:" + fileSavePath); } } } } } #endregion void MsgShow(string str) { txtMsg.AppendText(str + "\r\n"); } private void btnSend_Click(object sender, EventArgs e) { string strMsg=txtSend.Text.Trim(); byte[] arrMsg=System.Text.Encoding.UTF8.GetBytes(strMsg); //==================================================================== //找到选中客户端,让字典中的key值与选中客户端的名字相同,则向他发送消息 string strClient = lbClientList.Text; dtSocket[strClient].Send(arrMsg); MsgShow(strMsg + "已发送!"); } private void btnSendToAll_Click(object sender, EventArgs e) { string strMsg = txtSend.Text.Trim(); byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); foreach (Socket s in dtSocket.Values) { s.Send(arrMsg); } MsgShow("群发完毕~~"); } }
public partial class FClient : Form { public FClient() { InitializeComponent(); //防止出现不运行的txtMsg线程操作 TextBox.CheckForIllegalCrossThreadCalls = false; } Socket socketClient; private void BtnConntionServer_Click(object sender, EventArgs e) { socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress address = IPAddress.Parse(txtIP.Text.ToString()); IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); socketClient.Connect(endpoint); Thread recThread = new Thread(RecMsg); recThread.IsBackground = false;//防止在关闭程序后另外一个线程还在进行 recThread.Start();//出现一个实例,一定要让其start 说明是线程可以开始了即就绪了 } void RecMsg() { while(true) { Byte[] arrMsgRec = new byte[1024 * 1024 * 2]; //用来储存接收服务端的二进制数据 //接收到文件的长度 int length=socketClient.Receive(arrMsgRec); //把接收到的消息转化成string类型 string strMsgRec = System.Text.Encoding.UTF8.GetString(arrMsgRec, 0, length); MsgShow(strMsgRec); } } void MsgShow(string str) { txtMsg.AppendText(str + "\r\n"); } private void btnSend_Click(object sender, EventArgs e) {//先与主机建立链接,然后让主机产生一个负责通讯的套接字,最终由客户端send,服务端recieve就可以 //socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //IPAddress address = IPAddress.Parse(txtIP.Text.ToString()); //IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); //socketClient.Connect(endpoint); string msg = this.txtC2S.Text.Trim(); byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(msg); byte[] arrMsgSend = new byte[arrMsg.Length + 1]; arrMsgSend[0] = 0; Buffer.BlockCopy(arrMsg, 0, arrMsgSend, 1, arrMsg.Length); socketClient.Send(arrMsgSend); MsgShow("已经将"+msg+"发送到服务端"); } private void btnChoiceFile_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtFilePath.Text = ofd.FileName; } } private void btnSendFile_Click(object sender, EventArgs e) { using(FileStream fs=new FileStream(txtFilePath.Text,FileMode.Open)) { byte[] arrFile = new byte[1024 * 1024 * 2]; int length = fs.Read(arrFile, 0, arrFile.Length); byte[] arrFileSend = new byte[length + 1]; arrFileSend[0] = 1; Buffer.BlockCopy(arrFile, 0, arrFileSend,1, length ); socketClient.Send(arrFileSend); MsgShow("传送成功!"); } } }
上面的例子中注意把iP改为自己的ip,另外一个注意线程在本例子中的运用。