https://www.cnblogs.com/asdyzh/p/9839775.html
我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念:
- TCP/IP层次模型
当然这里我们只讨论重要的四层
01,应用层(Application):应用层是个很广泛的概念,有一些基本相同的系统级TCP/IP应用以及应用协议,也有许多的企业应用和互联网应用。http协议在应用层运行。
02,传输层(Tanspot):传输层包括UDP和TCP,UDP几乎不对报文进行检查,而TCP
提供传输保证。
03,网络层(Netwok):网络层协议由一系列协议组成,包括ICMP、IGMP、RIP、OSPF、IP(v4,v6)等。
04,链路层(Link):又称为物理数据网络接口层,负责报文传输。
然后我们来看下tcp层次模型图
从上图中可以看出,应用程序在应用层运行,在传输层,在数据前加上了TCP头,在
网络层加上的IP头,在数据链路层加上了帧。
2,端口
端口号范围:0-65535,总共能表示65536个数。
按端口号可分为3大类
(1)公认端口(WellKnownPorts):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
(2)注册端口(RegisteredPorts):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
(3)动态和/或私有端口(Dynamicand/orPrivatePorts):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。
3.TCP和UDP报文
下面一起来看下TCP和UDP的报文图
从图中我们可以看出TCP和UDP中都有校验和,但是在UDP报文中,一般不使用校验和,这样可以加快数据传输的速度,但是数据的准确性可能会受到影响。换句话说,Tcp协议都有校验和,为了保证传输数据的准确性。
3.Socket
Socket包括Ip地址和端口号两部分,程序通过Socket来通信,Socket相当于操作系统的一个组件。Socket作为进程之间通信机制,通常也称作”套接字”,用于描述IP地址和端口号,是一个通信链的句柄。说白了,就是两个程序通信用的。
生活案例对比:
Socket之间的通信可以类比生活中打电话的案例。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket,同时要知道对方的号码,相当于对方有一个固定的Socket,然后向对方拨号呼叫,相当于发出连接请求。假如对方在场并空闲,拿起 电话话筒,双方就可以进行通话了。双方的通话过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机,相当于关闭socket,撤销连接。
注意:Socket不仅可以在两台电脑之间通信,还可以在同一台电脑上的两个程序间通信。
4,端口进阶(深入)
通过IP地址确定了网络中的一台电脑后,该电脑上可能提供很多提供服务的应用,每一个应用都对应一个端口。
在Internet上有很多这样的主机,这些主机一般运行了多个服务软件 ,同时提供几种服务,每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务(应用程序)
例如:http 使用80端口, ftp使用21端口 smtp使用25端口
5.Socket分类
Socket主要有两种类型:
- 流式Socket
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低
2,数据报式Socket
是一种无连接的Socket,对应于无连接的UDP服务应用,不安全,但效率高
6. Socket一般应用模式(服务器端和客户端)
服务器端的Socket(至少需要两个)
01.一个负责接收客户端连接请求(但不负责与客户端通信)
02.每成功接收到客户端的连接便在服务器端产生一个对应的复杂通信的Socket
021.在接收到客户端连接时创建
022. 为每个连接成功的客户端请求在服务器端都创建一个对应的Socket(负责和客户端通信)
客户端的Socket
- 必须指定要连接的服务器地址和端口
- 通过创建一个Socket对象来初始化一个到服务器端的TCP连接
通过上图,我们可以看出,首先服务器会创建一个负责监听的socket,然后客户端通过socket连接到服务器指定端口,最后服务器端负责监听的socket,监听到客户端有连接过来了,就创建一个负责和客户端通信的socket。
下面我们来看下Socket更具体的通信过程:
Socket的通讯过程
服务器端:
01,申请一个socket
02,绑定到一个IP地址和一个端口上
03,开启侦听,等待接收连接
客户端:
01,申请一个socket
02,连接服务器(指明IP地址和端口号)
服务器端接收到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通信,原监听socket继续监听。
注意:负责通信的Socket不能无限创建,创建的数量和操作系统有关。
7.Socket的构造函数
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用来解析地址的寻址方案。例如:InterNetWork指示当Socket使用一个IP版本4地址连接
SocketType:定义要打开的Socket的类型
Socket类使用ProtocolType枚举向Windows Sockets API通知所请求的协议
注意:
1,端口号必须在 1 和 65535之间,最好在1024以后。
2,要连接的远程主机必须正在监听指定端口,也就是说你无法随意连接远程主机。
如:
IPAddress addr = IPAddress.Parse("127.0.0.1");
IPEndPoint endp = new IPEndPoint(addr,,9000);
服务端先绑定:serverWelcomeSocket.Bind(endp)
客户端再连接:clientSocket.Connect(endp)
3,一个Socket一次只能连接一台主机
4,Socket关闭后无法再次使用
5,每个Socket对象只能与一台远程主机连接。如果你想连接到多台远程主机,你必须创建多个Socket对象。
8.Socket常用类和方法
相关类:
IPAddress:包含了一个IP地址
IPEndPoint:包含了一对IP地址和端口号
方法:
Socket():创建一个Socket
Bind():绑定一个本地的IP和端口号(IPEndPoint)
Listen():让Socket侦听传入的连接吃那个病,并指定侦听队列容量
Connect():初始化与另一个Socket的连接
Accept():接收连接并返回一个新的Socket
Send():输出数据到Socket
Receive():从Socket中读取数据
Close():关闭Socket,销毁连接
https://blog.csdn.net/tianlansedeshijie/article/details/95357734
IPEndPoint继承与EndPoint。IPEndPoint引用的时候参数为IP和端口。IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
.net中 Bind(EndPoint localEP);方法参数是EndPoint,填入IPEndPoint会按照EndPoint参数执行。
EndPoint是IPEndPoint的基类,IPEndPoint可以强转为EndPoint。EndPoint ep = (EndPoint)iep;
主要目的是IPEndPoint可以自己写入IP地址和端口。
ServerControl.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Net.Sockets; 6 using System.Text; 7 using System.Threading; 8 using System.Threading.Tasks; 9 10 namespace ServerTest 11 { 12 public class ServerControl 13 { 14 private Socket serverSocket; 15 private List<Socket> clientList; 16 17 public ServerControl() 18 { 19 serverSocket = new Socket(AddressFamily.InterNetwork, 20 SocketType.Stream,ProtocolType.Tcp); 21 clientList = new List<Socket>(); 22 } 23 24 public void Start() 25 { 26 //绑定端口 27 serverSocket.Bind(new IPEndPoint(IPAddress.Any,12321)); 28 serverSocket.Listen(10); 29 Console.WriteLine("服务器启动成功"); 30 31 Thread threadAccept = new Thread(Accept); 32 threadAccept.IsBackground = true; 33 threadAccept.Start(); 34 35 } 36 37 private void Accept() 38 { 39 //接受客户端的方法,会挂起当前线程 40 Socket client = serverSocket.Accept(); 41 EndPoint localpoint = client.LocalEndPoint; 42 EndPoint remotepoint = client.RemoteEndPoint; 43 //IPEndPoint point = client.RemoteEndPoint as IPEndPoint; 44 //Console.WriteLine(point.Address+"["+point.Port+"]连接成功"); 45 Console.WriteLine($"本地ip:{localpoint}"); 46 Console.WriteLine($"远程ip:{remotepoint}连接成功"); 47 clientList.Add(client); 48 49 //创建一个新线程用于不停接收客户端发的消息 50 Thread threadReceive = new Thread(Receive); 51 threadReceive.IsBackground = true; 52 threadReceive.Start(client); 53 54 Accept(); 55 } 56 57 58 private void Receive(object obj) 59 { 60 Socket client = obj as Socket; 61 try 62 { 63 byte[] msg = new byte[1024]; 64 int msgLen = client.Receive(msg);//Receive同样会挂起当前线程 65 string msgStr = $"数据来自{client.RemoteEndPoint}:" + 66 $"{Encoding.UTF8.GetString(msg, 0, msgLen)}"; 67 Console.WriteLine(msgStr); 68 ////创建一个新线程用于不停向客户端发消息(开新线程) 69 ////不断向服务端发送数据 70 //client.Send(Encoding.UTF8.GetBytes( 71 // Encoding.UTF8.GetString(msg, 0, msgLen) 72 // +"楼上说的对")); 73 74 BroadCast(client,msgStr); 75 76 //递归,不断接收数据 77 Receive(client); 78 } 79 catch 80 { 81 Console.WriteLine($"{client.RemoteEndPoint}已经断开"); 82 clientList.Remove(client); 83 } 84 } 85 86 87 private void BroadCast(Socket clientOther,string msg) 88 { 89 foreach (var client in clientList) 90 { 91 if (client == clientOther) 92 { 93 //这个消息是由client发的,不需要响应 94 } 95 else 96 { 97 client.Send(Encoding.UTF8.GetBytes(msg)); 98 } 99 } 100 } 101 } 102 103 }
program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace ServerTest { class Program { static void Main(string[] args) { ServerControl server = new ServerControl(); server.Start(); Console.ReadKey(); } } }
ClientControl.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ClientTest { class ClientControl { public Socket clientSocket; public ClientControl() { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp); } //连接服务端 public void Connect(string ip, int port) { clientSocket.Connect(ip, port); Console.WriteLine("连接服务器成功"); //用于接收服务端的数据 Thread threadReceive = new Thread(Receive); threadReceive.IsBackground = true; threadReceive.Start(); } private void Receive() { while (true) { try { byte[] msg = new byte[1024]; int msgLen = clientSocket.Receive(msg); Console.WriteLine("服务器说:" + Encoding.UTF8.GetString(msg, 0, msgLen)); } catch { Console.WriteLine("服务器已经拒绝"); break; } } } //向服务端发送数据 public void Send() { Thread threadSend = new Thread(ReadAndSend); //threadSend.IsBackground = true; 这里不设为后台线程, //主线程关掉时这个线程不影响 threadSend.Start(); } private void ReadAndSend() { //不断向服务端发送数据 string msg = Console.ReadLine(); while (msg != "quit") { clientSocket.Send(Encoding.UTF8.GetBytes(msg)); msg = Console.ReadLine(); } } } }
program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Net.Configuration; using System.Text; using System.Threading.Tasks; namespace ClientTest { class Program { static void Main(string[] args) { ClientControl client = new ClientControl(); client.Connect("192.168.2.48",12321); client.Send(); Console.ReadKey(); } } }
1