搬运整合三个使用C#实现Socket编程的例子,包含服务器端和客户端。
原文链接:
按照链接顺序贴上原文。
例子一:
网络通讯流程如上
服务器:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _06Server
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
try
{
//当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;//IPAddress.Parse(txtServer.Text);
//创建端口号对象
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//监听
socketWatch.Bind(point);
ShowMsg("监听成功");
socketWatch.Listen(10);
Thread th = new Thread(Listen);
th.IsBackground = true;
th.Start(socketWatch);
}
catch
{
}
}
/// <summary>
/// 等待客户端的连接 并且创建与之通信用的Socket
/// </summary>
///
Socket socketSend;
void Listen(object o)
{
Socket socketWatch = o as Socket;
//等待客户端的连接 并且创建一个负责通信的Socket
while (true)
{
try
{
//负责跟客户端通信的Socket
socketSend = socketWatch.Accept();
//将远程连接的客户端的IP地址和Socket存入集合中
dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend);
//将远程连接的客户端的IP地址和端口号存储下拉框中
cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString());
//192.168.11.78:连接成功
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功");
//开启 一个新线程不停的接受客户端发送过来的消息
Thread th = new Thread(Recive);
th.IsBackground = true;
th.Start(socketSend);
}
catch
{ }
}
}
//将远程连接的客户端的IP地址和Socket存入集合中
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
/// <summary>
/// 服务器端不停的接受客户端发送过来的消息
/// </summary>
/// <param name="o"></param>
void Recive(object o)
{
Socket socketSend = o as Socket;
while (true)
{
try
{
//客户端连接成功后,服务器应该接受客户端发来的消息
byte[] buffer = new byte[1024 * 1024 * 2];
//实际接受到的有效字节数
int r = socketSend.Receive(buffer);
if (r == 0)
{
break;
}
string str = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint + ":" + str);
}
catch
{ }
}
}
void ShowMsg(string str)
{
txtLog.AppendText(str + "\r\n");
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
/// <summary>
/// 服务器给客户端发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
string str = txtMsg.Text;
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
List<byte> list = new List<byte>();
list.Add(0);
list.AddRange(buffer);
//将泛型集合转换为数组
byte[] newBuffer = list.ToArray();
//buffer = list.ToArray();不可能
//获得用户在下拉框中选中的IP地址
string ip = cboUsers.SelectedItem.ToString();
dicSocket[ip].Send(newBuffer);
// socketSend.Send(buffer);
}
/// <summary>
/// 选择要发送的文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSelect_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"C:\Users\SpringRain\Desktop";
ofd.Title = "请选择要发送的文件";
ofd.Filter = "所有文件|*.*";
ofd.ShowDialog();
txtPath.Text = ofd.FileName;
}
private void btnSendFile_Click(object sender, EventArgs e)
{
//获得要发送文件的路径
string path = txtPath.Text;
using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int r = fsRead.Read(buffer, 0, buffer.Length);
List<byte> list = new List<byte>();
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, 0, r+1, SocketFlags.None);
}
}
/// <summary>
/// 发送震动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnZD_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[1];
buffer[0] = 2;
dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
}
}
}
客户端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _07Client
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Socket socketSend;
private void btnStart_Click(object sender, EventArgs e)
{
try
{
//创建负责通信的Socket
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(txtServer.Text);
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//获得要连接的远程服务器应用程序的IP地址和端口号
socketSend.Connect(point);
ShowMsg("连接成功");
//开启一个新的线程不停的接收服务端发来的消息
Thread th = new Thread(Recive);
th.IsBackground = true;
th.Start();
}
catch
{ }
}
/// <summary>
/// 不停的接受服务器发来的消息
/// </summary>
void Recive()
{
while (true)
{
try
{
byte[] buffer = new byte[1024 * 1024 * 3];
int r = socketSend.Receive(buffer);
//实际接收到的有效字节数
if (r == 0)
{
break;
}
//表示发送的文字消息
if (buffer[0] == 0)
{
string s = Encoding.UTF8.GetString(buffer, 1, r-1);
ShowMsg(socketSend.RemoteEndPoint + ":" + s);
}
else if (buffer[0] == 1)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.InitialDirectory = @"C:\Users\SpringRain\Desktop";
sfd.Title = "请选择要保存的文件";
sfd.Filter = "所有文件|*.*";
sfd.ShowDialog(this);
string path = sfd.FileName;
using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fsWrite.Write(buffer, 1, r - 1);
}
MessageBox.Show("保存成功");
}
else if (buffer[0] == 2)
{
ZD();
}
}
catch { }
}
}
/// <summary>
/// 震动
/// </summary>
void ZD()
{
for (int i = 0; i < 500; i++)
{
this.Location = new Point(200, 200);
this.Location = new Point(280, 280);
}
}
void ShowMsg(string str)
{
txtLog.AppendText(str + "\r\n");
}
/// <summary>
/// 客户端给服务器发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
string str = txtMsg.Text.Trim();
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
socketSend.Send(buffer);
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
private void txtServer_TextChanged(object sender, EventArgs e)
{
}
}
}
例子二:
TCP/IP:Transmission Control Protocol/Internet Protocol,传输控制协议/因特网互联协议,又名网络通讯协议。简单来说:TCP控制传输数据,负责发现传输的问题,一旦有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地,而IP是负责给因特网中的每一台电脑定义一个地址,以便传输。TCP协议在许多分布式应用程序中进行消息命令传递是必不可少的部分。
TCP通信的三次握手:三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
- 第一次握手:客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
- 第二次握手:服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
- 第三次握手:客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1
先看下服务端Socket监听代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketDome
{
/// <summary>
/// 处理Socket监听逻辑
/// </summary>
public class SocketProvider
{
private static Socket serviceSocketListener; //Socke监听处理请求
/// <summary>
/// 开启Socket监听
/// </summary>
public static void Init()
{
serviceSocketListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serviceSocketListener.Bind(new IPEndPoint(IPAddress.Parse("10.0.0.217"), 20000)); //IP和端口应该是可配置
serviceSocketListener.Listen(1024);
Thread handleSocket = new Thread(new ThreadStart(HandleSocket));
handleSocket.Start();
}
/// <summary>
/// 监听链接
/// </summary>
private static void HandleSocket()
{
while (true)
{
try
{
Socket currSocket = serviceSocketListener.Accept(); //为新建连接创建新的 System.Net.Sockets.Socket
Thread processThread = new Thread(new ParameterizedThreadStart(ProcessSocket));
processThread.Start(currSocket);
}
catch { }
}
}
/// <summary>
/// 处理Socket信息
/// </summary>
/// <param name="obj">新建连接创建新Socket对象</param>
private static void ProcessSocket(object obj)
{
Socket currSocket = (Socket)obj;
try
{
byte[] recvBytess = new byte[1048576];
int recbytes;
recbytes = currSocket.Receive(recvBytess, recvBytess.Length, 0);
if (recbytes > 0)
{
var contentStr = Encoding.UTF8.GetString(recvBytess, 0, recbytes);
var _order = contentStr.Split('~');
byte[] sendPass = Encoding.UTF8.GetBytes(_order[0].ToUpper() + "#SUCCESS"); //先相应对话,然后去异步处理
currSocket.Send(sendPass, sendPass.Length, SocketFlags.None);
switch (_order[0].ToUpper())
{
case"ADDCACHE":
Console.WriteLine("添加缓存消息" + _order[1]);
//处理ADDCACHE逻辑
Console.WriteLine("写Log日志");
break;
default :
Console.WriteLine("命令错误");
Console.WriteLine("写Log日志");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("写Error日志" + ex.Message);
}
}
}
}
这个服务端,监听着客户端发来的命令,格式定义为:命令~参数,在服务端接受到客户端的命令消息后立即回传接到命令并开始处理,进行异步处理避免客户端等待。
下面看下客户端的Socket客户端主动请求服务端代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ConsoleApplication7
{
/// <summary>
/// Socket Helper
/// </summary>
public class SocketHelper
{
private string ip;
private IPEndPoint ex;
private Socket socket;
public SocketHelper(string ip, int port)
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.ip = ip;
this.ex = new IPEndPoint(IPAddress.Parse(ip), port);
}
/// <summary>
/// Socket 进行连接
/// </summary>
/// <returns>连接失败OR成功</returns>
public bool Socketlink()
{
try
{
socket.Connect(ex);
return true;
}
catch (Exception ex)
{
return false;
}
}
/// <summary>
/// Socket 发送消息
/// </summary>
/// <param name="strmsg">消息</param>
public void SendVarMessage(string strmsg)
{
try
{
byte[] msg = System.Text.Encoding.UTF8.GetBytes(strmsg);
this.socket.Send(msg);
}
catch (Exception ex)
{
this.socket.Close();
}
}
/// <summary>
/// Socket 消息回传
/// </summary>
/// <returns></returns>
public string ReceiveMessage()
{
try
{
byte[] msg = new byte[1048576];
int recv = socket.Receive(msg);
this.socket.Close();
return System.Text.Encoding.UTF8.GetString(msg, 0, recv);
}
catch (Exception ex)
{
this.socket.Close();
return "ERROR";
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication7
{
class Program
{
static void Main(string[] args)
{
SocketHelper socket = new SocketHelper("10.0.0.217",20000);
if(socket.Socketlink())
{
Console.WriteLine("连接成功");
socket.SendVarMessage("ADDCACHE~张三");
string strReposon = socket.ReceiveMessage();
Console.WriteLine(strReposon);
}
Console.Read();
}
}
}
首先以管理园身份开启服务端查询,然后客户端主动请求服务端进行消息请求。
例子三:
一、摘要
总结一下基于C#的TCP传输协议的涉及到的常用方法及同步实现。
二、实验平台
Visual Studio 2010
三、socket编程的一些常用方法(同步实现)
3.1 命名空间
需要添加的命名空间
using System.Net;
using System.Net.Socket;
3.2 构造新的socket对象
public socket (AddressFamily addressFamily,SocketType sockettype,ProtocolType protocolType)
(1) AddressFamily 用来指定socket解析地址的寻址方案,Inte.Network标示需要ip版本4的地址,Inte.NetworkV6需要ip版本6的地址;
(2) SocketType 参数指定socket类型,Raw支持基础传输协议访问,Stream支持可靠,双向,基于连接的数据流;
(3) ProtocolType 表示socket支持的网络协议,如常用的TCP和UDP协议。
3.3 定义主机对象
(1) IPEndPoint类
原型:
a)
public IPEndPoint(IPAddress address,int port)
参数address可以直接填写主机的IP,如"192.168.2.1";
b)
public IPEndPoint(long address,int port)
参数address整型int64如123456,参数port端口int32,如6655。
(2) 利用DNS服务器解析主机,使用Dns.Resolve方法
原型:
public static IPHostEntry Resolve(string hostname)
参数:待解析的主机名称,返回IPHostEntry类值,IPHostEntry为Inte.Net主机地址信息提供容器,该容器提供存有IP地址列表,主机名称等。
(3) Dns.GetHostByName获取本地主机名称
原型:
public static IPHostEntry GetHostByName(string hostname)
(4) GetHostByAddress
原型:
a)
public static IPHostEntry GetHostByAddress(IPAddress address)
参数:IP地址。
b)
public static IPHostEntry GetHostByAddress(string address)
参数:IP地址格式化字符串。
3.4 端口绑定和监听
同步套接字服务器主机的绑定和端口监听,Socket类的Bind(绑定主机),Listen(监听端口),Accept(接收客户端的连接请求)。
(1) Bind
原型:
public void Bind(EndPoint LocalEP)
参数为主机对象 IPEndPoint
(2) Listen
原型:
public void Listen(int backlog)
参数整型数值,挂起队列最大值
(3) accept
原型:
public socket accept()
返回为套接字对象
3.5 socket的发送和接收方法
(1) 发送数据
a)socket类的send方法
原型一:
public int Send(byte[] buffer)
参数:待发送的字节数组;
原型二:
public int Send(byte[],SocketFlags)
SocketFlags成员列表:
DontRoute不使用路由表发送,
MaxIOVectorLength为发送和接收数据的wsabuf结构数量提供标准值,
None 不对次调用使用标志,
OutOfBand消息的部分发送或接收,
Partial消息的部分发送或接收,
Peek查看传入的消息。
原型三:
public int Send(byte[],int,SocketFlags)
参数二要发送的字节数
原型四:
public int Send(byte[],int,int,SocketFlags)
参数二为Byte[]中开始发送的位置
b) NetWordStream类的Write方法
原型:
public override void write(byte[] buffer,int offset,int size)
参数分别为:字节数组,开始字节位置,总字节数。
(2) 接收数据
a) Socket类Receive方法
原型一:
原型二:public int Receive(byte[] buffer)
原型三:public int Receive(byte[],SocketFlags)
public int Receive(byte[],int,SocketFlags)
原型四:
public int Receive(byte[],int,int,SocketFlags)
Socket类Receive方法的相关参数可参看Socket类Send方法中的参数。
b) NetworkStream类的Read方法
public override int Read(int byte[] buffer,int offset,int size)
参数可参看NetworkStream类的Write方法。
四、TCP传输协议的同步实现
4.1 服务器端编程的步骤:
(1) 创建套接字;
(2) 绑定套接字到一个IP地址和一个端口上(bind());
(3)将套接字设置为监听模式等待连接请求(listen());
(4)请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
(5)用返回的套接字和客户端进行通信(send()/recv());
(6)返回,等待另一连接请求;
(7)关闭套接字。
服务器端代码:
using System; using System.Net; using System.Net.Sockets; using System.Collections.Generic; using System.Text; namespace net { class Program { static void Main(string[] args) { //定义接收数据长度变量 int recv; //定义接收数据的缓存 byte[] data = new byte[1024]; //定义侦听端口 IPEndPoint ipEnd = new IPEndPoint(IPAddress.Any, 5566); //定义套接字类型 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //连接 socket.Bind(ipEnd); //开始侦听 socket.Listen(10); //控制台输出侦听状态 Console.Write("Waiting for a client"); //一旦接受连接,创建一个客户端 Socket client = socket.Accept(); //获取客户端的IP和端口 IPEndPoint ipEndClient = (IPEndPoint)client.RemoteEndPoint; //输出客户端的IP和端口 Console.Write("Connect with {0} at port {1}", ipEndClient.Address, ipEndClient.Port); //定义待发送字符 string welcome = "Welcome to my server"; //数据类型转换 data = Encoding.ASCII.GetBytes(welcome); //发送 client.Send(data, data.Length, SocketFlags.None); while (true) { //对data清零 data = new byte[1024]; //获取收到的数据的长度 recv = client.Receive(data); //如果收到的数据长度为0,则退出 if (recv == 0) break; //输出接收到的数据 Console.Write(Encoding.ASCII.GetString(data, 0, recv)); //将接收到的数据再发送出去 client.Send(data, recv, SocketFlags.None); } Console.Write("Disconnect form{0}", ipEndClient.Address); client.Close(); socket.Close(); } } }
4.2 客户端编程的步骤:
(1) 创建套接字;
(2) 向服务器发出连接请求(connect());
(3) 和服务器端进行通信(send()/recv());
(4) 关闭套接字。
客户端代码:
上述代码实现了,当连接建立之后,客户端向服务器端发送键盘输入的字符,服务器端收到字符后,显示在控制台并发送给客户端,客户端收到字符后,显示在控制台并再次发送给服务器端,如此循环。using System; using System.Net; using System.Net.Sockets; using System.Collections.Generic; using System.Text; namespace client { class Program { static void Main(string[] args) { //定义发送数据缓存 byte[] data = new byte[1024]; //定义字符串,用于控制台输出或输入 string input, stringData; //定义主机的IP及端口 IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint ipEnd = new IPEndPoint(ip, 5566); //定义套接字类型 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //尝试连接 try { socket.Connect(ipEnd); } //异常处理 catch (SocketException e) { Console.Write("Fail to connect server"); Console.Write(e.ToString()); return; } //定义接收数据的长度 int recv = socket.Receive(data); //将接收的数据转换成字符串 stringData = Encoding.ASCII.GetString(data, 0, recv); //控制台输出接收到的数据 Console.Write(stringData); //定义从键盘接收到的字符串 input = Console.ReadLine(); //将从键盘获取的字符串转换成整型数据并存储在数组中 data = Encoding.ASCII.GetBytes(input); //发送该数组 socket.Send(data, data.Length, SocketFlags.None); while (true) { // //如果字符串是"exit",退出while循环 if (input == "exit") { break; } //对data清零 data = new byte[1024]; //定义接收到的数据的长度 recv = socket.Receive(data); //将接收到的数据转换为字符串 stringData = Encoding.ASCII.GetString(data, 0, recv); //控制台输出字符串 Console.Write(stringData); //发送收到的数据 socket.Send(data, recv, 0); } Console.Write("disconnect from server"); socket.Shutdown(SocketShutdown.Both); socket.Close(); } } }
五、实验结果
先后运行服务器端程序和客户端程序,控制台界面如下:
图1 服务器端控制台
当连接建立后,服务器端控制台显示等待客户端的状态"Waiting for a client",并打印出连接信息。
图2 客户端控制台
当连接建立后,客户端收到来自服务器端发送的字符串"Welcome to my server"。
之后,客户端通过键盘发送数据,二者循环接收并发送,控制台分别如下:
图3 服务器控制台
图4 客户端控制台
六、几点说明
6.1 传输速度
(1) 增大发送和接收的数组可提升传输速度,即增加一次实际发送数据的数量可以提高传输速度,但数组中数据的个数也不能一味的增大。需要说明的,由于地层MIT的限制,底层具体实现的时候每次发送的数据仍是不超过1510个的。
(2) 将控制台界面最小化后,速度也会有翻倍的提升。
6.2 MFC的转换
为了使传输协议更有可观性和使用性,通常做成MFC的样式,具体的使用已在基于TCP协议的网络摄像头的设计与实现应用。