从socket开始学习网络编程

1 URL快速下载(上传)。使用WebClinet对URL进行浏览并下载,可以说代码清晰、支持丰富。包括编码格式下载格式异步下载Form上传参数拼接等等各种。

 WebClient mywebclient = new WebClient();
 //下载为字符串
var websiteString = mywebclient.DownloadString("123.com");
//下载为二进制
var websitedata = mywebclient.DownloadData("123.com");
//异步下载并监听完成
 mywebclient.DownloadStringCompleted += (Object Sender,DownloadStringCompletedEventArgs e) =>{
 Console.Write("");
};

HTTP请求构造。在很多场景中,需要伪造Referer、UserAgent、ContentType等等,从一个语言的HTTP库对HTTP协议的支持细腻程度可以看出其是否亲爬虫,幸运的是,HttpWebRequest确实足够全面,能够满足所有的自定义需求。

 var myhttpWebRequset = (HttpWebRequest)WebRequest.Create(new Uri(@"htttp://www.baidu.com"));
            myhttpWebRequset.Method = "post";
            myhttpWebRequset.ContentType = "application/x-www-form-urlencoded";
            myhttpWebRequset.ContentLength = 100;
            myhttpWebRequset.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)";

Cookies处理。虽然Cookies已经逐渐淡出历史的舞台,但依然有大量的Web开发框架是以Cookie为支撑做Session体系的,所以Cookie的灵活操作也非常重要。

  var myhttpWebRequset = (HttpWebRequest)WebRequest.Create(new Uri(@"htttp://www.baidu.com"));
            myhttpWebRequset.Referer = @"http://www.baidu.com";
  var cookieContainer = new CookieContainer();
            cookieContainer.Add(new Uri(@"http://baidu.com"), new Cookie("key", "1"));
            myhttpWebRequset.CookieContainer = cookieContainer;

代理服务。有时候目标服务器会对IP访问做限制,这时候使用代理服务器以及不停的更换代理服务器就非常重要了,如下处理也很简洁

 var mywebProxy = new WebProxy(new Uri(@"127.0.0.1:8080"));
            myhttpWebRequset.Proxy = mywebProxy;

socket概念

Socket的中文释义称为套接字,是支撑TCP/IP通信最基本的操作单元。可以将Socket看做不同主机之间的进程进行双向通信的端点,在一个双方都可以通信的Socket实例中,既保存了对方的IP地址和端口,也保持了双方通信采用的协议等信息。

 

①.流套接字:实现面向连接的TCP通信

②.数据报套接字:实现无连接的UDP通信

③.原始套接字:实现IP数据包的通信(这里不做讨论)

端口

网络协议中使用端口号识别主机上不同的进程。

Tcp

TCP是一种面向连接的、可靠的,基于字节流的传输层通信协议。

TCP的工作过程

TCP是面向连接的协议,TCP协议通过三个报文段完成类似电话呼叫的连接建立过程,这个过程称为三次握手

第一次握手:建立连接时,客户端发送SYN包(SEQ=x)到服务器,并进入SYN_SEND状态,等待服务器确认。

第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=x+1),同时自己也发送一个SYN包(SEQ=y),即SYN+ACK包,此时服务器进入SYN_RECV状态。

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

传输数据

一旦通信双方建立了TCP连接,连接中的任何一方都能向对方发送数据和接收对方发来的数据。TCP协议负责把用户数据(字节流)按一定的格式和长度组成多个数据报进行发送,并在接收到数据报之后按分解顺序重新组装和恢复用户数据。
利用TCP传输数据时,数据是以字节流的形式进行传输的。

tcp协议负责把数据按格式和长度发送并接受后组装。

连接的终止

建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。 

TCP最主要的特点如下。
(1) 是面向连接的协议。
(2) 端到端的通信。每个TCP连接只能有两个端点,而且只能一对一通信,不能一点对多点直接通信。
(3) 高可靠性。通过TCP连接传送的数据,能保证数据无差错、不丢失、不重复地准确到达接收方,并且保证各数据到达的顺序与其发出的顺序相同。
(4) 全双工方式传输。
(5) 数据以字节流的方式传输。
(6) 传输的数据无消息边界。

 

 同步与异步

同步工作方式是指利用TCP编写的程序执行到监听或接收语句时,在未完成工作(侦听到连接请求或收到对方发来的数据)前不再继续往下执行,线程处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句。
异步工作方式是指程序执行到监听或接收语句时,不论工作是否完成,都会继续往下执行。

 

UDP是一种简单的、面向数据报的无连接的协议,提供的是不一定可靠的传输服务。所谓“无连接”是指在正式通信前不必与对方先建立连接,不管对方状态如何都直接发送过去。这与发手机短信非常相似,只要知道对方的手机号就可以了,不要考虑对方手机处于什么状态。UDP虽然不能保证数据传输的可靠性,但数据传输的效率较高。

 UDP与TCP的区别
(1) UDP可靠性不如TCP
TCP包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其他信息,否则将一直等待直到收到确认信息为止。与TCP不同,UDP并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP称为不可靠的传输协议。
(2) UDP不能保证有序传输
UDP不能确保数据的发送和接收顺序。对于突发性的数据报,有可能会乱序。

UDP的优势

  1. UDP速度比TCP快
    由于UDP不需要先与对方建立连接,也不需要传输确认,因此其数据传输速度比TCP快得多。对于强调传输性能而不是传输完整性的应用(比如网络音频播放、视频点播和网络会议等),使用UDP比较合适,因为它的传输速度快,使通过网络播放的视频音质好、画面清晰。
    (2) UDP有消息边界
    发送方UDP对应用程序交下来的报文,在添加首部后就向下直接交付给IP层。既不拆分,也不合并,而是保留这些报文的边界。使用UDP不需要考虑消息边界问题,这样使得UDP编程相比TCP,在对接收到的数据的处理方面要方便的多。在程序员看来,UDP套接字使用比TCP简单。UDP的这一特征也说明了它是一种面向报文的传输协议。
    (3) UDP可以一对多传输
    由于传输数据不建立连接,也就不需要维护连接状态(包括收发状态等),因此一台服务器可以同时向多个客户端传输相同的消息。利用UDP可以使用广播或组播的方式同时向子网上的所有客户进程发送消息,这一点也比TCP方便。
    其中,速度快是UDP的首要优势
    由于TCP协议中植入了各种安全保障功能,在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重影响。反观UDP,由于抛弃了信息可靠传输机制,将安全和排序等功能移交给上层应用完成,极大地降低了执行时间,使速度得到了保证。简而言之,UDP的“理念”就是“不顾一切,只为更快地发送数据”。

根据socket通信基本流程图,总结通信的基本步骤:

服务器端:

第一步:创建一个用于监听连接的Socket对像;

第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;

第三步:用socket对像的Bind()方法绑定EndPoint;

第四步:用socket对像的Listen()方法开始监听;

第五步:接收到客户端的连接,用socket对像的Accept()方法创建一个新的用于和客户端进行通信的socket对像;

第六步:通信结束后一定记得关闭socket;

客户端:

第一步:建立一个Socket对像;

第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;

第三步:用socket对像的Connect()方法以上面建立的EndPoint对像做为参数,向服务器发出连接请求;

第四步:如果连接成功,就用socket对像的Send()方法向服务器发送信息;

第五步:用socket对像的Receive()方法接受服务器发来的信息 ;

第六步:通信结束后一定记得关闭socket;

在服务端

 

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
           //定义回调:解决跨线程访问问题
          private delegate void SetTextValueCallBack(string strValue);
        //定义接收客户端发送消息的回调
        private delegate void ReceiveMsgCallBack(string strReceive);
        //声明回调
        private SetTextValueCallBack setCallBack;
        //声明
        private ReceiveMsgCallBack receiveCallBack;
        //定义回调:给ComboBox控件添加元素
        private delegate void SetCmbCallBack(string strItem);
        //声明
        private SetCmbCallBack setCmbCallBack;
        //定义发送文件的回调
        private delegate void SendFileCallBack(byte[] bf);
        //声明
        private SendFileCallBack sendCallBack;
        //用于通信的Socket
        Socket socketSend;
        //用于监听的SOCKET
        Socket socketWatch;

        //将远程连接的客户端的IP地址和Socket存入集合中
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        //创建监听连接的线程
        Thread AcceptSocketThread;

        //接收客户端发送消息的线程
        Thread threadReceive;

        private void btn_Start_Click(object sender, EventArgs e)
        {
            //当点击开始监听的时候 在服务器端创建一个负责监听IP地址和端口号的Socket
            socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //获取ip地址
            IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim());
            //创建端口号
            IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(this.txt_Port.Text.Trim()));
            //绑定IP地址和端口号
            socketWatch.Bind(point);
            this.txt_Log.AppendText("监听成功" + " \r \n");
            //开始监听:设置最大可以同时连接多少个请求
            socketWatch.Listen(10);
            setCallBack = new SetTextValueCallBack(SetTextValue);
            receiveCallBack = new ReceiveMsgCallBack(ReceiveMsg);
            setCmbCallBack = new SetCmbCallBack(AddCmbItem);
            sendCallBack = new SendFileCallBack(SendFile);

            //创建线程
            AcceptSocketThread = new Thread(new ParameterizedThreadStart(StartListen));



        } 
             /// <summary>
         /// 等待客户端的连接,并且创建与之通信用的Socket
         /// </summary>
         /// <param name="obj"></param>
         private void StartListen(object obj)
         {
             Socket socketWatch = obj as Socket;
              while (true)
              {               
                //等待客户端的连接,并且创建一个用于通信的Socket
                  socketSend = socketWatch.Accept();
                  //获取远程主机的ip地址和端口号
                   string strIp = socketSend.RemoteEndPoint.ToString();
                   dicSocket.Add(strIp, socketSend);
                   this.cmb_Socket.Invoke(setCmbCallBack, strIp);
                   string strMsg = "远程主机:" + socketSend.RemoteEndPoint + "连接成功";
                  //使用回调
                     txt_Log.Invoke(setCallBack, strMsg);
 
                  //定义接收客户端消息的线程
                  Thread threadReceive = new Thread(new ParameterizedThreadStart(Receive));
                 threadReceive.IsBackground = true;
                threadReceive.Start(socketSend);
  
            }
        }


                  /// <summary>
          /// 服务器端不停的接收客户端发送的消息
          /// </summary>
          /// <param name="obj"></param>
          private void Receive(object obj)
          {
              Socket socketSend = obj as Socket;
              while (true)
              {
                //客户端连接成功后,服务器接收客户端发送的消息
                  byte[] buffer = new byte[2048];
                  //实际接收到的有效字节数
                  int count = socketSend.Receive(buffer);
                  if (count == 0)//count 表示客户端关闭,要退出循环
                  {
                      break;
                  }
                  else
                  {
                      string str = Encoding.Default.GetString(buffer, 0, count);
                      string strReceiveMsg = "接收:" + socketSend.RemoteEndPoint + "发送的消息:" + str;
                      txt_Log.Invoke(receiveCallBack, strReceiveMsg);
                  }
              }
          }
 

        /// <summary>
        /// 回调委托需要执行的方法
        /// </summary>
        /// <param name="strValue"></param>
        private void SetTextValue(string strValue)
         {
              this.txt_Log.AppendText(strValue + " \r \n");
          }

    private void txt_Port_TextChanged(object sender, EventArgs e)
        {

        } 
          private void ReceiveMsg(string strMsg)
         {
             this.txt_Log.AppendText(strMsg + " \r \n");
         }
  
         private void AddCmbItem(string strItem)
         {
             this.cmb_Socket.Items.Add(strItem);
        }

         private void SendFile(byte[] sendBuffer)
         {
 
             try
              {
                 dicSocket[cmb_Socket.SelectedItem.ToString()].Send(sendBuffer, SocketFlags.None);
             }
           catch (Exception ex)
             {
                  MessageBox.Show("发送文件出错:"+ex.Message);
             }
         }
        /// 服务器给客户端发送消息
        private void btn_Send_Click_Click(object sender, EventArgs e)
        {
            try
            {
                string strMsg = this.txt_Msg.Text.Trim();
                byte[] buffer = Encoding.Default.GetBytes(strMsg);
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(buffer);
                //将泛型集合转换为数组
                byte[] newBuffer = list.ToArray();
                //获得用户选择的IP地址
                string ip = this.cmb_Socket.SelectedItem.ToString();
                dicSocket[ip].Send(newBuffer);
            }
            catch (Exception ex)
            {

            }

        }

 
        private void btn_StopListen_Click_1(object sender, EventArgs e)
        {
            socketWatch.Close();
            socketSend.Close();
            //终止线程
            AcceptSocketThread.Abort();
            threadReceive.Abort();
        }
    }

2 在客户端

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

        //创建连接的Socket
        Socket socketSend;
        //创建接收客户端发送消息的线程
        Thread threadReceive; 

        /// <summary>
        /// 回调委托需要执行的方法
        /// </summary>
        /// <param name="strValue"></param>
        private void SetTextValue(string strValue)
        {
            this.txt_Log.AppendText(strValue + " \r \n");
        }

 
        private void ReceiveMsg(string strMsg)
        {
            this.txt_Log.AppendText(strMsg + " \r \n");
        } 
       //连接
        private void btn_Connect_Click(object sender, EventArgs e)
        {
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim());
            socketSend.Connect(ip, Convert.ToInt32(this.txt_Port.Text.Trim()));
            this.txt_Log.Invoke(new MethodInvoker(()=> { SetTextValue("连接成功"); }));
            //开启一个新的线程不停的接收服务器发送消息的线程
            threadReceive = new Thread(new ThreadStart(Receive));
            //设置为后台线程
            threadReceive.IsBackground = true;
            threadReceive.Start();
        }

        /// 接口服务器发送的消息
        private void Receive()
        {
            while (true) {
                byte[] buffer = new byte[2048];
                //实际接收到的字节数
                int r = socketSend.Receive(buffer);
                if (r == 0)
                {
                    break;
                }
                else
                {
                    if (buffer[0] == 0)//表示发送的是文字消息
                    {
                        string str = Encoding.Default.GetString(buffer, 1, r - 1);
                        this.txt_Log.Invoke(new MethodInvoker(() => { SetTextValue("接收远程服务器:" + socketSend.RemoteEndPoint + "发送的消息:" + str); }));
                      

                    }
                }
            }

        }

        private void btn_StopListen_Click_1(object sender, EventArgs e)
        {
            socketSend.Close(); 
            //终止线程
            threadReceive.Abort(); 
        }

        private void btn_Send_Click(object sender, EventArgs e)
        {
            string strMsg = this.txt_Msg.Text.Trim();
            byte[] buffer = new byte[2048];
            buffer = Encoding.Default.GetBytes(strMsg);
            int receive = socketSend.Send(buffer);
        }
    }

  我们可以通过TcpClient对象的GetStream()方法获取该对象发送和接收数据的 NetworkStream 对象:

            TcpClient client = new TcpClient();
            client.Connect("www.baidu.com", 8099);
            NetworkStream nStream = client.GetStream();

也可以通过使用Socket来获取 NetworkStream 对象:

NetworkStream myNetworkStream = new NetworkStream(mySocket); 

扩展:

WebClient类

提供向URI标识的任何本地、Intranet或Internet资源发送数据以及从这些资源接收数据的公共方法。

GET获取数据

WebClient client = new WebClient();
//设置编码格式
client.Encoding = System.Text.Encoding.UTF8;
//获取数据
var result = client.DownloadString(url);

post

WebClient client = new WebClient();
//添加请求头
client.Headers.Add("key", "value");
//创建请求体
NameValueCollection nvc = new NameValueCollection();
nvc.Add("key1", "value1");
string url = "http://wf.h5120.com:5280/admin/users/";
//提交数据
var btys = client.UploadValues(url, nvc);
//解析提交返回结果
string str = System.Text.Encoding.UTF8.GetString(btys);

公共构造函数

WebClient 构造函数初始化 WebClient 类的新实例。

公共属性

BaseAddress获取或设置 WebClient 发出请求的基 URI。
Container(从 Component 继承)获取 IContainer,它包含 Component。
Credentials获取或设置用于对向 Internet 资源的请求进行身份验证的网络凭据。
Headers获取或设置与请求关联的标头名称/值对集合。
QueryString获取或设置与请求关联的查询名称/值对集合。
ResponseHeaders获取与响应关联的标头名称/值对集合。
Site(从 Component 继承)获取或设置 Component 的 ISite。

公共方法

CreateObjRef(从 MarshalByRefObject 继承)创建一个对象,该对象包含生成用于与远程对象进行通讯的代理所需的全部相关信息。
Dispose(从 Component 继承)已重载。释放由 Component 占用的资源。
DownloadData从具有指定 URI 的资源下载数据。
DownloadFile从具有指定 URI 的资源将数据下载到本地文件。
Equals(从 Object 继承)已重载。确定两个 Object 实例是否相等。
GetHashCode(从 Object 继承)用作特定类型的哈希函数,适合在哈希算法和数据结构(如哈希表)中使用。
GetLifetimeService(从 MarshalByRefObject 继承)检索控制此实例的生存期策略的当前生存期服务对象。
GetType(从 Object 继承)获取当前实例的 Type。
InitializeLifetimeService(从 MarshalByRefObject 继承)获取控制此实例的生存期策略的生存期服务对象。
OpenRead为从具有指定 URI 的资源下载的数据打开一个可读的流。
OpenWrite已重载。打开一个流以将数据写入具有指定 URI 的资源。
ToString(从 Object 继承)返回表示当前 Object 的 String。
UploadData已重载。将数据缓冲区上载到具有指定 URI 的资源。
UploadFile已重载。将本地文件上载到具有指定 URI 的资源。
UploadValues已重载。将名称/值集合上载到具有指定 URI 的资源。

从上表中我们可以看到WebClient提供四种将数据上载到资源的方法:

  • OpenWrite 返回一个用于将数据发送到资源的 Stream。
  • UploadData 将字节数组发送到资源并返回包含任何响应的字节数组。
  • UploadFile 将本地文件发送到资源并返回包含任何响应的字节数组。
  • UploadValues 将 NameValueCollection 发送到资源并返回包含任何响应的字节数组。

另外WebClient还提供三种从资源下载数据的方法:

  • DownloadData 从资源下载数据并返回字节数组。
  • DownloadFile 从资源将数据下载到本地文件。
  • OpenRead 从资源以 Stream 的形式返回数据。

  下面我们将通过一个简单的应用程序来测试WebClient的最简单用法作为本小节的结束让大家对WebClient有个初步的认识
   例子1:利用WebClient实现对博客园首页的访问
    首先我们用HttpLook对这次访问进行分析,为了方便分析我特别将浏览器对图片的访问去掉 让我们能看到更简便的分析结果

我们可以看到整个过程中我们发起了4次资源请求,其中第一次是对博客园首页进行访问
     第二次访问的是样式表文件,第三和四次访问的是js脚本。
     我们点击第一项可以看见关于这次资源访问的http头部信息,所谓http头部就是我们不能看见的浏览器和远程服务器传递的一些不可见元素。

1GET / HTTP/1.1
2Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
3Accept-Language: zh-cn
4UA-CPU: x86
5Accept-Encoding: gzip, deflate
6User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
7Host: www.cnblogs.com
8Connection: Keep-Alive
9Cookie: .DottextCookie=(隐藏)

  这些http信息包含了浏览器访问的过程。其中
  第一行:请求地址的相对路径和使用协议 相对路径为/ 协议采用http1.1
  第二行:表示我们请求的资源种类。
  第三行:我们的语言是简体中文。
  第四行:我们使用的cup结构。这个http头在一般的网页中并不过见。估计是博客园的一次调查??
  第五行:标示采用gzip方式压缩html编码进行传递。只有一些浏览器支持的gzip解压缩时采用这种方式传递文本。由于我们
  要写的程序不具备gzi解压缩的能力 所以我们不考虑使用这种方式发送请求。
  第六行:浏览器说明
  第七行:当前主机地址
  第八行:连接请求状态
  第九行:cookies信息
 
  我在新建的应用程序里面利用WebClient来实现这了一过程。

 

下面我将就关键实现做一些解释
 

 1WebClient _client=new WebClient();
 2            _client.BaseAddress="http://www.cnblogs.com";
 3            _client.Headers.Add("Accept","image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
 4            _client.Headers.Add("Accept-Language","zh-cn");
 5            _client.Headers.Add("UA-CPU","x86");
 6            //_client.Headers.Add("Accept-Encoding","gzip, deflate");
 7            _client.Headers.Add("User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)");
 8            System.IO.Stream objStream=_client.OpenRead("/");
 9            System.IO.StreamReader _read=new System.IO.StreamReader(objStream,System.Text.Encoding.UTF8);
10            textBox1.Text=_read.ReadToEnd();

  第一行:新建一个WebClient 实例_client
  第二行~第七行:将上边捕捉到的Http头部放入到_client实例,注意第六行的被注释掉了。因为我们的程序无法进行gzip解码所以如果这样请求
  获得的资源可能无法解码。当然我们可以给程序加入gzip处理的模块 那是题外话了。
  第八行:利用_client.OpenRead(string URI)的方法获取网上资源的Stream
  第九行:利用StreamReader将Stream用我们需要的编码方法去解析。这里使用了UTF8。对应不同的网站可以使用Default等不同的解码方法。
  第十行:将我们解码后的内容放到textBox1里面显示出来

 

 

参考文献:

https://blog.csdn.net/zhanglei5415/article/details/1685453?utm_source=blogxgwz5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值