本文实例讲述了C# Socket网络编程技巧。分享给大家供大家参考。具体分析如下:
客户端要连接服务器:首先要知道服务器的IP地址。而服务器里有很多的应用程序,每一个应用程序对应一个端口号
所以客户端想要与服务器中的某个应用程序进行通信就必须要知道那个应用程序的所在服务器的IP地址,及应用程序所对应的端口号
TCP协议:
安全稳定,一般不会发生数据丢失,但是效率低。利用TCP发生数据一般经过3次握手(所有效率低,自己百度三次握手)
UDP协议:
快速,效率高,但是不稳定,容易发生数据丢失(没有经过三次握手,不管服务器有空没空,信息全往服务器发,所有效率搞,但服务器忙的时候就没办法处理你的数据,容易造成数据丢失,不稳定)
代码如下:
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.Net.Sockets;
using System.Net;
using System.Threading;
namespace Socket通信
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.txtPort.Text = “5000”;
this.txtIp.Text = “192.168.137.1”;
}
private void btnStart_Click(object sender, EventArgs e)
{
//当点击开始监听的时候,在服务器端创建一个负责监听IP地址跟端口号的Socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//Any:提供一个 IP 地址,指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
IPAddress ip = IPAddress.Any;
//创建端口号对象;将txtPort.Text控件的值设为服务端的端口号
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//监听
socketWatch.Bind(point);
ShowMsg(“监听成功”);
socketWatch.Listen(10);//连接队列的最大长度 ;即:一个时间点内最大能让几个客户端连接进来,超过长度就进行排队
//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket
Thread th = new Thread(Listen); //被线程执行的方法如果有参数的话,参数必须是object类型
Control.CheckForIllegalCrossThreadCalls = false; //因为.net不允许跨线程访问的,所以这里取消跨线程的检查。.net不检查是否有跨线程访问了,所有就不会报: “从不是创建控件“txtLog”的线程访问它” 这个错误了,从而实现了跨线程访问
th.IsBackground = true; //将th这个线程设为后台线程。
//Start(object parameter); parameter:一个对象,包含线程执行的方法要使用的数据,即线程执行Listen方法,Listen的参数
th.Start(socketWatch); //这个括号里的参数其实是Listen()方法的参数。因为Thread th = new Thread(Listen)这个括号里只能写方法名(函数名) 但是Listen()方法是有参数的,所有就要用Start()方法将它的参数添加进来
}
/// <summary>
/// 等待客户端连接,如果监控到有客户端连接进来就创建一个与之通信的Socket
/// </summary>
/// <param name=”o”></param>
void Listen(object o) //这里为什么不直接传递Socket类型的参数呢? 原因是:被线程执行的方法如果有参数的话,参数必须是object类型
{
Socket socketWatch = o as Socket;
while (true) //为什么这里要有个while循环?因为当一个人连接进来的时候创建了与之通信的Socket后就程序就会往下执行了,就不会再回来为第二个人的连接创建负责通信的Socket了。(应该是每个人(每个客户端)创建一个与之通信的Socket)所以要写在循环里。
{
//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket
Socket socketSend = socketWatch.Accept();
dic.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //(根据客户端的IP地址和端口号找负责通信的Socket,每个客户端对应一个负责通信的Socket),ip地址及端口号作为键,将负责通信的Socket作为值填充到dic键值对中。
//我们通过负责通信的这个socketSend对象的一个RemoteEndPoint属性,能够拿到远程连过来的客户端的Ip地址跟端口号
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + “连接成功”);//效果:192.168.1.32:连接成功
comboBox1.Items.Add(socketSend.RemoteEndPoint.ToString()); //将连接过来的每个客户端都添加到combBox控件中。
//客户端连接成功后,服务器应该接收客户端发来的消息。
Thread getdata = new Thread(GetData);
getdata.IsBackground = true;
getdata.Start(socketSend);
}
}
Dictionary<string, Socket> dic = new Dictionary<string, Socket>();
/// <summary>
/// 不停的接收客户端发送过来的消息
/// </summary>
/// <param name=”o”></param>
void GetData(object o)
{
while (true)
{
Socket socketSend = o as Socket;
//将客户端发过来的数据先放到一个字节数组里面去
byte[] buffer = new byte[1024 * 1024 * 2]; //创建一个字节数组,字节数组的长度为2M
//实际接收到的有效字节数; (利用Receive方法接收客户端传过来的数据,然后把数据保存到buffer字节数组中,返回一个接收到的数据的长度)
int r = socketSend.Receive(buffer);
if (r == 0) //如果接收到的有效字节数为0 说明客户端已经关闭了。这时候就跳出循环了。
{
//只有客户端给用户发消息,不可能是发0个长度的字节。即便发空消息,空消息也是有过个长度的。所有接收到的有效字节长度为0就代表客户端已经关闭了
break;
}
//将buffer这个字节数组里面的数据按照UTF8的编码,解码成我们能够读懂的的string类型,因为buffer这个数组的实际存储数据的长度是r个 ,所以从索引为0的字节开始解码,总共解码r个字节长度。
string str = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + str);
}
}
private void ShowMsg(string str)
{
txtLog.AppendText(str + “\r\n”); //将str这个字符串添加到txtLog这个文本框中。
}
/// <summary>
/// 服务端给客户端发信息
/// </summary>
/// <param name=”sender”></param>
/// <param name=”e”></param>
private void btnSend_Click_1(object sender, EventArgs e)
{
if (comboBox1.SelectedItem == null) //如果comboBox控件没有选中值。就提示用户选择客户端
{
MessageBox.Show(“请选择客户端”);
return;
}
string str = txtMes.Text; //获取用户输入的内容 (服务端要给客户端发的信息)
byte[] strByte = Encoding.Default.GetBytes(str); //将信息转换成二进制字节数组
string getIp = comboBox1.SelectedItem as string; //comboBox存储的是客户端的(ip+端口号)
Socket socketSend = dic[getIp] as Socket; //根据这个(ip及端口号)去dic键值对中找对应 赋值与客户端通信的Socket【每个客户端都有一个负责与之通信的Socket】
socketSend.Send(strByte); //将信息发送到客户端
}
}
}
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.Net.Sockets;
using System.Net;
using System.Threading;
namespace Socket通信
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.txtPort.Text = “5000”;
this.txtIp.Text = “192.168.137.1”;
}
private void btnStart_Click(object sender, EventArgs e)
{
//当点击开始监听的时候,在服务器端创建一个负责监听IP地址跟端口号的Socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//Any:提供一个 IP 地址,指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
IPAddress ip = IPAddress.Any;
//创建端口号对象;将txtPort.Text控件的值设为服务端的端口号
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//监听
socketWatch.Bind(point);
ShowMsg(“监听成功”);
socketWatch.Listen(10);//连接队列的最大长度 ;即:一个时间点内最大能让几个客户端连接进来,超过长度就进行排队
//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket
Thread th = new Thread(Listen); //被线程执行的方法如果有参数的话,参数必须是object类型
Control.CheckForIllegalCrossThreadCalls = false; //因为.net不允许跨线程访问的,所以这里取消跨线程的检查。.net不检查是否有跨线程访问了,所有就不会报: “从不是创建控件“txtLog”的线程访问它” 这个错误了,从而实现了跨线程访问
th.IsBackground = true; //将th这个线程设为后台线程。
//Start(object parameter); parameter:一个对象,包含线程执行的方法要使用的数据,即线程执行Listen方法,Listen的参数
th.Start(socketWatch); //这个括号里的参数其实是Listen()方法的参数。因为Thread th = new Thread(Listen)这个括号里只能写方法名(函数名) 但是Listen()方法是有参数的,所有就要用Start()方法将它的参数添加进来
}
/// <summary>
/// 等待客户端连接,如果监控到有客户端连接进来就创建一个与之通信的Socket
/// </summary>
/// <param name=”o”></param>
void Listen(object o) //这里为什么不直接传递Socket类型的参数呢? 原因是:被线程执行的方法如果有参数的话,参数必须是object类型
{
Socket socketWatch = o as Socket;
while (true) //为什么这里要有个while循环?因为当一个人连接进来的时候创建了与之通信的Socket后就程序就会往下执行了,就不会再回来为第二个人的连接创建负责通信的Socket了。(应该是每个人(每个客户端)创建一个与之通信的Socket)所以要写在循环里。
{
//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket
Socket socketSend = socketWatch.Accept();
dic.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //(根据客户端的IP地址和端口号找负责通信的Socket,每个客户端对应一个负责通信的Socket),ip地址及端口号作为键,将负责通信的Socket作为值填充到dic键值对中。
//我们通过负责通信的这个socketSend对象的一个RemoteEndPoint属性,能够拿到远程连过来的客户端的Ip地址跟端口号
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + “连接成功”);//效果:192.168.1.32:连接成功
comboBox1.Items.Add(socketSend.RemoteEndPoint.ToString()); //将连接过来的每个客户端都添加到combBox控件中。
//客户端连接成功后,服务器应该接收客户端发来的消息。
Thread getdata = new Thread(GetData);
getdata.IsBackground = true;
getdata.Start(socketSend);
}
}
Dictionary<string, Socket> dic = new Dictionary<string, Socket>();
/// <summary>
/// 不停的接收客户端发送过来的消息
/// </summary>
/// <param name=”o”></param>
void GetData(object o)
{
while (true)
{
Socket socketSend = o as Socket;
//将客户端发过来的数据先放到一个字节数组里面去
byte[] buffer = new byte[1024 * 1024 * 2]; //创建一个字节数组,字节数组的长度为2M
//实际接收到的有效字节数; (利用Receive方法接收客户端传过来的数据,然后把数据保存到buffer字节数组中,返回一个接收到的数据的长度)
int r = socketSend.Receive(buffer);
if (r == 0) //如果接收到的有效字节数为0 说明客户端已经关闭了。这时候就跳出循环了。
{
//只有客户端给用户发消息,不可能是发0个长度的字节。即便发空消息,空消息也是有过个长度的。所有接收到的有效字节长度为0就代表客户端已经关闭了
break;
}
//将buffer这个字节数组里面的数据按照UTF8的编码,解码成我们能够读懂的的string类型,因为buffer这个数组的实际存储数据的长度是r个 ,所以从索引为0的字节开始解码,总共解码r个字节长度。
string str = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + str);
}
}
private void ShowMsg(string str)
{
txtLog.AppendText(str + “\r\n”); //将str这个字符串添加到txtLog这个文本框中。
}
/// <summary>
/// 服务端给客户端发信息
/// </summary>
/// <param name=”sender”></param>
/// <param name=”e”></param>
private void btnSend_Click_1(object sender, EventArgs e)
{
if (comboBox1.SelectedItem == null) //如果comboBox控件没有选中值。就提示用户选择客户端
{
MessageBox.Show(“请选择客户端”);
return;
}
string str = txtMes.Text; //获取用户输入的内容 (服务端要给客户端发的信息)
byte[] strByte = Encoding.Default.GetBytes(str); //将信息转换成二进制字节数组
string getIp = comboBox1.SelectedItem as string; //comboBox存储的是客户端的(ip+端口号)
Socket socketSend = dic[getIp] as Socket; //根据这个(ip及端口号)去dic键值对中找对应 赋值与客户端通信的Socket【每个客户端都有一个负责与之通信的Socket】
socketSend.Send(strByte); //将信息发送到客户端
}
}
}
开打开始命令 cmd telnet 10.18.16.46 5000 即telnet 服务器ip地址 绑定的端口号
希望本文所述对大家的C#程序设计有所帮助。