C#的Socket通讯

1.Socket简介

Socket:称”套接字”,套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。应用程序通过”套接字”向网络发送请求或应答,它是一个针对TCP和UDP编程的接口,借助它建立TCP/UDP连接。创建Socket连接时,可以指定使用传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。当使用UDP协议进行连接时,该Socket连接就是一个UDP连接。Socket是应用层与传输层之间的桥梁。

1.OSI七层网络模型:

应用层:网络服务与最终用户的一个接口。
协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP

表示层:数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)
格式有:JPEG、ASCll、DECOIC、加密格式等

会话层:建立、管理、终止会话。(在五层模型里面已经合并到了应用层)
对应主机进程:指本地主机与远程主机正在进行的会话

传输层:定义传输数据的协议,端口号,以及流控和差错校验
协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层

网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。
协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP

数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。(由底层网络定义协议)
将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。

物理层:建立、维护、断开物理连接。(由底层网络定义协议)

2.TCP/IP协议

tcp/ip是用于计算机通信的一组协议,我们通常称它为tcp/ip协议族。它是70年代中期美国国防部为其arpanet广域网开发的网络体系结构和协议标准,以它为基础组建的internet是目前国际上规模最大的计算机网络,正因为internet的广泛使用,使得tcp/ip成了事实上的标准。之所以说tcp/ip是一个协议族,是因为tcp/ip协议包括tcp、ip、udp、icmp、rip、telnetftp、smtp、arp、tftp等许多协议,这些协议一起称为tcp/ip协议。以下我们对协议族中一些常用协议英文名称和用途作一介绍:
tcp(transport control protocol):传输控制协议
ip(internetworking protocol):网间网协议
udp(user datagram protocol):用户数据报协议
icmp(internet control message protocol):互联网控制信息协议
smtp(simple mail transfer protocol):简单邮件传输协议
snmp(simple network manage protocol):简单网络管理协议
ftp(file transfer protocol):文件传输协议

从协议分层模型方面来讲,tcp/ip由四个层次组成:网络接口层、网间网层、传输层、应用层。其中:

网络接口层:这是tcp/ip软件的最低层,负责接收ip数据报并通过网络发送之,或者从网络上接收物理帧,抽出ip数据报,交给ip层。

网间网层 :负责相邻计算机之间的通信。其功能包括三方面。
一、处理来自传输层的分组发送请求,收到请求后,将分组装入ip数据报,填充报头,选择去往信宿机的路径,然后将数据报发往适当的网络接口。
二、处理输入数据报:首先检查其合法性,然后进行寻径——假如该数据报已到达信宿机,则去掉报头,将剩下部分交给适当的传输协议;假如该数据报尚未到达信宿,则转发该数据报。
三、处理路径、流控、拥塞等问题。

传输层:提供应用程序间的通信。其功能包括:一、格式化信息流;二、提供可靠传输,传输层协议规定接收端必须发回确认,并且假如分组丢失,必须重新发送。

应用层:向用户提供一组常用的应用程序,比如电子邮件、文件传输访问、远程登录等。远程登录telnet使telnet协议提供在网络其它主机上注册的接口。telnet会话提供了基于字符的虚拟终端。文件传输访问ftp使用ftp协议来提供网络内机器间的文件拷贝功能。
C#的Socket通讯

从协议分层模型方面来讲,tcp/ip协议族和OSI的对应关系:

应用层:大致对应于O S I模型的应用层和表示层,应用程序通过该层利用网络。
传输层:大致对应于O S I模型的会话层和传输层,包括T C P(传输控制协议)以及U D P(用户数据报协议),这些协议负责提供流控制、错误校验和排序服务。所有的服务请求都使用这些协议。
网间网层:对应于O S I模型的网络层,包括I P(网际协议)、I C M P(网际控制报文协议)、I G M P(网际组报文协议)以及A R P(地址解析协议)。这些协议处理信息的路由以及主机地址解析。
网络接口层:大致对应于O S I模型的数据链路层和物理层。该层处理数据的格式化以及将数据传输到网络电缆。

3.Socket与TCP/IP协议的关系

Socket是支持TCP/IP协议的网络通信的基本操作单元。数据链路层、网络层、传输层协议是在内核中实现的,因此操作系统需要实现一组系统调用,使得应用程序能够访问这些协议提供的服务,实现这组系统调用的API有socket。Socket是一套通用网络编程接口,它不但可以访问内核中TCP/IP协议栈,而且还可以访问其他网络协议栈(X.25协议栈、UNIX本地域协议栈等)。
socket与TCP/IP协议族的关系:

C#的Socket通讯
由socket定义的一组API提供两点功能:
一是将应用程序数据从用户缓冲区中复制到TCP/UDP内核发送缓冲区以交付内核来发送数据,或者是从内核TCP/UDP接收缓冲区中复制数据到用户缓冲区,以读取数据。

C#的Socket通讯

二是应用程序可以通过它们来修改内核中各层协议的某些头部信息或其他数据结构,从而精细地控制底层通信的行为。

4.TCP和UDP两种传输协议以及简单区别

TCP协议:传输控制协议,提供面向连接.可靠的字节流服务,提供超时重发,丢弃重复数据,检验数据,流量控制等功能。在正式收发数据前,必须建立可靠的连接,也即:三次握手.

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN (ack=j+1),同时自已也发送一个SYN 包(syn=k),即SYN+ ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

UDP协议:用户数据报协议,面向非连接,不保证可靠性的数据传输服务,没有超时重发等机制,故而传输速度很快.特点:它不与对方建立连接,而是直接就把数据包发送过去,UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。

TCP和UDP区别:

(1)tcp是面向连接的,udp是面向无连接的

tcp在通信之前必须通过三次握手机制与对方建立连接,而udp通信不必与对方建立连接,不管对方的状态就直接把数据发送给对方

(2)tcp连接过程耗时,udp不耗时。

(3)udp程序结构较简单

(4)tcp连接过程中出现的延时增加了被攻击的可能,安全性不高,而udp不需要连接,安全性较高

(5)tcp是可靠的,保证数据传输的正确性,不易丢包,udp是不可靠的,易丢包

5.服务端和客户端的理解

它的两个基本概念:客户端和服务端。当两个应用之间需要采用SOCKET通信时,首先需要在两个应用之间(可能位于同一台机器,也可能位于不同的机器)建立SOCKET连接,发起呼叫连接请求的一方为客户方,接受呼叫连接请求的一方成为服务方。客户方和服务方是相对的,同一个应用可以是客户方,也可以是服务方。

2.服务端

C#的Socket通讯

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;
using System.IO;

namespace SocketServer ///以下程序為服务端程序
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
///靜態字段,用.txt文檔存儲IP地址和端口號
public string _str_ip;
public string _str_port;
Socket socketcom;
Dictionary<string, Socket> dicsocket = new Dictionary<string, Socket>();

    /// <summary>
    /// 連接以太網通信
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_connect_Click(object sender, EventArgs e)
    {
        ///IP地址:服务器的地址
        ///端口号:服务器中的哪一个应用程序
        ///tcp:稳定,效率低,数据丢失少,它会进行三次握手,才会建立连接进行数据通信.要求你必须有服务器,一般是客户端发给服务器.
        ///udp:快速,效率高,但是不稳定,容易发生数据丢失.
        ///當點擊開始監聽的時候,在服務器創建一個負責監聽IP地址和端口號的Socket
        ///socket 设置 - bind 绑定监听端口号- listen 设置监听队列 - Accept:循环等待客户端连接 - Recipe/send  - 捕获端口号
        ///telnet
        ///ipconfig
        ///10.244.198.77
        ///net start telnet
        Txt_save();  //將當期的IP地址和port保存在.txt文檔裡面
        Socket Socketwatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            //创建IP地址
            //IPAddress ip = IPAddress.Any;
            IPAddress ip = IPAddress.Parse(tb_ip.Text);
            // 创建端口号对象
            IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(tb_port.Text));
            //綁定監聽的端口號
            Socketwatch.Bind(point);
            tb_log.AppendText(DateTime.Now.ToString() + "连接成功!" + "\n");
            tb_log.AppendText(DateTime.Now.ToString() + "开始监听" + "\n");
            Socketwatch.Listen(10);//设置最大可以同时监听10个客户端
            //开线程等待客戶端返回信息.
            Thread th = new Thread(Listen);
            th.IsBackground = true;
            th.Start(Socketwatch);
        }
        catch
        {
            tb_log.AppendText(DateTime.Now.ToString() + "IP或者端口号错误!请重新输入IP地址或者端口号!" + "\n");
        }
    }

    /// <summary>
    /// 程序開始監聽
    /// </summary>
    /// <param name="o"></param>
    void Listen(object o)
    {
        Socket socketwath = o as Socket;
        if (socketwath == null)
        {
            tb_log.AppendText(DateTime.Now.ToString() + "Socet函数转换有异常!" + "\n");
        }
        while (true)
        {
            socketcom = socketwath.Accept();//等待连接成功,并创造一个负责通信的Socket通信
            //將連接的端口號存入鍵值對集合和COMBOX組件中去
            dicsocket.Add(socketcom.RemoteEndPoint.ToString(), socketcom);
            cb_user.Items.Add(socketcom.RemoteEndPoint.ToString());
            //连接成功
            tb_log.AppendText(DateTime.Now.ToString() + " Ip地址是:" + socketcom.RemoteEndPoint.ToString() + ":" + " 监听成功!" + "\n");
            ///開啟新線程不停地接受客戶端發送過來的消息
            Thread socket = new Thread(Socket_Recieve);
            socket.IsBackground = true;
            socket.Start(socketcom);
        }
    }

    /// <summary>
    /// 服務器端不停的接受客戶端發過來的消息
    /// </summary>
    /// <param name="s"></param>
    void Socket_Recieve(object s)
    {
        Socket socketedsend = s as Socket;
        byte[] buffer = new byte[5 * 1024 * 1024];
        while (true)
        {
            try
            {
                int r = socketedsend.Receive(buffer);
                if (r != 0)
                {
                    string str = Encoding.Default.GetString(buffer, 0, r);
                    tb_log.AppendText(DateTime.Now.ToString() + " 接受內容:" + str + "\n");
                }
                else
                {
                    break;
                }
            }
            catch
            { }

        }
    }

    /// <summary>
    /// 服務器給客戶端發送消息,用第一個位來判斷是否是消息或者發送文件,是否發送視頻.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_send_Click(object sender, EventArgs e)
    {
        try
        {
            string str = tb_send.Text;
            byte[] buffer = Encoding.Default.GetBytes(str);
            //協議:第一位0是傳送內容
            List<byte> list_send = new List<byte>();
            list_send.Add(0);
            list_send.AddRange(buffer);
            byte[] newBuffer = list_send.ToArray();
            string ip = cb_user.SelectedItem.ToString();
            dicsocket[ip].Send(newBuffer);
            tb_log.AppendText(DateTime.Now + "  " + str + " 已經發送成功!" + "\n");
        }
        catch
        { }
    }
    /// <summary>
    /// 保存當期的IP和端口號到.txt文件
    /// </summary>
    public void Txt_save()
    {
        string[] text_save = new string[2] { "Ip:" + tb_ip.Text, "Port:" + tb_port.Text };
        File.WriteAllLines(@"C:\Users\Administrator\Desktop\1.txt", text_save, Encoding.Default);
    }

    /// <summary>
    /// 窗口登陸處理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_Load(object sender, EventArgs e)
    {
        Control.CheckForIllegalCrossThreadCalls = false;
        ///做一個記事本文件用來保存IP地址和端口號.
        string[] txt_read = File.ReadAllLines(@"C:\Users\Administrator\Desktop\1.txt", Encoding.Default);
        int indexip = txt_read[0].IndexOf(":");
        _str_ip = txt_read[0].Substring(indexip + 1, txt_read[0].Length - indexip - 1);
        tb_ip.Text = _str_ip;
        int indexport = txt_read[1].IndexOf(":");
        _str_port = txt_read[1].Substring(indexport + 1, txt_read[1].Length - indexport - 1);
        tb_port.Text = _str_port;

    }

    private void bt_sendfile_Click(object sender, EventArgs e)
    {
        byte[] buffer = new byte[1024 * 1024 * 5];
        if (tb_fath.Text == "")
        {
            return;
        }
        using (FileStream fs = new FileStream(tb_fath.Text, FileMode.OpenOrCreate, FileAccess.Read))
        {
            try
            {
                int r = fs.Read(buffer, 0, buffer.Length);
                List<byte> list_send = new List<byte>();
                list_send.Add(1);
                list_send.AddRange(buffer);
                byte[] newbuffer = list_send.ToArray();
                string ip = cb_user.SelectedItem.ToString();
                dicsocket[ip].Send(newbuffer, 0, r+1, SocketFlags.None);//注意這和普通發送.txt文檔的不一樣.
            }
            catch
            { }
        }
    }

    private void bt_choice_Click(object sender, EventArgs e)
    {
        OpenFileDialog fd = new OpenFileDialog();
        fd.Title = "請選擇你需要傳送的文件:";
        fd.InitialDirectory = @"C:\Users\Administrator\Desktop";
        fd.Multiselect = true;
        fd.Filter = "所有文件|*.*";
        fd.ShowDialog();
        tb_fath.Text = fd.FileName;
    }

    private void bt_dd_Click(object sender, EventArgs e)
    {
        byte[] buffer=new byte[1];
        buffer[0] = 2;
        try
        {
            dicsocket[cb_user.SelectedItem.ToString()].Send(buffer);
        }
        catch { }
    }
}

3.客户端

C#的Socket通讯

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;
using System.IO;

namespace SocketClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

  Socket SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  
    /// <summary>
    /// 連接服務端,同時開線程進行接受數據
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_connet_Click(object sender, EventArgs e)
    {
        try
        {
            IPAddress Ip = IPAddress.Parse(tb_ip.Text);  //IP的格式處理
            IPEndPoint point = new IPEndPoint(Ip, Convert.ToInt32(tb_port.Text));  //網路格點
            SocketClient.Connect(point);                                           //連接網絡格點
            tb_log.AppendText(DateTime.Now + "連接成功!" + "\n");
        }
        catch
        { }

        //開線程進行實時接受數據
        Thread th = new Thread(Recive);
        th.IsBackground = true;
        th.Start(SocketClient);
    }

    /// <summary>
    /// 連接成功后實時接受客戶端發送的信息
    /// </summary>
    /// <param name="s"></param>
    public void Recive(object s)
    {
        Socket socket_recive = s as Socket;

        while (true)
        {
            try
            {
                byte[] buffer = new byte[3 * 1024 * 1024];
                int r = socket_recive.Receive(buffer);
                if (r == 0)
                    break;
                else
                {
                    if (buffer[0] == 0)
                    {
                        string str = Encoding.Default.GetString(buffer, 1, r - 1);
                        tb_log.AppendText(DateTime.Now.ToString() + " 接受內容為:" + str + "\n");
                    }
                    else if (buffer[0] == 1)
                    {
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.Title = "請選擇你要保存文件的路徑:";
                        sfd.InitialDirectory = @"C:\Users\Administrator\Desktop";
                        sfd.Filter = "文本文檔|*.txt|所有文件|*.*";
                        sfd.ShowDialog(this);
                        string path = sfd.FileName;
                        using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            //byte[] buffer_save = new byte[5 * 1024 * 1024];
                            fs.Write(buffer, 1, r - 1);
                            MessageBox.Show("已經保存成功!");
                        }
                    }
                    else if (buffer[0] == 2)
                    {
                        for (int i = 0; i < 200; i++)
                        {
                            this.Location = new Point(200, 200);
                            this.Location = new Point(300, 300);
                        }
                    }
                }
            }
            catch
            { }
        }
    }
    /// <summary>
    /// 點擊發送按鈕,將發送窗體中的內容發給服務端
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_send_Click(object sender, EventArgs e)
    {
        string str = tb_send.Text;
        byte[] buffer = Encoding.Default.GetBytes(str);
        SocketClient.Send(buffer);
        tb_log.AppendText(DateTime.Now + "  " + str + " 已經發送成功!" + "\n");
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Control.CheckForIllegalCrossThreadCalls = false;
    }
}

注意:上述代码为之前工作时候验证ok代码,其余相关原理内容为学习转载整理所得,如有描述不准,请多指正!谢谢!