青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

twzheng's cppblog

『站在風口浪尖緊握住鼠標旋轉!』 http://www.cnblogs.com/twzheng

  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
  136 隨筆 :: 78 文章 :: 353 評論 :: 0 Trackbacks

A Reusable Windows Socket Server Class With C++

Contributed by Len Holgate

摘自:http://www.devarticles.com 

Ever thought of writing your own Windows socket server class? In this article Len shows you exactly how to do just that, including details of what a socket server should do and example C++ code.Writing a high performance server that runs on Windows NT and uses sockets to communicate with the outside world isn't that hard once you dig through the API references. What's more most of the code is common between all of the servers that you're likely to want to write. It should be possible to wrap all of the common code up in some easy to reuse classes. However, when I went looking for some classes to use to write my first socket server all of the examples and articles that I found required the user to pretty much start from scratch or utilise "cut and paste reuse" when they wanted to use the code in their own servers. Also the more complicated examples, ones that used io completion ports for example, tended to stop short of demonstrating real world usage. After all, anyone can write an echo server...

 

The aim of this article is to explain the set of reusable classes that I designed for writing socket servers and show how they can be used with servers which do more than simply echo every byte they receive. Note that I'm not going to bother explaining the hows and why's of IO completion ports etc, there are plenty of references available. A socket server needs to be able to listen on a specific port, accept connections and read and write data from the socket. A high performance and scaleable socket server should use asynchronous socket IO and IO completion ports. Since we're using IO completion ports we need to maintain a pool of threads to service the IO completion packets. If we were to confine ourselves to running on Win2k and above we could use the QueueUserWorkItem api to deal with our threading requirements but to enable us to run on the widest selection of operating systems we have to do the work ourselves.

 

Before we can start accepting connections we need to have a socket to listen on. Since there are many different ways to set such a socket up, we'll allow the user's derived class to create this socket by providing a pure virtual function as follows:

 

virtual SOCKET CreateListeningSocket(
                                     unsigned 
long address,
                                     unsigned 
short port) = 0;

 

The user's class can now implement this function as they see fit, a common implementation might be something like this:

 

SOCKET CSocketServer::CreateListeningSocket(
    unsigned 
long address,
    unsigned 
short port)
{
    SOCKET s 
= ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
    
if (s == INVALID_SOCKET)
    
{
        
throw CWin32Exception(_T("CSocket::CreateListeningSocket()"), ::WSAGetLastError());
    }

    CSocket listeningSocket(s);
    CSocket::InternetAddress localAddress(address, port);
    listeningSocket.Bind(localAddress);
    listeningSocket.Listen(
5);
    
return listeningSocket.Detatch();
}

 

Note that we use a helper class, CSocket, to handle setting up our listening socket. This class acts as a "smart pointer" for sockets, automatically closing the socket to release resources when it goes out of scope and also wraps the standard socket API calls with member functions that throw exceptions on failure.

 

Now that we have a socket to listen on we can expect to start receieving connections. We'll use the WSAAccept() function to accept our connections as this is easier to use than the higher performance AcceptEx() we'll then compare the performance characteristics with AcceptEx() in a later article.

 

When a connection occurs we create a Socket object to wrap the SOCKET handle. We associate this object with our IO completion port so that IO completion packets will be generated for our asynchronous IO. We then let the derived class know that a connection has occurred by calling the OnConnectionEstablished() virtual function. The derived class can then do whatever it wants with the connection, but the most common thing would be to issue a read request on the socket after perhaps writing a welcome message to the client.

 

void CSocketServer::OnConnectionEstablished(
    Socket 
*pSocket,
    CIOBuffer 
*pAddress)
{
    
const std::string welcomeMessage("+OK POP3 server readyrn");
    pSocket.
>Write(welcomeMessage.c_str(), welcomeMessage.length());
    pSocket.
>Read();
}

 

Since all of our IO operations are operating aynchronously they return imediately to the calling code. The actual implementation of these operations is made slightly more complex by the fact that any outstanding IO requests are terminated when the thread that issued those requests exits. Since we wish to ensure that our IO requests are not terminated inappropriately we marshal these calls into our socket server's IO thread pool rather than issuing them from the calling thread. This is done by posting an IO completion packet to the socket server's IO Completion Port. The server's worker threads know how to handle 4 kinds of operation: Read requests, read completions, write requests and write completions. The request operations are generated by calls to PostQueuedCompletionStatus and the completions are generated when calls to WSARecv and WSASend complete asyncronously.

 

To be able to read and write data we need somewhere to put it, so we need some kind of memory buffer. To reduce memory allocations we could pool these buffers so that we don't delete them once they're done with but instead maintain them in a list for reused. Our data buffers are managed by an allocator which is configured by passing arguments to the construtor of our socket server. This allows the user to set the size of the IO buffers used as well as being able to control how many buffers are retained in the list for reuse. The CIOBuffer class serves as our data buffer follows the standard IO Completion Port pattern of being an extended "overlapped" structure.

 

As all good references on IO Completion Ports tell you, calling GetQueuedCompletionStatus blocks your thread until a completion packet is available and, when it is, returns you a completion key, the number of bytes transferred and an "overlapped" structure. The completion key represents 'per device' data and the overlapped structure represents 'per call' data. In our server we use the completion key to pass our Socket class around and the overlapped structure to pass our data buffer. Both our Socket class and our data buffer class allow the user to associate 'user data' with them. This is in the form of a single unsigned long value (which could always be used to store a pointer to a larger structure).

 

The socket server's worker threads loop continuously, blocking on their completion port until work is available and then extracting the Socket and CIOBuffer from the completion data and processing the IO request. The loop looks something like this:

 

int CSocketServer::WorkerThread::Run()
{
    
while (true)
    
{
        DWORD dwIoSize 
= 0;
        Socket 
*pSocket = 0;
        OVERLAPPED 
*pOverlapped = 0;

        m_iocp.GetStatus((PDWORD_PTR)
&pSocket, &dwIoSize, &pOverlapped);
        CIOBuffer 
*pBuffer = CIOBuffer::FromOverlapped(pOverlapped);
        
switch pBuffer.>GetUserData()
        
{
        
case IO_Read_Request:
            Read(pSocket, pBuffer);
            
break;
        
case IO_Read_Completed:
            ReadCompleted(pSocket, pBuffer);
            
break;
        
case IO_Write_Request:
            Write(pSocket, pBuffer);
            
break;
        
case IO_Write_Completed:
            WriteCompleted(pSocket, pBuffer);
            
break;
        }

    }

}

 

Read and write requests cause a read or write to be performed on the socket. Note that the actual read/write is being performed by our IO threads so that they cannot be terminated early due to the thread exiting. The ReadCompleted() and WriteCompleted() methods are called when the read or write actually completes. The worker thread provides two virtual functions to allow the caller's derived class to handle these situations. Most of the time the user will not be interested in the write completion, but the derived class is the only place that read completion can be handled.

 

virtual void ReadCompleted(
                           Socket 
*pSocket,
                           CIOBuffer 
*pBuffer) = 0;

virtual void WriteCompleted(
                            Socket 
*pSocket,
                            CIOBuffer 
*pBuffer);

 

Since our client must provide their own worker thread that derives from our socket server's worker thread we need to have a way for the server to be configured to use this derived worker thread. Whenever the server creates a worker thread (and this only occurs when the server first starts as the threads run for the life time of the server) it calls the following pure virtual function:

 

virtual WorkerThread *CreateWorkerThread(
    CIOCompletionPort 
&iocp) = 0;

 

We now have a framework for creating servers. The user needs to provide a worker thread class that is derived from CSocketServer::WorkerThread and a socket server that's derived from CSocketServer.These classes could look something like this:

 

class CSocketServerWorkerThread : public CSocketServer::WorkerThread
{
public :
    CSocketServerWorkerThread(CIOCompletionPort 
&iocp);

private :
    
virtual void ReadCompleted(
        CSocketServer::Socket 
*pSocket,
        CIOBuffer 
*pBuffer);
}
;

class CMySocketServer : CSocketServer
{
public :
    CMySocketServer (
        unsigned 
long addressToListenOn,
        unsigned 
short portToListenOn);

private :
    
virtual WorkerThread *CreateWorkerThread(
        CIOCompletionPort 
&iocp);
    
virtual SOCKET CreateListeningSocket(
        unsigned 
long address,
        unsigned 
short port);
    
virtual void OnConnectionEstablished(
        Socket 
*pSocket,
        CIOBuffer 
*pAddress);
}
;

 

Implementations for CreateListeningSocket() and OnConnectionEstablished() have already been presented. CreateWorkerThread() is as simple as this:

 

CSocketServer::WorkerThread *CMySocketServer::CreateWorkerThread(
    CIOCompletionPort 
&iocp)
{
    
return new CSocketServerWorkerThread(iocp);
}

 

Which leaves us with the implementation of our worker thread's ReadCompleted() method. This is where the server handles incoming data and, in the case of a simple Echo server ;) it could be as simple as this:

 

void CSocketServerWorkerThread::ReadCompleted(
    CSocketServer::Socket 
*pSocket,
    CIOBuffer 
*pBuffer)
{
    pSocket.
>Write(pBuffer);
}

 

A complete echo server is available for download in SocketServer1.zip. The server simply echos the incoming byte stream back to the client. In addition to implementing the methods discussed above the socket server and worker thread derived classes also implement several 'notifciation' methods that the server and worker thread classes call to inform the derived class of various internal goings on. The echo server simply outputs a message to the screen (and log file) when these notifications occur but the idea behind them is that the derived class can use them to report on internal server state via performance counters or suchlike. You can test the echo server by using telnet. Simply telnet to localhost on port 5001 (the port that the sample uses by default) and type stuff and watch it get typed back at you. The server runs until a named event is set and then shuts down. The very simple Server Shutdown program, available in ServerShutdown.zip, provides an off switch for the server.

 

 

More complex servers

Servers that do nothing but echo a byte stream are rare, except as poor examples. Normally a server will be expecting a message of some kind, the exact format of the message is protocol specific but two common formats are a binary message with some form of message length indicator in a header and an ASCII text message with a predefined set of 'commands' and a fixed command terminator, often "rn". As soon as you start to work with real data you are exposed to a real.world problem that is simply not an issue for echo servers. Real servers need to be able to break the input byte stream provided by the TCP/IP socket interface into distinct commands. The results of issuing a single read on a socket could be any number of bytes up to the size of the buffer that you supplied. You may get a single, distinct, message or you may only get half of a message, or 3 messages, you just can't tell. Too often inexperienced socket developers assume that they'll always get a complete, distinct, message and often their testing methods ensure that this is the case during development.

 

Chunking the byte stream

One of the simplest protocols that a server could implement is a packet based protocol where the first X bytes are a header and the header contains details of the length of the complete packet. The server can read the header, work out how much more data is required and keep reading until it has a complete packet. At this point it can pass the packet to the business logic that knows how to process it. The code to handle this kind of situation might look something like this:

 

void CSocketServerWorkerThread::ReadCompleted(
    CSocketServer::Socket 
*pSocket,
    CIOBuffer 
*pBuffer)
{
    pBuffer 
= ProcessDataStream(pSocket, pBuffer);
    pSocket.
>Read(pBuffer);
}


CIOBuffer 
*CSocketServerWorkerThread::ProcessDataStream(
    CSocketServer::Socket 
*pSocket,
    CIOBuffer 
*pBuffer)
{
    
bool done;
    
do
    
{
        done 
= true;
        
const size_t used = pBuffer.>GetUsed();
        
if (used >= GetMinimumMessageSize())
        
{
            
const size_t messageSize = GetMessageSize(pBuffer);
            
if (used == messageSize)
            
{
                
// we have a whole, distinct, message
                EchoMessage(pSocket, pBuffer);
                pBuffer 
= 0;
                done 
= true;
            }

            
else if (used > messageSize)
            
{
                
// we have a message, plus some more data 
                
// allocate a new buffer, copy the extra data into it and try again
                CIOBuffer *pMessage = pBuffer.>SplitBuffer(messageSize);
                EchoMessage(pSocket, pMessage);
                pMessage.
>Release();
                
// loop again, we may have another complete message in there
                done = false;
            }

            
else if (messageSize > pBuffer.>GetSize())
            
{
                Output(_T(
"Error: Buffer too smallnExpecting: "+ ToString(messageSize) + 
                    _T(
"Got: "+ ToString(pBuffer.>GetUsed()) + _T("nBuffer size = "+ 
                    ToString(pBuffer.
>GetSize()) + _T("nData = n"+ 
                    DumpData(pBuffer.
>GetBuffer(), pBuffer.>GetUsed(), 40));

                pSocket.
>Shutdown();
                
// throw the rubbish away 
                pBuffer.>Empty(); 
                done 
= true;
            }

        }

    }

    
while (!done);
    
    
// not enough data in the buffer, reissue a read into the same buffer to collect more data
    return pBuffer;
}

 

The key points of the code above are that we need to know if we have at least enough data to start looking at the header, if we do then we can work out the size of the message somehow. Once we know that we have the minimum amount of data required we can work out if we have all the data that makes up this message. If we do, great, we process it. If the buffer only contains our message then we simply process the message and since processing simply involves us posting a write request for the data buffer we return 0 so that the next read uses a new buffer. If we have a complete message and some extra data then we split the buffer into two, a new one with our complete message in it and the old one which has the extra data copied to the front of the buffer. We then pass our complete message to the business logic to handle and loop to handle the data that we had left over. If we dont have enough data we return the buffer and the Read() that we issue in ReadCompleted() reads more data into the same buffer, starting at the point that we're at now. Since we're a simple server we have a fairly important limitation, all our messages must fit into the IO buffer size that our server is using. Often this is a practical limitation, maximum message sizes can be known in advance and by setting our IO buffer size to be at least our maximum message size we avoid having to copy data around. If this isn't a viable limitation for your server then you'll need to have an alternative strategy here, copying data out of IO buffers and into something big enough to hold your whole message, or, processing the message in pieces...

 

In our simple server if the message is too big then we simply shutdown the socket connection, throw away the garbage data, and wait for the client to go away...

 

So how do we implement GetMinimumMessageSize() and GetMessageSize(), well, obviously it's protocol dependant, but for our packet echo server we do it like this:

 

size_t CSocketServerWorkerThread::GetMinimumMessageSize() const
{
    
return 1;
}


size_t CSocketServerWorkerThread::GetMessageSize(CIOBuffer 
*pBuffer) const
{
    size_t messageSize 
= *pBuffer.>GetBuffer();
    
return messageSize;
}

 

You may have noticed that in the case where we had a message and some extra data we called SplitBuffer() to break the complete message out into its own buffer, and then, once we'd dealt with it, we called Release(). This is a little of the implementation of the socket server's buffer allocator poking through. The buffers are reference counted. The only time we need to worry about this is if we create a new buffer using SplitBuffer, or if we decide to call AddRef() on the buffer because we wish to pass it off to another thread for processing. We'll cover this in more detail in the next article, but the gist of it is that every time we post a read or a write the buffer's reference count goes up and every time a read or write completes the count goes down, when there are no outstanding references the buffer goes back into the pool for reuse.

 

A packet echo server

 

A packet based echo server is available for download in SocketServer2.zip. The server expects to receive packets of up to 256 bytes which have a 1 byte header. The header byte contains the total length of the packet (including the header). The server reads complete packets and echos them back to the client. You can test the echo server by using telnet, if you're feeling clever ;) Simply telnet to localhost on port 5001 (the port that the sample uses by default) and type stuff and watch it get typed back at you. (Hint, CTRL B is 2 which is the smallest packet that contains data).

 

A real internet RFC protocol

 

Some of the common internet protocols, such as RFC 1939 (POP3), use a crlf terminated ASCII text stream command structure. An example of how such a server might be implemented using the CSocketServer classes presented here can be found in SocketServer3.zip. The classes presented here provide an easy way to develop scalable socket servers using IO completion and thread pooling in such a way that the user of the classes need not concern themselves with these low level issues. To create your own server simply derive from CSocketServer to create your listening socket, handle connection establishment and any of the other notifications that you require. Then derive from the WorkerThread class to provide the byte stream chunking and business logic. Running your server is as simple as this:

 

    CSocketServer server(
                         
"+OK POP3 server readyrn",
                         INADDR_ANY, 
// address to listen on
                         5001// port to listen on
                         10// max number of sockets to keep in the pool
                         10// max number of buffers to keep in the pool
                         1024); // buffer size
    server.Start();
    server.StartAcceptingConnections();

 

Your code can then do whatever it likes and the socket server runs on its own threads. When you are finished, simply call:

 

server.WaitForShutdownToComplete();

 

And the socket server will shutdown.

In the next article we address the issue of moving the business logic out of the IO thread pool and into a thread pool of its own so that long operations don't block the IO threads.

 

Notes

The source was built using Visual Studio 6.0 SP5 and Visual Studio .Net. You need to have a version of the Microsoft Platform SDK installed.

All of the zip files mentioned can be found in the single zip file attached to this article, which is linked here.

 

Revision history

· 21st May 2002 . Initial revision posted on www.jetbyte.com.

· 27th May 2002 . Added pause/resume functionality to all servers and the server shutdown program. Use CSocket to protect from resource leaks when creating the listening socket. Refactored the Socket and CIOBuffer classes so that common list management code is now in CNodeList and common user data code is now in COpaqueUserData.

· 29th May 2002 . Linting and general code cleaning

· 18th June 2002 . Removed call to ReuseAddress() during the creation of the listening socket as it not required . Thanks to Alun Jones for pointing this out to me.

 

DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real.world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.
posted on 2007-05-23 00:12 譚文政 閱讀(1637) 評論(0)  編輯 收藏 引用 所屬分類: 網絡編程vc++.net
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            一区二区三区 在线观看视频| 亚洲欧美日韩在线一区| 一区二区三区日韩| 欧美日韩综合久久| 一区二区国产精品| 亚洲素人在线| 精品999网站| 亚洲国产综合在线看不卡| 久久国产精品亚洲77777| 亚洲综合首页| 亚洲精品男同| 欧美中文字幕久久| 夜色激情一区二区| 午夜视频一区二区| 一区二区高清在线| 久久精品欧美日韩| 99热在线精品观看| 午夜电影亚洲| 在线视频亚洲| 蜜月aⅴ免费一区二区三区 | 欧美绝品在线观看成人午夜影视| 国产视频在线观看一区| 久久精品一区二区三区不卡牛牛 | 欧美亚一区二区| 香蕉成人啪国产精品视频综合网| 亚洲一区国产精品| 黑人中文字幕一区二区三区| 欧美a级片网站| 国产精品久久久久久久久久免费| 欧美国产日韩一区二区在线观看 | 欧美v日韩v国产v| 久久av免费一区| 国产精品久久77777| 亚洲美女淫视频| 日韩一区二区电影网| 欧美成人综合| 最新中文字幕一区二区三区| 开元免费观看欧美电视剧网站| 99国产一区| 欧美日韩xxxxx| 亚洲视频精品| 欧美在线高清| 国产亚洲精品久久久久婷婷瑜伽| 亚洲欧美三级伦理| 久久久精品国产一区二区三区| 国内精品久久久久影院优| 欧美亚洲专区| 亚洲成人资源网| 亚洲调教视频在线观看| 国产精品女人网站| 欧美亚洲尤物久久| 亚洲精品视频免费观看| 性久久久久久久久久久久| 国产综合欧美在线看| 欧美久久久久久蜜桃| 午夜欧美不卡精品aaaaa| 亚洲高清视频一区二区| 欧美一区二区三区久久精品茉莉花 | 亚洲国产高清在线观看视频| 亚洲天天影视| 亚洲激情电影在线| 精品动漫3d一区二区三区| 欧美视频在线不卡| 免费不卡在线视频| 性欧美xxxx大乳国产app| 91久久久亚洲精品| 亚洲国语精品自产拍在线观看| 欧美影院一区| 欧美一区二区高清| 欧美亚洲网站| 欧美一区二区三区免费观看视频| 一本一本a久久| 国产精品99久久久久久久vr| 91久久久国产精品| 先锋a资源在线看亚洲| 亚洲香蕉成视频在线观看| 亚洲精品一区二区三区在线观看| 欧美国产日韩亚洲一区| 亚洲国产91色在线| 亚洲精品永久免费精品| 亚洲精品一区中文| 欧美精品在线播放| 翔田千里一区二区| 性一交一乱一区二区洋洋av| 精品动漫3d一区二区三区| 中文亚洲免费| 性色av一区二区三区| 欧美激情视频给我| 亚洲国产成人av在线| 国产精品欧美经典| 欧美国产三区| 影音先锋中文字幕一区| 欧美激情中文字幕一区二区| 一本久久综合亚洲鲁鲁| 暖暖成人免费视频| 免费亚洲电影| 亚洲视频1区| 久久国产精品99精品国产| 久久激情视频久久| 欧美日韩一区二区视频在线观看| 国产精品区二区三区日本| 伊甸园精品99久久久久久| 亚洲一区精品在线| 免费在线欧美黄色| 制服丝袜亚洲播放| 久久精品国产亚洲5555| 一区二区三区欧美视频| 久久精品国产v日韩v亚洲| 欧美日韩精品免费在线观看视频| 国内精品伊人久久久久av影院 | 欧美一区亚洲| 国产精品都在这里| 亚洲久久一区| 国产精品国产馆在线真实露脸| 狠狠88综合久久久久综合网| 性色一区二区三区| 亚洲素人在线| 国产精品露脸自拍| 亚洲欧美日韩一区在线| 一本色道久久综合狠狠躁篇怎么玩| 狼狼综合久久久久综合网 | 国语自产精品视频在线看| 亚洲愉拍自拍另类高清精品| 亚洲精选视频免费看| 欧美日韩1080p| 亚洲网站啪啪| 亚洲在线一区二区| 狠狠色香婷婷久久亚洲精品| 久久影视三级福利片| 久久亚洲精品网站| 日韩视频中文字幕| 亚洲欧美日韩另类精品一区二区三区| 国产精品久久久一区麻豆最新章节| 亚洲欧美一区二区激情| 亚洲永久精品大片| 亚洲国产精品黑人久久久 | 久久久精品一品道一区| 亚洲电影免费观看高清| 99re热精品| 亚洲国产高清一区二区三区| 日韩一级片网址| 伊人久久婷婷色综合98网| 99re视频这里只有精品| 国产精品美女主播| 亚洲精品韩国| 亚洲高清不卡在线观看| 亚洲在线观看免费视频| 亚洲精品国产精品国自产在线| 亚洲一区日本| 亚洲一区二区三区高清| 欧美精品久久一区二区| 麻豆精品在线播放| 国产亚洲精品久久久久动| 一区二区欧美在线观看| 亚洲深夜福利网站| 欧美日韩网址| 久久亚洲一区二区| 久热成人在线视频| 久久天天躁夜夜躁狠狠躁2022| 欧美视频成人| 亚洲神马久久| 欧美一区不卡| 国模套图日韩精品一区二区| 久久成人人人人精品欧| 久久久亚洲一区| 亚洲国产精品va在线观看黑人| 久久久久国产精品午夜一区| 久久亚洲二区| 亚洲毛片播放| 国产欧美日韩伦理| 久久精品动漫| 亚洲欧洲精品一区二区三区不卡| 亚洲精品午夜精品| 国产精品亚洲第一区在线暖暖韩国| 亚洲资源av| 亚洲成人在线视频播放 | 亚洲人成网站色ww在线| 欧美成ee人免费视频| 亚洲日本欧美| 久久免费少妇高潮久久精品99| 亚洲国产精品t66y| 国产精品免费福利| 蜜臀久久99精品久久久画质超高清| 最新国产精品拍自在线播放| 欧美在线视频观看免费网站| 亚洲精品影院在线观看| 国产亚洲欧美一区二区三区| 欧美韩国在线| 久久久久久日产精品| 亚洲一区二区视频| 日韩亚洲成人av在线| 亚洲第一中文字幕| 久久久中精品2020中文| 午夜免费电影一区在线观看| 亚洲精品久久在线| 亚洲国产精品一区二区www| 国产伦精品一区二区三区视频孕妇 | 欧美激情综合在线| 久久国产精品久久精品国产| 亚洲欧美国产视频|