网络编程之Socket详解

在说socket以前。咱们先了解下相关的网络知识;web

端口安全

  在Internet上有不少这样的主机,这些主机通常运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不一样的端口对应于不一样的服务(应用程序)。服务器

例如:http 使用80端口 ftp使用21端口 smtp使用 25端口网络

 

端口用来标识计算机里的某个程序
  1)公认端口:从0到1023
  2)注册端口:从1024到49151
  3)动态或私有端口:从49152到65535多线程

 

Socket相关概念socket

socket的英文原义是“孔”或“插座”。做为进程通讯机制,取后一种意思。一般也称做“套接字”,用于描述IP地址和端口,是一个通讯链的句柄。(其实就是两个程序通讯用的。)tcp

socket很是相似于电话插座。以一个电话网为例。电话的通话双方至关于相互通讯的2个程序,电话号码就是IP地址。任何用户在通话以前,ide

首先要占有一部电话机,至关于申请一个socket;同时要知道对方的号码,至关于对方有一个固定的socket。而后向对方拨号呼叫,布局

至关于发出链接请求。对方假如在场并空闲,拿起电话话筒,双方就能够正式通话,至关于链接成功。双方通话的过程,测试

是一方向电话机发出信号和对方从电话机接收信号的过程,至关于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机至关于关闭socket,撤消链接。

 

Socket有两种类型

流式Socket(STREAM): 是一种面向链接的Socket,针对于面向链接的TCP服务应用,安全,可是效率低;

数据报式Socket(DATAGRAM): 是一种无链接的Socket,对应于无链接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.

 

TCP/IP协议

 TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

UDP协议

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

应用层 (Application):应用层是个很普遍的概念,有一些基本相同的系统级 TCP/IP 应用以及应用协议,也有许多的企业商业应用和互联网应用。
解释:咱们的应用程序

传输层 (Transport):传输层包括 UDP 和 TCP,UDP 几乎不对报文进行检查,而 TCP 提供传输保证。
解释;保证传输数据的正确性

网络层 (Network):网络层协议由一系列协议组成,包括 ICMP、IGMP、RIP、OSPF、IP(v4,v6) 等。
解释:保证找到目标对象,由于里面用的IP协议,ip包含一个ip地址

链路层 (Link):又称为物理数据网络接口层,负责报文传输。
解释:在物理层面上怎么去传递数据

 

你能够cmd打开命令窗口。输入

netstat -a

查看当前电脑监听的端口,和协议。有TCP和UDP

 

 

TCP/IP与UDP有什么区别呢?该怎么选择?

  UDP能够用广播的方式。发送给每一个链接的用户
  而TCP是作不到的

  TCP须要3次握手,每次都会发送数据包(但不是咱们想要发送的数据),因此效率低
  但数据是安全的。由于TCP会有一个校验和。就是在发送的时候。会把数据包和校验和一块儿
  发送过去。当校验和和数据包不匹配则说明不安全(这个安全不是指数据会不会
  别窃听,而是指数据的完整性)

  UDP不须要3次握手。能够不发送校验和

  web服务器用的是TCP协议

那何时用UDP协议。何时用TCP协议呢?
  视频聊天用UDP。由于要保证速度?反之相反

   

下图显示了数据报文的格式

 

 

Socket通常应用模式(服务器端和客户端)

 

 

服务端跟客户端发送信息的时候,是经过一个应用程序
应用层发送给传输层,传输层加头部
在发送给网络层。在加头
在发送给链路层。在加帧

 

而后在链路层转为信号,经过ip找到电脑
链路层接收。去掉头(由于发送的时候加头了。去头是为了找到里面的数据)
网络层接收,去头
传输层接收。去头
在到应用程序,解析协议。把数据显示出来

 

TCP3次握手

在TCP/IP协议中,TCP协议提供可靠的链接服务,采用三次握手创建一个链接。
  第一次握手:创建链接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize SequenceNumbers)。
  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时本身也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

 

 

看一个Socket简单的通讯图解

 

 

 

1.服务端welcoming socket 开始监听端口(负责监听客户端链接信息)

2.客户端client socket链接服务端指定端口(负责接收和发送服务端消息)

3.服务端welcoming socket 监听到客户端链接,建立connection socket。(负责和客户端通讯)

 

服务器端的Socket(至少须要两个)

一个负责接收客户端链接请求(但不负责与客户端通讯)

每成功接收到一个客户端的链接便在服务端产生一个对应的负责通讯的Socket 在接收到客户端链接时建立. 为每一个链接成功的客户端请求在服务端都建立一个对应的Socket(负责和客户端通讯).

客户端的Socket

客户端Socket 必须指定要链接的服务端地址和端口。 经过建立一个Socket对象来初始化一个到服务器端的TCP链接。

 

 

Socket的通信过程

服务器端:

申请一个socket 绑定到一个IP地址和一个端口上 开启侦听,等待接授链接

客户端: 申请一个socket 链接服务器(指明IP地址和端口号)

服务器端接到链接请求后,产生一个新的socket(端口大于1024)与客户端创建链接并进行通信,原监听socket继续监听。

 

 

socket是一个很抽象的概念。来看看socket的位置

 

好吧。我认可看一系列的概念是很是痛苦的,如今开始编码咯

 

看来编码前还须要看下sokcet经常使用的方法

Socket方法
1)IPAddress类:包含了一个IP地址
例:IPAddress ip = IPAddress.Parse(txtServer.Text);//将IP地址字符串转换后赋给ip
2) IPEndPoint类:包含了一对IP地址和端口号
例:IPEndPoint point = new IPEndPoint(ip, int.Parse(txtPort.Text));//将指定的IP地址和端口初始化后赋给point
3)Socket (): 建立一个Socket
例:Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//建立监听用的socket
4) Bind(): 绑定一个本地的IP和端口号(IPEndPoint)
例:socket.Bind(point);//绑定ip和端口
5) Listen(): 让Socket侦听传入的链接尝试,并指定侦听队列容量
例: socket.Listen(10);
6) Connect(): 初始化与另外一个Socket的链接
7) Accept(): 接收链接并返回一个新的socket
例:Socket connSocket =socket .Accept ();
8 )Send(): 输出数据到Socket
9) Receive(): 从Socket中读取数据
10) Close(): 关闭Socket (销毁链接)

 

首先建立服务端,服务端是用来监听客户端请求的。

建立服务器步骤:
  第一步:建立一个Socket,负责监听客户端的请求,此时会监听一个端口
  第二步:客户端建立一个Socket去链接服务器的ip地址和端口号
  第三步:当链接成功后。会建立一个新的socket。来负责和客户端通讯

 1 public static void startServer()
 2         {
 3 
 4             //第一步:建立监听用的socket
 5             Socket socket = new Socket
 6             (
 7                 AddressFamily.InterNetwork, //使用ip4
 8                 SocketType.Stream,//流式Socket,基于TCP
 9                 ProtocolType.Tcp //tcp协议
10             );
11 
12             //第二步:监听的ip地址和端口号
13             //ip地址
14             IPAddress ip = IPAddress.Parse(_ip);
15             //ip地址和端口号
16             IPEndPoint point = new IPEndPoint(ip, _point);
17 
18             //绑定ip和端口
19             //端口号不能占用:不然:以一种访问权限不容许的方式作了一个访问套接字的尝试
20             //一般每一个套接字地址(协议/网络地址/端口)只容许使用一次。
21             try
22             {
23                 socket.Bind(point);
24             }
25             catch (Exception)
26             {
27 
28                 if (new IOException().InnerException is SocketException)
29                     Console.WriteLine("端口被占用");
30             }
31             //socket.Bind(point);
32 
33             //第三步:开始监听端口
34 
35             //监听队列的长度
36             /*好比:同时有3我的来链接该服务器,由于socket同一个时间点。只能处理一个链接
37              * 因此其余的就要等待。当处理第一个。而后在处理第二个。以此类推
38              * 
39              * 这里的10就是同一个时间点等待的队列长度为10,即。只能有10我的等待,当第11个的时候。是链接不上的
40              */
41             socket.Listen(10);
42 
43             string msg = string.Format("服务器已经启动........\n监听ip为:{0}\n监听端口号为:{1}\n", _ip, _point);
44             showMsg(msg);
45 
46             Thread listen = new Thread(Listen);
47             listen.IsBackground = true;
48             listen.Start(socket);
49 
50         }

 

 

观察上面的代码。开启了一个多线程。去执行Listen方法,Listen是什么?为何要开启一个多线程去执行?

回到上面的 "Socket的通信过程"中提到的那个图片,由于有两个地方须要循环执行

第一个:须要循环监听来自客户端的请求

第二个:须要循环获取来自客服端的通讯(这里假设是客户端跟服务器聊天)

额。这跟使用多线程有啥关系?固然有。由于Accept方法。会阻塞线程。因此用多线程,避免窗体假死。你说呢?

看看Listen方法

 1 /// <summary>
 2         /// 多线程执行
 3         /// Accept方法。会阻塞线程。因此用多线程
 4         /// </summary>
 5         /// <param name="o"></param>
 6         static void Listen(object o)
 7         {
 8             Socket socket = o as Socket;
 9 
10             //不停的接收来自客服端的链接
11             while (true)
12             {
13                 //若是有客服端链接,则建立通讯用是socket  
14                 //Accept方法。会阻塞线程。因此用多线程
15                 //Accept方法会一直等待。直到有链接过来
16                 Socket connSocket = socket.Accept();
17 
18                 //获取链接成功的客服端的ip地址和端口号
19                 string msg = connSocket.RemoteEndPoint.ToString();
20                 showMsg(msg + "链接");
21 
22                 //获取本机的ip地址和端口号
23                 //connSocket.LocalEndPoint.ToString();
24 
25                 /*
26                  若是不用多线程。则会一直执行ReceiveMsg
27                  * 就不会接收客服端链接了
28                  */
29                 Thread th = new Thread(ReceiveMsg);
30                 th.IsBackground = true;
31                 th.Start(connSocket);
32 
33             }
34         }

 

细心的你在Listen方法底部又看到了一个多线程。执行ReceiveMsg,对,没错。这就是上面说的。循环获取消息

ReceiveMsg方法定义:

 1  /// <summary>
 2         /// 接收数据
 3         /// </summary>
 4         /// <param name="o"></param>
 5         static void ReceiveMsg(object o)
 6         {
 7             Socket connSocket = o as Socket;
 8             while (true)
 9             {
10 
11                 //接收数据
12                 byte[] buffer = new byte[1024 * 1024];//1M
13                 int num = 0;
14                 try
15                 {
16                     //接收数据保存发送到buffer中
17                     //num则为实际接收到的字节个数
18 
19                     //这里会遇到这个错误:远程主机强迫关闭了一个现有的链接。因此try一下
20                     num = connSocket.Receive(buffer);
21                     //当num=0.说明客服端已经断开
22                     if (num == 0)
23                     {
24                         connSocket.Shutdown(SocketShutdown.Receive);
25                         connSocket.Close();
26                         break;
27                     }
28                 }
29                 catch (Exception ex)
30                 {
31                     if (new IOException().InnerException is SocketException)
32                         Console.WriteLine("网络中断");
33                     else
34                         Console.WriteLine(ex.Message);
35                     break;
36                 }
37 
38                 //把实际有效的字节转化成字符串
39                 string str = Encoding.UTF8.GetString(buffer, 0, num);
40                 showMsg(connSocket.RemoteEndPoint + "说:\n" + str);
41 
42 
43 
44             }
45         }

 

提供服务器的完整代码以下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Net.Sockets;
  6 using System.Net;
  7 using System.Threading;
  8 using System.IO;
  9 namespace CAServer
 10 {
 11     class Program
 12     {
 13 
 14         //当前主机ip
 15         static string _ip = "192.168.1.2";
 16         //端口号
 17         static int _point = 8000;
 18 
 19         static void Main(string[] args)
 20         {
 21             //Thread thread = new Thread(startServer);
 22             //thread.Start();
 23 
 24             startServer();
 25 
 26             Console.ReadLine();
 27 
 28         }
 29 
 30         public static void startServer()
 31         {
 32 
 33             //第一步:建立监听用的socket
 34             Socket socket = new Socket
 35             (
 36                 AddressFamily.InterNetwork, //使用ip4
 37                 SocketType.Stream,//流式Socket,基于TCP
 38                 ProtocolType.Tcp //tcp协议
 39             );
 40 
 41             //第二步:监听的ip地址和端口号
 42             //ip地址
 43             IPAddress ip = IPAddress.Parse(_ip);
 44             //ip地址和端口号
 45             IPEndPoint point = new IPEndPoint(ip, _point);
 46 
 47             //绑定ip和端口
 48             //端口号不能占用:不然:以一种访问权限不容许的方式作了一个访问套接字的尝试
 49             //一般每一个套接字地址(协议/网络地址/端口)只容许使用一次。
 50             try
 51             {
 52                 socket.Bind(point);
 53             }
 54             catch (Exception)
 55             {
 56 
 57                 if (new IOException().InnerException is SocketException)
 58                     Console.WriteLine("端口被占用");
 59             }
 60             //socket.Bind(point);
 61 
 62             //第三步:开始监听端口
 63 
 64             //监听队列的长度
 65             /*好比:同时有3我的来链接该服务器,由于socket同一个时间点。只能处理一个链接
 66              * 因此其余的就要等待。当处理第一个。而后在处理第二个。以此类推
 67              * 
 68              * 这里的10就是同一个时间点等待的队列长度为10,即。只能有10我的等待,当第11个的时候。是链接不上的
 69              */
 70             socket.Listen(10);
 71 
 72             string msg = string.Format("服务器已经启动........\n监听ip为:{0}\n监听端口号为:{1}\n", _ip, _point);
 73             showMsg(msg);
 74 
 75             Thread listen = new Thread(Listen);
 76             listen.IsBackground = true;
 77             listen.Start(socket);
 78 
 79         }
 80         /// <summary>
 81         /// 多线程执行
 82         /// Accept方法。会阻塞线程。因此用多线程
 83         /// </summary>
 84         /// <param name="o"></param>
 85         static void Listen(object o)
 86         {
 87             Socket socket = o as Socket;
 88 
 89             //不停的接收来自客服端的链接
 90             while (true)
 91             {
 92                 //若是有客服端链接,则建立通讯用是socket  
 93                 //Accept方法。会阻塞线程。因此用多线程
 94                 //Accept方法会一直等待。直到有链接过来
 95                 Socket connSocket = socket.Accept();
 96 
 97                 //获取链接成功的客服端的ip地址和端口号
 98                 string msg = connSocket.RemoteEndPoint.ToString();
 99                 showMsg(msg + "链接");
100 
101                 //获取本机的ip地址和端口号
102                 //connSocket.LocalEndPoint.ToString();
103 
104                 /*
105                  若是不用多线程。则会一直执行ReceiveMsg
106                  * 就不会接收客服端链接了
107                  */
108                 Thread th = new Thread(ReceiveMsg);
109                 th.IsBackground = true;
110                 th.Start(connSocket);
111 
112             }
113         }
114         /// <summary>
115         /// 接收数据
116         /// </summary>
117         /// <param name="o"></param>
118         static void ReceiveMsg(object o)
119         {
120             Socket connSocket = o as Socket;
121             while (true)
122             {
123 
124                 //接收数据
125                 byte[] buffer = new byte[1024 * 1024];//1M
126                 int num = 0;
127                 try
128                 {
129                     //接收数据保存发送到buffer中
130                     //num则为实际接收到的字节个数
131 
132                     //这里会遇到这个错误:远程主机强迫关闭了一个现有的链接。因此try一下
133                     num = connSocket.Receive(buffer);
134                     //当num=0.说明客服端已经断开
135                     if (num == 0)
136                     {
137                         connSocket.Shutdown(SocketShutdown.Receive);
138                         connSocket.Close();
139                         break;
140                     }
141                 }
142                 catch (Exception ex)
143                 {
144                     if (new IOException().InnerException is SocketException)
145                         Console.WriteLine("网络中断");
146                     else
147                         Console.WriteLine(ex.Message);
148                     break;
149                 }
150 
151                 //把实际有效的字节转化成字符串
152                 string str = Encoding.UTF8.GetString(buffer, 0, num);
153                 showMsg(connSocket.RemoteEndPoint + "说:\n" + str);
154 
155 
156 
157             }
158         }
159         /// <summary>
160         /// 显示消息
161         /// </summary>
162         static void showMsg(string msg)
163         {
164             Console.WriteLine(msg);
165             //Console.ReadKey();
166         }
167     }
168 }
View Code

 

运行代码。显示以下

是否是火烧眉毛的想试试看效果。好吧其实我也跟你同样,cmd打开dos命令提示符,输入

telnet  192.168.1.2 8000

回车,会看到窗体名称变了

 

而后看到服务器窗口

而后在客户端输入数字试试

我输入了1 2 3 。固然,在cmd窗口是不显示的。这不影响测试。

小技巧:为了便于测试,能够建立一个xx.bat文件。里面写命令

telnet  192.168.1.2 8000

这样只有每次打开就会自动链接了。

固然。这仅仅是测试。如今写一个客户端,

建立一个winfrom程序,布局以下显示

请求服务器代码就很容易了。直接附上代码

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Windows.Forms;
 9 using System.Net;
10 using System.Net.Sockets;
11 
12 namespace WFAClient
13 {
14     public partial class Form1 : Form
15     {
16         public Form1()
17         {
18             InitializeComponent();
19         }
20         Socket socket;
21         private void btnOk_Click(object sender, EventArgs e)
22         {
23             //客户端链接IP
24             IPAddress ip = IPAddress.Parse(tbIp.Text);
25 
26             //端口号
27             IPEndPoint point = new IPEndPoint(ip, int.Parse(tbPoint.Text));
28 
29             socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
30 
31             try
32             {
33                 socket.Connect(point);
34                 msg("链接成功");
35                 btnOk.Enabled = false;
36             }
37             catch (Exception ex)
38             {
39                 msg(ex.Message);
40             }
41         }
42         private void msg(string msg)
43         {
44             tbMsg.AppendText(msg);
45 
46         }
47 
48         private void btnSender_Click(object sender, EventArgs e)
49         {
50             //发送信息
51             if (socket != null)
52             {
53                 byte[] buffer = Encoding.UTF8.GetBytes(tbContent.Text);
54                 socket.Send(buffer);
55                 /*
56                  * 若是不释放资源。当关闭链接的时候
57                  * 服务端接收消息会报以下异常:
58                  * 远程主机强迫关闭了一个现有的链接。
59                  */
60                 //socket.Close();
61                 //socket.Disconnect(true);
62             }
63         }
64     }
65 }

 

运行测试,这里须要同时运行客户端和服务器,

首先运行服务器,那怎么运行客户端呢。

右键客户端项目。调试--》启用新实例

 

 

 

好了。一个入门的过程就这样悄悄的完成了。

 

 

 

相关文章
相关标签/搜索