Socket基础

目录

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()

总结


1. 概念

套接字(socket)在互联网通信中是由IP地址和端口号组成的。它是一种通信机制,使得两台计算机之间的网络程序可以进行数据交换。

  • IP地址:用于标识互联网上的每一台计算机或网络设备,确保数据能够到达正确的目标主机。
  • 端口号:用于标识主机上运行的特定应用程序或服务。这样一来,操作系统就知道将接收到的数据发送给哪个应用程序处理。

一个socket就定义了网络上某个主机上的某个应用程序的通信端点,格式通常为IP地址:端口号。这种机制允许同一台主机上的多个应用程序同时进行网络通信,而不会混淆数据的归属。

2. 和TCP/IP的关系

Socket基础_第1张图片

Socket是对TCP/IP协议的封装,它允许程序通过TCP或UDP协议发送和接收数据。使用Socket,开发者可以创建客户端程序来连接服务器,或者创建服务器程序来监听并接受客户端的连接请求。

  • 当使用TCP时,Socket通常被称为流式Socket(Stream Socket),它基于TCP协议,确保数据的可靠传输和有序性。
  • 当使用UDP时,Socket则被称为数据报Socket(Datagram Socket),它基于UDP协议,提供了一种无连接的数据传输方式,不保证数据的可靠到达和顺序。

3.客户端-服务器连接

Socket基础_第2张图片

连接过程

   服务器端初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

服务器监听:服务器端socket并不定位具体的客户端socket,而是处于等待监听状态,实时监控网络状态。

客户端请求:客户端clientSocket发送连接请求,目标是服务器的serverSocket。为此,clientSocket必须知道serverSocket的地址和端口号,进行扫描发出连接请求。

连接确认:当服务器socket监听到或者是受到客户端socket的连接请求时,服务器就响应客户端的请求,建议一个新的socket,把服务器socket发送给客户端,一旦客户端确认连接,则连接建立。

注:在连接确认阶段:服务器socket即使在和一个客户端socket建立连接后,还在处于监听状态,仍然可以接收到其他客户端的连接请求,这也是一对多产生的原因。

4.demo

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();
    }
}

5.Socket

5.1 socket对象
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//监控 ip4 地址,套接字类型为 TCP ,协议类型为 TCP
服务器端有两种Socket

监听套接字(Listening Socket):

    • 监听套接字是服务器用来监听特定端口上的连接请求的。当服务器程序启动时,它会创建一个监听套接字,并将其绑定到一个指定的IP地址和端口号上,然后调用listen()函数进入监听状态。
    • 这个套接字的主要任务是等待并接受来自客户端的连接请求。它不参与实际的数据传输,只是起到“门房”的作用,负责接收连接。
    • 只有一个监听套接字对应于服务器上的一个特定服务端口,例如HTTP服务的80端口或HTTPS的443端口。
    • 不同的监听Socket可以通过绑定到不同的端口来提供不同类型的服务。

连接套接字(Connected Socket)

    • 每当有一个客户端成功连接到服务器的监听套接字时,服务器就会为这个连接创建一个新的连接套接字。这个新的套接字是用来与特定客户端进行双向数据传输的。
    • 连接套接字是全双工的,意味着它可以同时发送和接收数据,专门服务于与之关联的客户端,确保数据的正确路由和隔离。
    • 因此,如果有多个客户端同时连接到服务器,服务器就会为每个客户端生成一个单独的连接套接字,即使它们都是通过同一个监听端口接入的。
5.2 Bind和connect连接

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);
5.3Listen()监听请求连接和Accept接收请求连接

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(); 
5.4Recive()和Send()
  • Receive() 接收信息
  • Send() 发送信息

在服务端和客户端都使用这两个方法

Receive
   string recvStr = "";
            byte[] recvBytes = new byte[1024];
            int bytes;
            bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//从客户端接受信息
            recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
Send
 string str = "hello";
            byte[] a = Encoding.UTF8.GetBytes(str);
            send = socket.Send(a, 0);

发送/接收 都是使用 byte[] 字节流,所以接收时要进行转换。

5.5 释放资源
close()
  • 功能:close() 函数用于关闭一个套接字描述符,这意味着结束与该描述符关联的所有I/O操作,并释放系统为这个套接字分配的资源。一旦 close() 被调用,这个套接字就不能再用于读写操作。
  • 行为:对客户端而言,如果服务端调用了 close(),客户端在下一次读取时会得到文件结束标志(EOF)或者遇到错误(具体取决于操作系统和编程环境)。对服务端而言,当收到EOF或错误时,通常会知道对方已经关闭了连接。
  • 应用场景:当完全完成与一个套接字的交互,不再需要进行任何读写操作时使用。
shutdown()
  • 功能: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(),或者两者结合使用,以实现对套接字操作的精确控制。

你可能感兴趣的:(网络)