C#-Socket监听消息处理

2/10/2017来源:ASP.NET技巧人气:2981

搬运整合三个使用C#实现Socket编程的例子,包含服务器端和客户端。

原文链接:

C# socket监听

C#-Socket监听消息处理

基于C#的socket编程的TCP同步实现

按照链接顺序贴上原文。

例子一:

网络通讯流程如上

服务器:

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对象

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协议的网络摄像头的设计与实现应用。