91网首页-91网页版-91网在线观看-91网站免费观看-91网站永久视频-91网站在线播放

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

網(wǎng)絡編程-Socket編程模型

freeflydom
2025年6月3日 10:20 本文熱度 439

簡介

Socket(套接字)是計算機網(wǎng)絡中的一套編程接口,是網(wǎng)絡編程的核心,它將復雜的網(wǎng)絡協(xié)議封裝為簡單的API,是應用層(HTTP)與傳輸層(TCP)之間的橋梁。
應用程序通過調(diào)用Socket API,比如connect、send、recv,無需處理IP包封裝,路由選擇等復雜網(wǎng)絡操作,屏蔽底層細節(jié)將網(wǎng)絡通信簡化為建立連接-數(shù)據(jù)接收-數(shù)據(jù)發(fā)送-連接斷開,降低了開發(fā)復雜度。

FD&Handle

  1. FD
    文件描述符,在linux系統(tǒng)中,一切皆文件,它是內(nèi)核為了管理已打開的文件,而給每個進程維護的一個文件描述符表,而FD就是一個文件的索引。
  2. Handle
    而在windows平臺下,這個概念被稱為Handle(句柄),都為應用程序提供了一種統(tǒng)一的方式來訪問和操作資源,隱藏了底層資源管理的復雜性。

FD主要用于標識文件、套接字、管道等輸入輸出資源;而Handle的應用范圍更廣,除了文件和網(wǎng)絡資源外,還可以用于標識窗口、進程、線程、設備對象等各種系統(tǒng)資源。

Socket 網(wǎng)絡模型

BIO,Blocking I/O

BIO 是最傳統(tǒng)的 I/O 模型,其核心特征是一個連接一個線程,線程在讀取/寫入時會阻塞,直到I/O操作完成。

        private static Socket _server;
        private static byte[] _buffer = new byte[1024 * 4];
        static void Main(string[] args)
        {
            _server=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            _server.Bind(new IPEndPoint(IPAddress.Any, 6666));
            _server.Listen();
            
            while (true)
            {
                //BIO核心,線程阻塞,等待客戶端連接
                var client = _server.Accept();
                Console.WriteLine($"Client {client.RemoteEndPoint} connect. ");
                //BIO核心,線程阻塞,等待客戶端發(fā)送消息
                var messageCount = client.Receive(_buffer);
                var message = Encoding.UTF8.GetString(_buffer, 0, messageCount);
                Console.WriteLine($"Client {client.RemoteEndPoint} Say:{message}");
            }
        }

從代碼中可以看出,有兩個地方阻塞,一是Accept(),二是Receive(),如果客戶端一直不發(fā)送數(shù)據(jù),那么線程會一直阻塞在Receive()上,也不會接受其它客戶端的連接。

C10K問題

有聰明的小伙伴會想到,我可以利用多線程來處理Receive(),這樣就服務端就可以接受其它客戶端的連接了。

    internal class Program
    {
        private static Socket _server;
        private static byte[] _buffer = new byte[1024 * 4];
        static void Main(string[] args)
        {
            _server=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            _server.Bind(new IPEndPoint(IPAddress.Any, 6666));
            _server.Listen();
            
            while (true)
            {
                //BIO核心,線程阻塞,等待客戶端連接
                var client = _server.Accept();
                Console.WriteLine($"Client {client.RemoteEndPoint} connect. ");
                //多線程讀取客戶端數(shù)據(jù),避免主線程阻塞
                Task.Run(() => HandleClient(client));
            }
        }
        static void HandleClient(Socket client)
        {
            while (true)
            {
                //BIO核心,線程阻塞,等待客戶端發(fā)送消息
                var messageCount = client.Receive(_buffer);
                var message = Encoding.UTF8.GetString(_buffer, 0, messageCount);
                Console.WriteLine($"Client {client.RemoteEndPoint} Say:{message}");
            }
        }
    }

當給客戶端建立好連接后,會啟用一個新的線程來單獨處理Receive(),避免了主線程阻塞。
但有一個嚴重的缺陷,就是當一萬個客戶端同時連接,服務端要創(chuàng)建一萬個線程來接。一萬個線程帶來的CPU上下文切換與內(nèi)存成本,非常容易會拖垮服務器。這就是C10K問題來由來。

因此,BIO的痛點在于:

  1. 高并發(fā)下資源耗盡
    當連接數(shù)激增時,線程數(shù)量呈線性增長(如 10000 個連接對應 10000 個線程),導致內(nèi)存占用過高、上下文切換頻繁,系統(tǒng)性能急劇下降。
  2. 阻塞導致效率低下
    線程在等待 IO 時無法做其他事情,CPU 利用率低。

NIO,Non-Blocking I/O

為了解決此問題,需要跪舔操作系統(tǒng),為用戶態(tài)程序提供一個真正非阻塞的Accept/Receive的函數(shù)
該函數(shù)的效果應該是,當沒有新連接/新數(shù)據(jù)到達時,不阻塞線程。而是返回一個特殊標識,來告訴線程沒有活干。

Java 1.4 引入 NIO,C# 通過Begin/End異步方法或SocketAsyncEventArgs實現(xiàn)類似邏輯。

    internal class Program
    {
        private static Socket _server;
        private static byte[] _buffer = new byte[1024 * 4];
        //所有客戶端的連接
        private static readonly List<Socket> _clients = new List<Socket>();
        static void Main(string[] args)
        {
            _server=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            _server.Bind(new IPEndPoint(IPAddress.Any, 6666));
            _server.Listen();
            //NIO核心,設為非阻塞模式
            _server.Blocking = false;
            while (true)
            {
                try
                {
                    var client = _server.Accept();
                    _clients.Add(client);
                    Console.WriteLine($"Client {client.RemoteEndPoint} connect. ");
                }
                catch (SocketException ex) when(ex.SocketErrorCode==SocketError.WouldBlock)
                {
                    //沒有新連接時,調(diào)用Accept觸發(fā)WouldBlock異常,無視即可。
                }
				//一個線程同時管理Accept與Receive,已經(jīng)有了多路復用的意思。
                HandleClient();
            }
        }
        static void HandleClient()
        {
			//一個一個遍歷,尋找可用的客戶端,
            foreach (var client in _clients.ToList())
            {
                try
                {
                    //NIO核心,非阻塞讀取數(shù)據(jù),無數(shù)據(jù)時立刻返回
                    var messageCount = client.Receive(_buffer, SocketFlags.None);
                    var message = Encoding.UTF8.GetString(_buffer, 0, messageCount);
                    Console.WriteLine($"Client {client.RemoteEndPoint} Say:{message}");
                }
                catch (SocketException ex) when (ex.SocketErrorCode == SocketError.WouldBlock)
                {
                    //沒有新數(shù)據(jù)讀取時,調(diào)用Receive觸發(fā)WouldBlock異常,無視即可。
                }
            }
        }
    }

通過NIO,我們可以非常驚喜的發(fā)現(xiàn)。我們僅用了一個線程就完成對客戶端的連接與監(jiān)聽,相對BIO有了質(zhì)的變化。
當數(shù)據(jù)未就緒時(內(nèi)核緩沖區(qū)無數(shù)據(jù)),非阻塞模式下的Accept/Receive會立即返回WouldBlock異常(或-1);當數(shù)據(jù)就緒時,調(diào)用會立即返回讀取的字節(jié)數(shù)(>0),不會阻塞線程。數(shù)據(jù)從內(nèi)核緩沖區(qū)到用戶緩沖區(qū)的拷貝由 CPU 同步完成,屬于正常 IO 操作流程,不涉及線程阻塞

盡管NIO已經(jīng)是JAVA世界的絕對主流,但依舊存在幾個痛點:

  1. 輪詢開銷
    如果事件比較少,輪詢會產(chǎn)生大量空轉,CPU資源被浪費。
  2. 需要手動處理細節(jié)
    比如手動編寫捕獲when (ex.SocketErrorCode == SocketError.WouldBlock)來識別狀態(tài),
    需要手動處理TPC粘包,以及各種異常處理。

AIO,Asynchronous I/O

AIO作為大魔王與終極優(yōu)化,實現(xiàn)了真正的異步操作,當發(fā)起IO請求后,內(nèi)核完全接管IO處理,完成后通過回調(diào)或者事件來通知程序,開發(fā)者無需關心緩沖區(qū)管理、事件狀態(tài)跟蹤或輪詢開銷。

Java 7 引入 NIO.2(AIO),C# 通過IOCP+Async來實現(xiàn)

    internal class Program
    {
        private static Socket _server;
        private static Memory<byte> _buffer = new byte[1024 * 4];
        //所有客戶端的連接
        private static readonly List<Socket> _clients = new List<Socket>();
        static async Task Main(string[] args)
        {
            _server=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            _server.Bind(new IPEndPoint(IPAddress.Any, 6666));
            _server.Listen();
            while (true)
            {
                //異步等待連接,線程不阻塞
                var client = await _server.AcceptAsync();
                //不阻塞主線程,由線程池調(diào)度
                HandleClientAsync(client);
            }
            
        }
        private static async Task HandleClientAsync(Socket client)
        {
            //異步讀取數(shù)據(jù),由操作系統(tǒng)完成IO后喚醒
            var messageCount = await client.ReceiveAsync(_buffer);
            var message = Encoding.UTF8.GetString(_buffer.ToArray(), 0, messageCount);
            Console.WriteLine($"Client {client.RemoteEndPoint} Say:{message}");
        }
    }

Linux/Windows對模型的支持

NIO的改良,IO multiplexing

I/O Multiplexing 是一種高效處理多個I/O操作的技術,核心思想是通過少量線程管理多個I/O流,避免因為單個I/O阻塞導致整體服務性能下降。
它通過事件機制(可讀,可寫,異常)監(jiān)聽多個I/O源,當某個I/O流可操作時,才對其執(zhí)行讀寫操作,從而實現(xiàn)單線程處理多連接的高效模型。

IO 多路復用本質(zhì)是NIO的改良

select/poll

參考上面的代碼,HandleClient方法中,我們遍歷了整個_Clients,用以尋找客戶端的Receive。
同樣是C10K問題,如果我們1萬,甚至100萬個客戶端連接。那么遍歷的效率太過低下。尤其是每調(diào)用一次Receive都是一次用戶態(tài)到內(nèi)核態(tài)的切換。
那么,如果讓操作系統(tǒng)告訴我們,哪些連接是可用的,我們就避免了在用戶態(tài)遍歷,從而提高性能。

        /// <summary>
        /// 偽代碼
        /// </summary>
        static void HandleClientSelect()
        {
            var clients = _clients.ToList();
            //自己不遍歷,交給內(nèi)核態(tài)去遍歷.
            //這里會有一次list copy到內(nèi)核態(tài)的過程,如果list量很大,開銷也不小.
            var readyClients= Socket.Select(clients);
            //內(nèi)核會幫你標記好哪些client已經(jīng)就緒
            foreach (var client in readyClients)
            {
                //用戶態(tài)依舊需要遍歷一遍,但避免無意義的系統(tǒng)調(diào)用,用戶態(tài)到內(nèi)核態(tài)的切換.只有真正就緒的client才處理
                if (client.IsReady)
                {
                    var messageCount = client.Receive(_buffer, SocketFlags.None);
                    var message = Encoding.UTF8.GetString(_buffer, 0, messageCount);
                    Console.WriteLine($"Client {client.RemoteEndPoint} Say:{message}");
                }
                else
                {
                    break;
                }
            }
        }

通過監(jiān)聽一組文件描述符(File Descriptor, FD)的可讀、可寫或異常狀態(tài),當其中任意狀態(tài)滿足時,內(nèi)核返回就緒的 FD 集合。用戶需遍歷所有 FD 判斷具體就緒的 I/O 操作。

select模型受限于系統(tǒng)默認值,最大只能處理1024個連接。poll模型通過結構體數(shù)組替代select位圖的方式,避免了數(shù)量限制,其它無區(qū)別。

epoll

作為NIO的終極解決方案,它解決了什么問題?

  1. 調(diào)用select需要傳遞整個List
    var readyClients= Socket.Select(clients);
    如果list中有10W+,那么這個copy的成本會非常高
  2. select依舊是線性遍歷
    在內(nèi)核層面依舊是遍歷整個list,尋找可用的client,所以時間復雜度不變O(N),只是減少了從用戶態(tài)切換到內(nèi)核態(tài)的次數(shù)而已
  3. 僅僅對ready做標記,并不減少返回量
    select僅僅返回就緒的數(shù)量,具體是哪個就緒,還要自己遍歷一遍。

所以epoll模型主要主要針對這三點,做出了如下優(yōu)化:

  1. 通過mmap,zero copy,減少數(shù)據(jù)拷貝
  2. 不再通過輪詢方式,而是通過異步事件通知喚醒,內(nèi)部使用紅黑樹來管理fd/handle
  3. 喚醒后,僅僅返回有變化的fd/handle,用戶無需遍歷整個list

基于事件驅(qū)動(Event-Driven)機制,內(nèi)核維護一個 FD 列表,通過epoll_ctl添加 / 刪除 FD 監(jiān)控,epoll_wait阻塞等待就緒事件。就緒的 FD 通過事件列表返回,用戶僅需處理就緒事件對應的 FD。

?點擊查看代碼
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#define SEVER_PORT 6666
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10
#define handle_error(cmd,result)\
    if(result<0){               \
        perror(cmd);            \
        exit(EXIT_FAILURE);     \
    }                           \

char *read_buf=NULL;
char *write_buf=NULL;
void init_buf()
{
    read_buf=malloc(sizeof(char)* BUFFER_SIZE);
    //讀內(nèi)存分配判斷
    if(!read_buf)
    {
        printf("讀緩存創(chuàng)建異常,斷開連接\n");
        exit(EXIT_FAILURE);
    }
    //寫內(nèi)存分配判斷
    write_buf=malloc(sizeof(char)* BUFFER_SIZE);
    if(!write_buf)
    {
        printf("寫緩存創(chuàng)建異常,斷開連接\n");
        exit(EXIT_FAILURE);
    }
    memset(read_buf,0,BUFFER_SIZE);
    memset(write_buf,0,BUFFER_SIZE);
}
void clear_buf(char *buf)
{
    memset(buf,0,BUFFER_SIZE);
}
void set_nonblocking(int sockfd)
{
    int opts=fcntl(sockfd,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(F_GETFL)");
        exit(EXIT_FAILURE);
    }
    opts|=O_NONBLOCK;
    int res=fcntl(sockfd,F_SETFL,opts);
    if(res<0)
    {
        perror("fcntl(F_GETFL)");
        exit(EXIT_FAILURE);
    }
}
int main(int argc, char const *argv[])
{
    //初始化讀寫緩沖區(qū)
    init_buf();
    //聲明sockfd,clientfd
    int sockfd,client_fd,temp_result;
    //聲明服務端與客戶端地址
    struct  sockaddr_in server_addr,client_addr;
    memset(&server_addr,0,sizeof(server_addr));
    memset(&client_addr,0,sizeof(client_addr));
    
    //聲明IP協(xié)議
    server_addr.sin_family=AF_INET;
    //綁定主機地址
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    //綁定端口
    server_addr.sin_port=htons(SEVER_PORT);
    //創(chuàng)建socket
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",sockfd);
    //綁定地址
    temp_result=bind(sockfd,(struct  sockaddr *)&server_addr,sizeof(server_addr));
    handle_error("bind",temp_result);
    //進入監(jiān)聽
    temp_result=listen(sockfd,128);
    handle_error("listen",temp_result);
    //將sockfd設為非阻塞模式
    set_nonblocking(sockfd);
    int epollfd,nfds;
    struct epoll_event ev,events[MAX_EVENTS];
    //創(chuàng)建epoll
    epollfd=epoll_create1(0);
    handle_error("epoll_create1",epollfd);
    //將sockfd加入到監(jiān)控列表
    ev.data.fd=sockfd;
    //將關聯(lián)的文件描述符設為可讀,可讀說明有連接進入,就會被epoll觸發(fā)
    ev.events=EPOLLIN;
    temp_result=epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&ev);
    handle_error("epoll_ctl",temp_result);
    socklen_t client_addr_len=sizeof(client_addr);
    //接受client連接
    while (1)
    {
        //掛起等待,有可讀信息
        //nfds表示有多少個客戶端連接與多少條消息
        nfds=epoll_wait(epollfd,events,MAX_EVENTS,-1);
        handle_error("epoll_wait",nfds);
        for (int i = 0; i < nfds; i++)
        {
            //第一個是sockfd,要預處理一下。
            if(events[i].data.fd==sockfd)
            {
                client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&client_addr_len);
                handle_error("accept",client_fd);
                set_nonblocking(client_fd);
                printf("與客戶端from %s at PORT %d 文件描述符 %d 建立連接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),client_fd);
                //將獲取到的client連接也添加到監(jiān)控列表
                ev.data.fd=client_fd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epollfd,EPOLL_CTL_ADD,client_fd,&ev);
            }
            //既有新的客戶端連接,又有舊客戶端發(fā)送消息
            else if(events[i].events&EPOLLIN)
            {
                //老連接有數(shù)據(jù)
                int count=0,send_count=0;
                client_fd=events[i].data.fd;
                while ((count=recv(client_fd,read_buf,BUFFER_SIZE,0)>0))
                {
                    printf("receive message from client_fd: %d: %s \n",client_fd,read_buf);
                    clear_buf(read_buf);
                    strcpy(write_buf,"receive~\n");
                    send_count=send(client_fd,write_buf,strlen(write_buf),0);
                    handle_error("send",send_count);
                    clear_buf(write_buf);
                }
                if(count==-1&&errno==EAGAIN)
                {
                    printf("當前批次已經(jīng)讀取完畢。\n");
                }
                else if(count==0)
                {
                    printf("客戶端client_fd:%d請求關閉連接......\n",client_fd);
                    strcpy(write_buf,"recevie your shutdown signal 收到你的關閉信號\n");
                    send_count=send(client_fd,write_buf,strlen(write_buf),0);
                    handle_error("send",send_count);
                    clear_buf(write_buf);
                    //從epoll文件描述法符中移除該client_fd
                    epoll_ctl(epollfd,EPOLL_CTL_DEL,client_fd,NULL);
                    printf("釋放client_fd:%d資源\n",client_fd);
                    shutdown(client_fd,SHUT_WR);
                    close(client_fd);
                }
                
            }
        }
        
    }
    
    printf("服務端關閉后資源釋放\n");
    close(epollfd);
    close(sockfd);
    free(read_buf);
    free(write_buf);
    return 0;
}

IOCP

由于Windows并不開源,關于IOCP的資料不多,可以參考此文。

IOCP:Input/Output Completion Port,I/O完成端口
.NET Core在Windows下基于IOCP,在Linux下基于epoll,在macOS中基于kqueue
https://www.cnblogs.com/lmy5215006/p/18571532

理論與現(xiàn)實的割裂

從上面的理論可以看出,AIO似乎是版本答案,在C#中,AIO已經(jīng)充斥著每一個角落,但在JAVA的世界中,更加主流的是NIO,這是為什么呢?

1. Linux的支持不足
Linux 內(nèi)核直到 3.11 版本(2013 年)才支持真正的異步 IO(io_uring),從而間接影響了JAVA的發(fā)展,Java的 AIO直到 2011 年Java 7才正式發(fā)布,而其前一代 NIO已發(fā)展近 10 年。
而Windows的IOCP在Windows NT 4.0 (1996年)就登上了歷史舞臺,加上C#起步較晚,沒有歷史包袱,所以對AIO支持力度更大,尤其是2012年發(fā)布了async/await異步模型后,解決了回調(diào)地獄,實現(xiàn)了1+1>3的效果。
2. JAVA的路徑依賴
NIO生態(tài)過于強大,尤其是以Netty/Redis為首的經(jīng)典實現(xiàn),實在是太香了!
3. 理論優(yōu)勢并未轉換為實際收益
AIO的性能在特定場景(如超大規(guī)模文件讀寫、長連接低活躍)下可能優(yōu)于NIO,但在互聯(lián)網(wǎng)場景中,NIO的足夠高效,比如HTTP請求,AIO的異步回調(diào)優(yōu)勢相對輪詢并不明顯。

維度Java AIO未普及的原因C# AIO普及的原因
歷史發(fā)展NIO早于AIO 9年推出,生態(tài)成熟;AIO定位模糊,未解決NIO的核心痛點(如編程復雜度)AIO與async/await同步推出,解決了異步編程的“回調(diào)地獄”,成為高并發(fā)編程的默認選擇
跨平臺需適配多系統(tǒng)異步機制(如Linux的epoll、macOS的kqueue),實際性能提升有限早期綁定Windows IOCP,性能穩(wěn)定;跨平臺后對AIO需求不迫切
生態(tài)Netty等NIO框架統(tǒng)治市場,切換AIO成本高缺乏NIO統(tǒng)治級框架,AIO通過async/await成為原生選擇
開發(fā)者習慣NIO代碼雖復雜,但通過框架封裝已足夠易用;AIO回調(diào)模式學習成本更高async/await語法糖讓異步代碼接近同步,開發(fā)者更易接受
性能場景大多數(shù)場景下NIO已足夠高效,AIO的優(yōu)勢未顯著體現(xiàn)Windows IOCP場景下AIO性能優(yōu)勢明顯,且覆蓋主流企業(yè)級需求

說人話就是,Netty太香了,完全沒動力切換成AIO,順帶吐槽C#中沒有類似的框架。dotnetty不算,已經(jīng)停止更新了。

轉自https://www.cnblogs.com/lmy5215006/p/18877083


該文章在 2025/6/3 10:23:22 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務費用、相關報表等業(yè)務管理,結合碼頭的業(yè)務特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 人与动人 | 欧美日韩国产色 | 精品欧美亚 | 激情欧美经典日韩 | 97精品| 国产精品处女 | 国产性情精品在线 | 国产午夜免费网站 | 中文字幕日韩精品一 | 国产后入清纯学生妹 | 日韩欧美中文字幕出 | 日韩欧美在线播放 | 青青青爽在 | 日韩国产欧美视频 | 日韩欧美永久中文 | 国产性色| 国产美女极品粉 | 狠狠热精品免费视频 | 国产大片免费观看 | 成人动漫3d日本 | 成人午夜在线看 | 日韩免费视频网址 | AAa深夜日韩视频 | 国产日韩精品高清 | 国产女同精品 | 日本专区在线观看 | 成人国产精品高清 | 日韩欧美综合 | 国产丝袜大片 | 国产69精品 | 国内精品国产成 | 国产精品手机在线 | 91小视频网站 | 国色天香一区二区 | 国产精品三级 | 国产污视频网站 | 国产免费大黄 | 日韩熟女精品影院 | 日本乱码一区二 | sss欧美华人整片 | 91免费在线视 |