C#编程–socket网络通讯
一、网络socket概念:
- TCP/IP (Transmission Control Protocol / Internet Protocol)传输控制协议/网间协议。
socket 意为“插座”。通常称作“套接字”,用于描述IP地址和端口,是一个通讯链的句柄。(其实就是两个程序通讯用的。)
socket非常类似于电话插座。以一个电话网为例。电话的通话双方相当于相互通讯的2个程序,电话号码就是IP地址。任何用户在通话之前首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket.然后向对方拨号呼叫,相当于发出请求连接。对方加入再场并空闲,拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,相当于向socket发送数据和从socket接收数据。通话结束之后,一方挂起s电话机相当于关闭socket,撤销连接。
- 端口
二、 socket通讯图解

服务器端代码:
IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。
private void button1_Click(object sender, EventArgs e)
{
//socket服务器端代码:
//1.创建socket对象(用来侦听的socket对象)
//第一个参数设置 网络寻址协议。 AddressFamily.InterNetwork代表 IPV4
//第二个参数设置 数据传输的方式。 SocketType.Stream 以流的方式传输
//第三个参数设置 通讯的协议 ProtocolType.Tcp TCP/IP协议
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
this.textBox3.Text = "服务器端socket创建成功\r\n"+textBox3.Text;
//2、 绑定IP和端口
//IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。
IPAddress ip=IPAddress.Parse(textBox1.Text);
IPEndPoint ipendpoint=new IPEndPoint (ip,int.Parse(textBox2.Text));
serverSocket.Bind(ipendpoint);
this.textBox3.Text = "IP和端口绑定成功\r\n" + textBox3.Text;
//3、开启侦听
//等待的队列为:10;
serverSocket.Listen(10);
this.textBox3.Text = "开启侦听成功\r\n" + textBox3.Text;
//4.开始接受客户端的连接
//5.创建代理socket。
//accept方法一执行,当前线程会阻塞,一直等到客户端连接上。
Socket proxsocket = serverSocket.Accept();
string str = DateTime.Now.ToString();
byte[] data = Encoding.Default.GetBytes(str);
//6.发送的消息必须大于等于1.
proxsocket.Send(data, 0, data.Length, SocketFlags.None);
//7.告诉对方,我轻轻的走了。给对方发送一个空消息。
proxsocket.Shutdown(SocketShutdown.Both);
proxsocket.Close();
this.textBox3.Text = "服务器端代理socket关闭\r\n" + textBox3.Text;
//serverSocket.Shutdown(SocketShutdown.Both);
serverSocket.Close();
}
以上方法socket监听会阻塞主线程,不可取。
服务器端稳定的完整代码:
public partial class MainForm1 : Form
{
//用来存储代理socket的集合。
List<Socket> proxSocketList = new List<Socket>();
public MainForm1()
{
InitializeComponent();
}
private void btStart_Click(object sender, EventArgs e)
{
//1.创建服务器端socket对象(用来监听客户端Socket)
Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//2.绑定IP和端口
IPAddress ip = IPAddress.Parse(textIp.Text);
IPEndPoint ipendpoint = new IPEndPoint(ip,int.Parse(textPort.Text));
serverSocket.Bind(ipendpoint);
//3.开启侦听
//设置等待队列。
serverSocket.Listen(10); //链接: 同时来了100个链接请求,只能处理一个链接,队列里面放10个等待链接的客户端,其他的返回错误消息。
//4.开启线程池,服务器端接受客户端的连接
ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),serverSocket);
}
public void AcceptClientConnect(object socket)
{
var serverSocket = socket as Socket;
//4. 服务器端开始接受客户端的连接。
//此处应该循环一直接收客户端的连接。
while(true)
{
//创建代理socket,用来与客户端进行通讯。
//由于代理socket是用来与客户端通讯的,所以应该在其他地方也能访问到。
this.AppendToTextMessagr("服务器端开始接受客户端的连接请求。");
Socket proxSocket= serverSocket.Accept();
//将代理socket放到窗体级别中的proxSocketList(代理Socket集合)。可以让整个窗体访问到该代理prosocket,以便通讯。
proxSocketList.Add(proxSocket);
AppendToTextMessagr(string.Format("客户端:{0}已经连接上。",proxSocket.RemoteEndPoint.ToString()));
//当客户端连接上之后,服务器端就应该不停的接收客户端可能发来的消息。
//proxSocket.Receive(); //该方法也会阻塞当前线程,不能因为接收一个客户端的消息,而阻塞了当前线程对客户端的监听。
//因此此处考虑使用线程。
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMessage),proxSocket);
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btSend_Click(object sender, EventArgs e)
{
//将所要发送的内容存储到字节数组中。
byte[] data = Encoding.Default.GetBytes(textSend.Text);
//要想发送消息,首先要拿到代理Socket对象。
//使用foreach循环,遍历整个proxSocketList(代理Socket集合),拿到代理Socket.
foreach(var proxSocket in proxSocketList)
{
//首先判断当前proxSocket(当前代理Socket)是否是连接状态
if(proxSocket.Connected)
{
proxSocket.Send(data, 0, data.Length, SocketFlags.None);
}
}
}
/// <summary>
/// 接收客户端消息。
/// </summary>
/// <param name="Socket"></param>
public void ReceiveClientMessage(object socket)
{
var proxSocket = socket as Socket;
byte[] data1 = new byte[1024];
while(true)
{
// proxSocket.Receive() 方法返回值为实际接受到的字节数。
int len1 = 0;
try
{
len1 = proxSocket.Receive(data1, 0, data1.Length, SocketFlags.None);
}
catch
{
//异常退出。
AppendToTextMessagr(string.Format("客户端{0}非正常退出。", proxSocket.RemoteEndPoint.ToString()));
proxSocketList.Remove(proxSocket);
return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
}
//根据len1的值判断客户端是否正常退出
if(len1<=0)
{
AppendToTextMessagr(string.Format("客户端{0}正常退出。", proxSocket.RemoteEndPoint.ToString()));
//在代理Socket集合中将该proxSocket移除掉。
proxSocketList.Remove(proxSocket);
return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
}
//调用函数将接受到的数据放到文本框中。
string str = Encoding.Default.GetString(data1, 0, len1);
AppendToTextMessagr(string.Format("接收到客户端{0}的消息为:{1}",proxSocket.RemoteEndPoint.ToString(),str));
}
}
/// <summary>
/// 由于许多地方都要将数据放到Message文本框中,因此专门写一个函数来实现往文本框中追加数据。
/// </summary>
/// <param name="str"></param>
public void AppendToTextMessagr(string str)
{
if(textReceive.InvokeRequired)
{
//考虑跨线程访问控件。
textReceive.Invoke(new Action<string>(s =>
{
this.textReceive.Text = string.Format("{0}\r\n{1}", s, textReceive.Text);
}), str);
}
else
{
//不考虑跨线程访问控件的话向文本框追加内容如下。
this.textReceive.Text = string.Format("{0}\r\n{1}", str, textReceive.Text);
}
}
}
客户端代码:
public partial class Form1 : Form
{
//客户端Socket.
public Socket clientSocket { get;set;} //属性
public Form1()
{
InitializeComponent();
}
private void btuStart_Click(object sender, EventArgs e)
{
//客户端连接服务器端
//1. 创建socket对象
Socket socketClient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//为属性赋值。
clientSocket = socketClient;
//2. 连接服务器端
try
{
socketClient.Connect(IPAddress.Parse(textIP.Text), int.Parse(textPort.Text));
AppendToTextMessagr(string.Format("当前客户端连接成功。"));
}
catch(Exception ex)
{
//Thread.Sleep(500);
//btuStart_Click(this, ex);
MessageBox.Show("连接失败,重新连接");
return;
}
Thread thread = new Thread(ReceiveClientMessage);
thread.IsBackground = true;
thread.Start();
}
#region 客户端持续向服务器端发送数据(测试稳定性)
/// <summary>
/// 持续向服务器端发送数据
/// </summary>
public void ReceiveData()
{
byte[] data1 = new byte[1024];
byte[] data2 = new byte[1024];
int len1 = 0;
int len2 = 0;
//客户端连接上之后,首先向服务器端发送a
data1 = stringToByte("a");
clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);
while (true)
{
//接收服务器端发送来的消息。
len1 = 0;
try
{
len1 = clientSocket.Receive(data2, 0, data2.Length, SocketFlags.None);
if (len1 <= 0)
{
//服务器端断开连接
AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
StopConnect();
return;
}
else
{
string firstData = byteToString(data2, len1);
//AppendToTextMessagr(string.Format("服务器端发送消息:{0}",firstData));
if (firstData == "q")
{
data1 = stringToByte("@3111");
clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);
Thread.Sleep(1000);
}
else
{
continue;
}
}
}
catch
{
AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
StopConnect();
return;
}
len2 = 0;
try
{
len2 = clientSocket.Receive(data2, 0, data2.Length, SocketFlags.None);
if (len2 <= 0)
{
//服务器端断开连接
AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
StopConnect();
return;
}
else
{
string firstData2 = byteToString(data2, len1);
//AppendToTextMessagr(string.Format("服务器端发送消息:{0}", firstData2));
if (firstData2 == "b")
{
data1 = stringToByte("@3111");
clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);
Thread.Sleep(1000);
}
else
{
continue;
}
}
}
catch
{
AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
StopConnect();
return;
}
}
}
#endregion
/// <summary>
/// 接收客户端消息。
/// </summary>
/// <param name="Socket"></param>
public void ReceiveClientMessage(object socket)
{
var proxSocket = socket as Socket;
byte[] data1 = new byte[1024];
while(true)
{
// proxSocket.Receive() 方法返回值为实际接受到的字节数。
int len1 = 0;
try
{
len1 = proxSocket.Receive(data1, 0, data1.Length, SocketFlags.None);
}
catch
{
//异常退出。
AppendToTextMessagr(string.Format("服务器端{0}非正常退出。", proxSocket.RemoteEndPoint.ToString()));
StopConnect();
return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
}
//根据len1的值判断客户端是否正常退出
if(len1<=0)
{
AppendToTextMessagr(string.Format("服务器端{0}正常退出。", proxSocket.RemoteEndPoint.ToString()));
StopConnect();
return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
}
//调用函数将接受到的数据放到文本框中。
string str = Encoding.Default.GetString(data1, 0, len1);
AppendToTextMessagr(string.Format("接收到客户端{0}的消息为:{1}",proxSocket.RemoteEndPoint.ToString(),str));
}
}
private void StopConnect()
{
try
{
if(clientSocket.Connected)
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close(100);
}
}
catch
{
MessageBox.Show("关闭连接出错");
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//3. 发送消息,接收消息。
//客户端的Socket会自动分配一个随机的端口。
if(clientSocket.Connected)
{
byte[] data = Encoding.ASCII.GetBytes(textMessage.Text);
clientSocket.Send(data, 0, data.Length, SocketFlags.None); //发送消息
}
}
/// <summary>
/// 由于许多地方都要将数据放到Message文本框中,因此专门写一个函数来实现往文本框中追加数据。
/// </summary>
/// <param name="str"></param>
public void AppendToTextMessagr(string str)
{
if(textMessage.InvokeRequired)
{
//考虑跨线程访问控件。
textMessage.Invoke(new Action<string>(s =>
{
this.textMessage.Text = string.Format("{0}\r\n{1}", s, textMessage.Text);
}), str);
}
else
{
//不考虑跨线程访问控件的话向文本框追加内容如下。
this.textMessage.Text = string.Format("{0}\r\n{1}", str, textMessage.Text);
}
}
#region stringToByte 将字符串转换为字节流
/// <summary>
/// 将想要发送的字符串编码为字节数组
/// </summary>
/// <param name="data"></param>
public static byte[] stringToByte(string sendStr)
{
byte[] data1 = Encoding.ASCII.GetBytes(sendStr);
return data1;
}
#endregion
#region byteToString 将字节流转换为字符串
/// <summary>
/// 将接收到的字节数组重新编码为string字符串
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string byteToString(byte[] data, int Length)
{
string str = Encoding.ASCII.GetString(data, 0, Length); //将数组byte中的数据按ASCII的编码格式转化成string类型。重载(data,0(从第几位开始转化),Length(转换的字节长度))
return str;
}
#endregion
}
本文详细介绍C#中Socket编程的基础知识与实践应用,包括TCP/IP协议解释、Socket概念及其实现方式。通过具体代码示例,展示了服务器端与客户端的Socket通信流程,涉及线程池、数据收发及异常处理等关键环节。

5081

被折叠的 条评论
为什么被折叠?



