目录
1. 概念
2. 和TCP/IP的关系
3.客户端-服务器连接
连接过程
4.demo
5.Socket
5.1 socket对象
服务器端有两种Socket
5.2 Bind和connect连接
5.3Listen()监听请求连接和Accept接收请求连接
5.4Recive()和Send()
Receive
Send
5.5 释放资源
close()
shutdown()
总结
套接字(socket)在互联网通信中是由IP地址和端口号组成的。它是一种通信机制,使得两台计算机之间的网络程序可以进行数据交换。
一个socket就定义了网络上某个主机上的某个应用程序的通信端点,格式通常为IP地址:端口号
。这种机制允许同一台主机上的多个应用程序同时进行网络通信,而不会混淆数据的归属。
Socket是对TCP/IP协议的封装,它允许程序通过TCP或UDP协议发送和接收数据。使用Socket,开发者可以创建客户端程序来连接服务器,或者创建服务器程序来监听并接受客户端的连接请求。
服务器端初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
服务器监听:服务器端socket并不定位具体的客户端socket,而是处于等待监听状态,实时监控网络状态。
客户端请求:客户端clientSocket发送连接请求,目标是服务器的serverSocket。为此,clientSocket必须知道serverSocket的地址和端口号,进行扫描发出连接请求。
连接确认:当服务器socket监听到或者是受到客户端socket的连接请求时,服务器就响应客户端的请求,建议一个新的socket,把服务器socket发送给客户端,一旦客户端确认连接,则连接建立。
注:在连接确认阶段:服务器socket即使在和一个客户端socket建立连接后,还在处于监听状态,仍然可以接收到其他客户端的连接请求,这也是一对多产生的原因。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Server
{
public static void StartServer()
{
// 监听的IP地址和端口号
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
int port = 8888;
// 创建TCP监听器
TcpListener listener = new TcpListener(ipAddress, port);
Console.WriteLine($"服务器正在 {ipAddress}:{port} 上监听...");
// 开始监听
listener.Start();
try
{
while (true)
{
// 等待客户端连接
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("客户端已连接!");
// 使用线程来处理每个客户端的通信,这样可以同时处理多个客户端
Thread t = new Thread(new ParameterizedThreadStart(HandleClient));
t.Start(client);
}
}
finally
{
// 清理工作,停止监听
listener.Stop();
}
}
private static void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
// 接收客户端发送的消息
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("接收到: " + dataReceived);
// 回复客户端
byte[] message = Encoding.ASCII.GetBytes("服务器已收到: " + dataReceived);
stream.Write(message, 0, message.Length);
}
// 关闭客户端连接
client.Close();
}
}
public class Program
{
public static void Main(string[] args)
{
Server.StartServer();
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Client
{
public static void StartClient()
{
// 服务器的IP地址和端口号
IPAddress serverIpAddress = IPAddress.Parse("127.0.0.1");
int serverPort = 8888;
// 创建TCP客户端并尝试连接服务器
using (TcpClient client = new TcpClient())
{
client.Connect(serverIpAddress, serverPort);
Console.WriteLine("已连接到服务器!");
NetworkStream stream = client.GetStream();
// 发送消息给服务器
string message = "你好,服务器!";
byte[] data = Encoding.ASCII.GetBytes(message);
stream.Write(data, 0, data.Length);
// 接收服务器的回复
byte[] reply = new byte[1024];
int bytesRead = stream.Read(reply, 0, reply.Length);
Console.WriteLine($"服务器回复: {Encoding.ASCII.GetString(reply, 0, bytesRead)}");
// 关闭连接(可选,因为using语句会自动处理)
//client.Close();
}
}
}
public class Program
{
public static void Main(string[] args)
{
Client.StartClient();
}
}
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//监控 ip4 地址,套接字类型为 TCP ,协议类型为 TCP
监听套接字(Listening Socket):
listen()
函数进入监听状态。连接套接字(Connected Socket)
Bind() 用于绑定 IPEndPoint 对象,在服务端使用。
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iP = IPAddress.Parse("127.0.0.1");
serverSocket.Bind(new IPEndPoint(iP, 2300))
Connect() 在客户端使用,用于连接服务端。
创建 Socket 对象后,接着 绑定本地Socket / 连接服务端。
IPAddress iP = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(iP, 2300);
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建与远程主机的连接
serverSocket.Connect(iPEndPoint);
Listen:监控所有发送到此主机的、特点端口的连接请求。服务端使用,客户端不需要。
serverSocket.Listen(10); //开始监听
Accept() :以同步方式监听套接字,在连接请求队列中提取第一个挂起的连接请求,然后创建并返回一个新的 Socket 对象。作用是接收一个已连接的客户端请求,并为这个客户端连接创建一个新的套接字
//创建终结点(EndPoint)
IPAddress ip = IPAddress.Any;
IPEndPoint ipe = new IPEndPoint(ip, 8000);
//创建 socket 并开始监听
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(ipe);
serverSocket.Listen(10);//开始监听
//接受到client连接,为此连接建立新的socket,并接受信息
Socket temp = serverSocket.Accept();//为新建连接创建新的socket
//关闭连接
temp.Close();
在服务端和客户端都使用这两个方法
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//从客户端接受信息
recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
string str = "hello";
byte[] a = Encoding.UTF8.GetBytes(str);
send = socket.Send(a, 0);
发送/接收 都是使用 byte[] 字节流,所以接收时要进行转换。
close()
函数用于关闭一个套接字描述符,这意味着结束与该描述符关联的所有I/O操作,并释放系统为这个套接字分配的资源。一旦 close()
被调用,这个套接字就不能再用于读写操作。close()
,客户端在下一次读取时会得到文件结束标志(EOF)或者遇到错误(具体取决于操作系统和编程环境)。对服务端而言,当收到EOF或错误时,通常会知道对方已经关闭了连接。shutdown()
函数提供了更细粒度的控制来关闭套接字的部分连接能力,而不是完全关闭套接字。它允许分别或同时关闭读取、写入或两者。它有三个参数:第一个参数是套接字描述符,第二个参数指定要关闭的流的方向,可以是 SHUT_RD
(禁止后续读取操作)、SHUT_WR
(禁止后续写入操作)或 SHUT_RDWR
(禁止读写操作)。shutdown(sock, SHUT_WR)
时,服务端会向客户端发送一个FIN包,表示自己不会再发送数据,但仍然可以接收数据。shutdown(sock, SHUT_RD)
则告诉内核不再接收数据,但还可以继续发送。shutdown(sock, SHUT_RDWR)
则相当于同时禁止了读写,并且通常紧跟着 close()
来彻底关闭套接字。close()
是一个更加直接的方式,用于完全关闭套接字,结束所有读写操作并释放资源。
shutdown()
提供了更灵活的控制,允许逐步关闭套接字的读写能力,适用于需要在完全断开连接前进行特殊清理或通知的场景。
在实际应用中,根据的需求选择使用 close()
或者 shutdown()
,或者两者结合使用,以实现对套接字操作的精确控制。