Repository: aaugustin/websockets
Pull request #1269 (2022-11-27 16:29:53)

Rename Connection to Protocol.
This makes the following naming possible:

* Connection = TCP/TLS connection + pointer to protocol; concerned with opening the network connection, moving bytes, and closing it.
* Protocol = Sans-I/O protocol; concerned with parsing and serializing messages and with the state of the connection.

Previously, these names were reversed in the sockets & threads branch.

The previous choice was influenced by the legacy implementation using "protocol" to describe the two layers together, itself influenced by asyncio using the same word.


Patch:
diff --git a/docs/faq/server.rst b/docs/faq/server.rst
index ab665f2..4e4622d 100644
--- a/docs/faq/server.rst
+++ b/docs/faq/server.rst
@@ -43,19 +43,44 @@ Why can only one client connect at a time?
 ------------------------------------------
 
 Your connection handler blocks the event loop. Look for blocking calls.
+
 Any call that may take some time must be asynchronous.
 
-For example, if you have::
+For example, this connection handler prevents the event loop from running during
+one second::
 
     async def handler(websocket):
         time.sleep(1)
+        ...
 
-change it to::
+Change it to::
 
     async def handler(websocket):
         await asyncio.sleep(1)
+        ...
+
+In addition, calling a coroutine doesn't guarantee that it will yield control to
+the event loop.
+
+For example, this connection handler blocks the event loop by sending messages
+continuously::
+
+    async def handler(websocket):
+        while True:
+            await websocket.send("firehose!")
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.send` completes synchronously as
+long as there's space in send buffers. The event loop never runs. (This pattern
+is uncommon in real-world applications. It occurs mostly in toy programs.)
+
+You can avoid the issue by yielding control to the event loop explicitly::
+
+    async def handler(websocket):
+        while True:
+            await websocket.send("firehose!")
+            await asyncio.sleep(0)
 
-This is part of learning asyncio. It isn't specific to websockets.
+All this is part of learning asyncio. It isn't specific to websockets.
 
 See also Python's documentation about `running blocking code`_.
 
diff --git a/docs/howto/sansio.rst b/docs/howto/sansio.rst
index 197e29d..08b09f7 100644
--- a/docs/howto/sansio.rst
+++ b/docs/howto/sansio.rst
@@ -33,35 +33,36 @@ If you're building a client, parse the URI you'd like to connect to::
 Open a TCP connection to ``(wsuri.host, wsuri.port)`` and perform a TLS
 handshake if ``wsuri.secure`` is :obj:`True`.
 
-Initialize a :class:`~client.ClientConnection`::
+Initialize a :class:`~client.ClientProtocol`::
 
-    from websockets.client import ClientConnection
+    from websockets.client import ClientProtocol
 
-    connection = ClientConnection(wsuri)
+    protocol = ClientProtocol(wsuri)
 
 Create a WebSocket handshake request
-with :meth:`~client.ClientConnection.connect` and send it
-with :meth:`~client.ClientConnection.send_request`::
+with :meth:`~client.ClientProtocol.connect` and send it
+with :meth:`~client.ClientProtocol.send_request`::
 
-    request = connection.connect()
-    connection.send_request(request)
+    request = protocol.connect()
+    protocol.send_request(request)
 
-Then, call :meth:`~connection.Connection.data_to_send` and send its output to
+Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
 the network, as described in `Send data`_ below.
 
-The first event returned by :meth:`~connection.Connection.events_received` is
-the WebSocket handshake response.
+Once you receive enough data, as explained in `Receive data`_ below, the first
+event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket
+handshake response.
 
 When the handshake fails, the reason is available in
-:attr:`~client.ClientConnection.handshake_exc`::
+:attr:`~client.ClientProtocol.handshake_exc`::
 
-    if connection.handshake_exc is not None:
-        raise connection.handshake_exc
+    if protocol.handshake_exc is not None:
+        raise protocol.handshake_exc
 
 Else, the WebSocket connection is open.
 
 A WebSocket client API usually performs the handshake then returns a wrapper
-around the network connection and the :class:`~client.ClientConnection`.
+around the network socket and the :class:`~client.ClientProtocol`.
 
 Server-side
 ...........
@@ -69,45 +70,46 @@ Server-side
 If you're building a server, accept network connections from clients and
 perform a TLS handshake if desired.
 
-For each connection, initialize a :class:`~server.ServerConnection`::
+For each connection, initialize a :class:`~server.ServerProtocol`::
 
-    from websockets.server import ServerConnection
+    from websockets.server import ServerProtocol
 
-    connection = ServerConnection()
+    protocol = ServerProtocol()
 
-The first event returned by :meth:`~connection.Connection.events_received` is
-the WebSocket handshake request.
+Once you receive enough data, as explained in `Receive data`_ below, the first
+event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket
+handshake request.
 
 Create a WebSocket handshake response
-with :meth:`~server.ServerConnection.accept` and send it
-with :meth:`~server.ServerConnection.send_response`::
+with :meth:`~server.ServerProtocol.accept` and send it
+with :meth:`~server.ServerProtocol.send_response`::
 
-    response = connection.accept(request)
-    connection.send_response(response)
+    response = protocol.accept(request)
+    protocol.send_response(response)
 
 Alternatively, you may reject the WebSocket handshake and return an HTTP
-response with :meth:`~server.ServerConnection.reject`::
+response with :meth:`~server.ServerProtocol.reject`::
 
-    response = connection.reject(status, explanation)
-    connection.send_response(response)
+    response = protocol.reject(status, explanation)
+    protocol.send_response(response)
 
-Then, call :meth:`~connection.Connection.data_to_send` and send its output to
+Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
 the network, as described in `Send data`_ below.
 
-Even when you call :meth:`~server.ServerConnection.accept`, the WebSocket
+Even when you call :meth:`~server.ServerProtocol.accept`, the WebSocket
 handshake may fail if the request is incorrect or unsupported.
 
 When the handshake fails, the reason is available in
-:attr:`~server.ServerConnection.handshake_exc`::
+:attr:`~server.ServerProtocol.handshake_exc`::
 
-    if connection.handshake_exc is not None:
-        raise connection.handshake_exc
+    if protocol.handshake_exc is not None:
+        raise protocol.handshake_exc
 
 Else, the WebSocket connection is open.
 
-A WebSocket server API usually builds a wrapper around the network connection
-and the :class:`~server.ServerConnection`. Then it invokes a connection
-handler that accepts the wrapper in argument.
+A WebSocket server API usually builds a wrapper around the network socket and
+the :class:`~server.ServerProtocol`. Then it invokes a connection handler that
+accepts the wrapper in argument.
 
 It may also provide a way to close all connections and to shut down the server
 gracefully.
@@ -122,11 +124,11 @@ Go through the five steps below until you reach the end of the data stream.
 Receive data
 ............
 
-When receiving data from the network, feed it to the connection's
-:meth:`~connection.Connection.receive_data` method.
+When receiving data from the network, feed it to the protocol's
+:meth:`~protocol.Protocol.receive_data` method.
 
-When reaching the end of the data stream, call the connection's
-:meth:`~connection.Connection.receive_eof` method.
+When reaching the end of the data stream, call the protocol's
+:meth:`~protocol.Protocol.receive_eof` method.
 
 For example, if ``sock`` is a :obj:`~socket.socket`::
 
@@ -135,21 +137,21 @@ For example, if ``sock`` is a :obj:`~socket.socket`::
     except OSError:  # socket closed
         data = b""
     if data:
-        connection.receive_data(data)
+        protocol.receive_data(data)
     else:
-        connection.receive_eof()
+        protocol.receive_eof()
 
 These methods aren't expected to raise exceptions — unless you call them again
-after calling :meth:`~connection.Connection.receive_eof`, which is an error.
+after calling :meth:`~protocol.Protocol.receive_eof`, which is an error.
 (If you get an exception, please file a bug!)
 
 Send data
 .........
 
-Then, call :meth:`~connection.Connection.data_to_send` and send its output to
+Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
 the network::
 
-    for data in connection.data_to_send():
+    for data in protocol.data_to_send():
         if data:
             sock.sendall(data)
         else:
@@ -170,7 +172,7 @@ server starts the four-way TCP closing handshake. If the network fails at the
 wrong point, you can end up waiting until the TCP timeout, which is very long.
 
 To prevent dangling TCP connections when you expect the end of the data stream
-but you never reach it, call :meth:`~connection.Connection.close_expected`
+but you never reach it, call :meth:`~protocol.Protocol.close_expected`
 and, if it returns :obj:`True`, schedule closing the TCP connection after a
 short timeout::
 
@@ -185,11 +187,11 @@ data stream, possibly with an exception.
 Close TCP connection
 ....................
 
-If you called :meth:`~connection.Connection.receive_eof`, close the TCP
+If you called :meth:`~protocol.Protocol.receive_eof`, close the TCP
 connection now. This is a clean closure because the receive buffer is empty.
 
-After :meth:`~connection.Connection.receive_eof` signals the end of the read
-stream, :meth:`~connection.Connection.data_to_send` always signals the end of
+After :meth:`~protocol.Protocol.receive_eof` signals the end of the read
+stream, :meth:`~protocol.Protocol.data_to_send` always signals the end of
 the write stream, unless it already ended. So, at this point, the TCP
 connection is already half-closed. The only reason for closing it now is to
 release resources related to the socket.
@@ -199,8 +201,8 @@ Now you can exit the loop relaying data from the network to the application.
 Receive events
 ..............
 
-Finally, call :meth:`~connection.Connection.events_received` to obtain events
-parsed from the data provided to :meth:`~connection.Connection.receive_data`::
+Finally, call :meth:`~protocol.Protocol.events_received` to obtain events
+parsed from the data provided to :meth:`~protocol.Protocol.receive_data`::
 
     events = connection.events_received()
 
@@ -224,9 +226,9 @@ The connection object provides one method for each type of WebSocket frame.
 
 For sending a data frame:
 
-* :meth:`~connection.Connection.send_continuation`
-* :meth:`~connection.Connection.send_text`
-* :meth:`~connection.Connection.send_binary`
+* :meth:`~protocol.Protocol.send_continuation`
+* :meth:`~protocol.Protocol.send_text`
+* :meth:`~protocol.Protocol.send_binary`
 
 These methods raise :exc:`~exceptions.ProtocolError` if you don't set
 the :attr:`FIN <websockets.frames.Frame.fin>` bit correctly in fragmented
@@ -234,21 +236,21 @@ messages.
 
 For sending a control frame:
 
-* :meth:`~connection.Connection.send_close`
-* :meth:`~connection.Connection.send_ping`
-* :meth:`~connection.Connection.send_pong`
+* :meth:`~protocol.Protocol.send_close`
+* :meth:`~protocol.Protocol.send_ping`
+* :meth:`~protocol.Protocol.send_pong`
 
-:meth:`~connection.Connection.send_close` initiates the closing handshake.
+:meth:`~protocol.Protocol.send_close` initiates the closing handshake.
 See `Closing a connection`_ below for details.
 
 If you encounter an unrecoverable error and you must fail the WebSocket
-connection, call :meth:`~connection.Connection.fail`.
+connection, call :meth:`~protocol.Protocol.fail`.
 
-After any of the above, call :meth:`~connection.Connection.data_to_send` and
+After any of the above, call :meth:`~protocol.Protocol.data_to_send` and
 send its output to the network, as shown in `Send data`_ above.
 
-If you called :meth:`~connection.Connection.send_close`
-or :meth:`~connection.Connection.fail`, you expect the end of the data
+If you called :meth:`~protocol.Protocol.send_close`
+or :meth:`~protocol.Protocol.fail`, you expect the end of the data
 stream. You should follow the process described in `Close TCP connection`_
 above in order to prevent dangling TCP connections.
 
@@ -272,10 +274,10 @@ When a client wants to close the TCP connection:
 Applying the rules described earlier in this document gives the intended
 result. As a reminder, the rules are:
 
-* When :meth:`~connection.Connection.data_to_send` returns the empty
+* When :meth:`~protocol.Protocol.data_to_send` returns the empty
   bytestring, close the write side of the TCP connection.
 * When you reach the end of the read stream, close the TCP connection.
-* When :meth:`~connection.Connection.close_expected` returns :obj:`True`, if
+* When :meth:`~protocol.Protocol.close_expected` returns :obj:`True`, if
   you don't reach the end of the read stream quickly, close the TCP connection.
 
 Fragmentation
@@ -306,14 +308,14 @@ should happen automatically in a cooperative multitasking environment.
 
 However, you still have to make sure you don't break this property by
 accident. For example, serialize writes to the network
-when :meth:`~connection.Connection.data_to_send` returns multiple values to
+when :meth:`~protocol.Protocol.data_to_send` returns multiple values to
 prevent concurrent writes from interleaving incorrectly.
 
 Avoid buffers
 .............
 
 The Sans-I/O layer doesn't do any buffering. It makes events available in
-:meth:`~connection.Connection.events_received` as soon as they're received.
+:meth:`~protocol.Protocol.events_received` as soon as they're received.
 
 You should make incoming messages available to the application immediately and
 stop further processing until the application fetches them. This will usually
diff --git a/docs/project/changelog.rst b/docs/project/changelog.rst
index 40e090c..532eeff 100644
--- a/docs/project/changelog.rst
+++ b/docs/project/changelog.rst
@@ -33,6 +33,18 @@ They may change at any time.
 Backwards-incompatible changes
 ..............................
 
+.. admonition:: The Sans-I/O implementation was moved.
+    :class: caution
+
+    Aliases provide compatibility for all previously public APIs according to
+    the `backwards-compatibility policy`_
+
+    * The ``connection`` module was renamed to ``protocol``.
+
+    * The ``connection.Connection``, ``server.ServerConnection``, and
+      ``client.ClientConnection`` classes were renamed to ``protocol.Protocol``,
+      ``server.ServerProtocol``, and ``client.ClientProtocol``.
+
 .. admonition:: Closing a connection without an empty close frame is OK.
     :class: note
 
@@ -43,7 +55,6 @@ Backwards-incompatible changes
     As a consequence, calling ``WebSocket.close()`` without arguments in a
     browser isn't reported as an error anymore.
 
-
 New features
 ............
 
@@ -86,8 +97,8 @@ Backwards-incompatible changes
 .. admonition:: The ``exception`` attribute of :class:`~http11.Request` and :class:`~http11.Response` is deprecated.
     :class: note
 
-    Use the ``handshake_exc`` attribute of :class:`~server.ServerConnection` and
-    :class:`~client.ClientConnection` instead.
+    Use the ``handshake_exc`` attribute of :class:`~server.ServerProtocol` and
+    :class:`~client.ClientProtocol` instead.
 
     See :doc:`../howto/sansio` for details.
 
diff --git a/docs/reference/client.rst b/docs/reference/client.rst
index b72f49f..44f053b 100644
--- a/docs/reference/client.rst
+++ b/docs/reference/client.rst
@@ -69,7 +69,7 @@ Using a connection
 Sans-I/O
 --------
 
-.. autoclass:: ClientConnection(wsuri, origin=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None)
+.. autoclass:: ClientProtocol(wsuri, origin=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None)
 
     .. automethod:: receive_data
 
diff --git a/docs/reference/common.rst b/docs/reference/common.rst
index 6ba11bf..b42f5ea 100644
--- a/docs/reference/common.rst
+++ b/docs/reference/common.rst
@@ -57,9 +57,9 @@ asyncio
 Sans-I/O
 --------
 
-.. automodule:: websockets.connection
+.. automodule:: websockets.protocol
 
-.. autoclass:: Connection(side, state=State.OPEN, max_size=2 ** 20, logger=None)
+.. autoclass:: Protocol(side, state=State.OPEN, max_size=2 ** 20, logger=None)
 
     .. automethod:: receive_data
 
diff --git a/docs/reference/server.rst b/docs/reference/server.rst
index 12fe1f8..50ef4ee 100644
--- a/docs/reference/server.rst
+++ b/docs/reference/server.rst
@@ -115,7 +115,7 @@ websockets supports HTTP Basic Authentication according to
 Sans-I/O
 --------
 
-.. autoclass:: ServerConnection(origins=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None)
+.. autoclass:: ServerProtocol(origins=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2 ** 20, logger=None)
 
     .. automethod:: receive_data
 
diff --git a/docs/reference/types.rst b/docs/reference/types.rst
index d86429b..88550d0 100644
--- a/docs/reference/types.rst
+++ b/docs/reference/types.rst
@@ -15,7 +15,7 @@ Types
 
     .. autodata:: ExtensionParameter
 
-.. autodata:: websockets.connection.Event
+.. autodata:: websockets.protocol.Event
 
 .. autodata:: websockets.datastructures.HeadersLike
 
diff --git a/src/websockets/__init__.py b/src/websockets/__init__.py
index ec34841..826decc 100644
--- a/src/websockets/__init__.py
+++ b/src/websockets/__init__.py
@@ -9,7 +9,7 @@ __all__ = [  # noqa
     "basic_auth_protocol_factory",
     "BasicAuthWebSocketServerProtocol",
     "broadcast",
-    "ClientConnection",
+    "ClientProtocol",
     "connect",
     "ConnectionClosed",
     "ConnectionClosedError",
@@ -40,7 +40,7 @@ __all__ = [  # noqa
     "RedirectHandshake",
     "SecurityError",
     "serve",
-    "ServerConnection",
+    "ServerProtocol",
     "Subprotocol",
     "unix_connect",
     "unix_serve",
@@ -60,7 +60,7 @@ lazy_import(
         "basic_auth_protocol_factory": ".legacy.auth",
         "BasicAuthWebSocketServerProtocol": ".legacy.auth",
         "broadcast": ".legacy.protocol",
-        "ClientConnection": ".client",
+        "ClientProtocol": ".client",
         "connect": ".legacy.client",
         "unix_connect": ".legacy.client",
         "WebSocketClientProtocol": ".legacy.client",
@@ -93,7 +93,7 @@ lazy_import(
         "WebSocketProtocolError": ".exceptions",
         "protocol": ".legacy",
         "WebSocketCommonProtocol": ".legacy.protocol",
-        "ServerConnection": ".server",
+        "ServerProtocol": ".server",
         "serve": ".legacy.server",
         "unix_serve": ".legacy.server",
         "WebSocketServerProtocol": ".legacy.server",
diff --git a/src/websockets/client.py b/src/websockets/client.py
index 373e6b7..a439ab8 100644
--- a/src/websockets/client.py
+++ b/src/websockets/client.py
@@ -1,8 +1,8 @@
 from __future__ import annotations
 
-from typing import Generator, List, Optional, Sequence
+import warnings
+from typing import Any, Generator, List, Optional, Sequence
 
-from .connection import CLIENT, CONNECTING, OPEN, Connection, State
 from .datastructures import Headers, MultipleValuesError
 from .exceptions import (
     InvalidHandshake,
@@ -24,6 +24,7 @@ from .headers import (
     parse_upgrade,
 )
 from .http11 import Request, Response
+from .protocol import CLIENT, CONNECTING, OPEN, Protocol, State
 from .typing import (
     ConnectionOption,
     ExtensionHeader,
@@ -40,10 +41,10 @@ from .utils import accept_key, generate_key
 from .legacy.client import *  # isort:skip  # noqa
 
 
-__all__ = ["ClientConnection"]
+__all__ = ["ClientProtocol"]
 
 
-class ClientConnection(Connection):
+class ClientProtocol(Protocol):
     """
     Sans-I/O implementation of a WebSocket client connection.
 
@@ -342,3 +343,12 @@ class ClientConnection(Connection):
                 self.events.append(response)
 
         yield from super().parse()
+
+
+class ClientConnection(ClientProtocol):
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        warnings.warn(
+            "ClientConnection was renamed to ClientProtocol",
+            DeprecationWarning,
+        )
+        super().__init__(*args, **kwargs)
diff --git a/src/websockets/connection.py b/src/websockets/connection.py
index 7a9be9f..5ce4d6a 100644
--- a/src/websockets/connection.py
+++ b/src/websockets/connection.py
@@ -1,702 +1,13 @@
 from __future__ import annotations
 
-import enum
-import logging
-import uuid
-from typing import Generator, List, Optional, Type, Union
+import warnings
 
-from .exceptions import (
-    ConnectionClosed,
-    ConnectionClosedError,
-    ConnectionClosedOK,
-    InvalidState,
-    PayloadTooBig,
-    ProtocolError,
-)
-from .extensions import Extension
-from .frames import (
-    OK_CLOSE_CODES,
-    OP_BINARY,
-    OP_CLOSE,
-    OP_CONT,
-    OP_PING,
-    OP_PONG,
-    OP_TEXT,
-    Close,
-    Frame,
-)
-from .http11 import Request, Response
-from .streams import StreamReader
-from .typing import LoggerLike, Origin, Subprotocol
-
-
-__all__ = [
-    "Connection",
-    "Side",
-    "State",
-    "SEND_EOF",
-]
-
-Event = Union[Request, Response, Frame]
-"""Events that :meth:`~Connection.events_received` may return."""
-
-
-class Side(enum.IntEnum):
-    """A WebSocket connection is either a server or a client."""
-
-    SERVER, CLIENT = range(2)
-
-
-SERVER = Side.SERVER
-CLIENT = Side.CLIENT
-
-
-class State(enum.IntEnum):
-    """A WebSocket connection is in one of these four states."""
-
-    CONNECTING, OPEN, CLOSING, CLOSED = range(4)
-
-
-CONNECTING = State.CONNECTING
-OPEN = State.OPEN
-CLOSING = State.CLOSING
-CLOSED = State.CLOSED
-
-
-SEND_EOF = b""
-"""Sentinel signaling that the TCP connection must be half-closed."""
-
-
-class Connection:
-    """
-    Sans-I/O implementation of a WebSocket connection.
-
-    Args:
-        side: :attr:`~Side.CLIENT` or :attr:`~Side.SERVER`.
-        state: initial state of the WebSocket connection.
-        max_size: maximum size of incoming messages in bytes;
-            :obj:`None` to disable the limit.
-        logger: logger for this connection; depending on ``side``,
-            defaults to ``logging.getLogger("websockets.client")``
-            or ``logging.getLogger("websockets.server")``;
-            see the :doc:`logging guide <../topics/logging>` for details.
-
-    """
-
-    def __init__(
-        self,
-        side: Side,
-        state: State = OPEN,
-        max_size: Optional[int] = 2**20,
-        logger: Optional[LoggerLike] = None,
-    ) -> None:
-        # Unique identifier. For logs.
-        self.id: uuid.UUID = uuid.uuid4()
-        """Unique identifier of the connection. Useful in logs."""
-
-        # Logger or LoggerAdapter for this connection.
-        if logger is None:
-            logger = logging.getLogger(f"websockets.{side.name.lower()}")
-        self.logger: LoggerLike = logger
-        """Logger for this connection."""
-
-        # Track if DEBUG is enabled. Shortcut logging calls if it isn't.
-        self.debug = logger.isEnabledFor(logging.DEBUG)
-
-        # Connection side. CLIENT or SERVER.
-        self.side = side
-
-        # Connection state. Initially OPEN because subclasses handle CONNECTING.
-        self.state = state
-
-        # Maximum size of incoming messages in bytes.
-        self.max_size = max_size
-
-        # Current size of incoming message in bytes. Only set while reading a
-        # fragmented message i.e. a data frames with the FIN bit not set.
-        self.cur_size: Optional[int] = None
-
-        # True while sending a fragmented message i.e. a data frames with the
-        # FIN bit not set.
-        self.expect_continuation_frame = False
-
-        # WebSocket protocol parameters.
-        self.origin: Optional[Origin] = None
-        self.extensions: List[Extension] = []
-        self.subprotocol: Optional[Subprotocol] = None
-
-        # Close code and reason, set when a close frame is sent or received.
-        self.close_rcvd: Optional[Close] = None
-        self.close_sent: Optional[Close] = None
-        self.close_rcvd_then_sent: Optional[bool] = None
-
-        # Track if an exception happened during the handshake.
-        self.handshake_exc: Optional[Exception] = None
-        """
-        Exception to raise if the opening handshake failed.
-
-        :obj:`None` if the opening handshake succeeded.
-
-        """
-
-        # Track if send_eof() was called.
-        self.eof_sent = False
-
-        # Parser state.
-        self.reader = StreamReader()
-        self.events: List[Event] = []
-        self.writes: List[bytes] = []
-        self.parser = self.parse()
-        next(self.parser)  # start coroutine
-        self.parser_exc: Optional[Exception] = None
-
-    @property
-    def state(self) -> State:
-        """
-        WebSocket connection state.
-
-        Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`.
-
-        """
-        return self._state
-
-    @state.setter
-    def state(self, state: State) -> None:
-        if self.debug:
-            self.logger.debug("= connection is %s", state.name)
-        self._state = state
-
-    @property
-    def close_code(self) -> Optional[int]:
-        """
-        `WebSocket close code`_.
-
-        .. _WebSocket close code:
-            https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
-
-        :obj:`None` if the connection isn't closed yet.
-
-        """
-        if self.state is not CLOSED:
-            return None
-        elif self.close_rcvd is None:
-            return 1006
-        else:
-            return self.close_rcvd.code
-
-    @property
-    def close_reason(self) -> Optional[str]:
-        """
-        `WebSocket close reason`_.
-
-        .. _WebSocket close reason:
-            https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6
-
-        :obj:`None` if the connection isn't closed yet.
-
-        """
-        if self.state is not CLOSED:
-            return None
-        elif self.close_rcvd is None:
-            return ""
-        else:
-            return self.close_rcvd.reason
-
-    @property
-    def close_exc(self) -> ConnectionClosed:
-        """
-        Exception to raise when trying to interact with a closed connection.
-
-        Don't raise this exception while the connection :attr:`state`
-        is :attr:`~websockets.connection.State.CLOSING`; wait until
-        it's :attr:`~websockets.connection.State.CLOSED`.
-
-        Indeed, the exception includes the close code and reason, which are
-        known only once the connection is closed.
-
-        Raises:
-            AssertionError: if the connection isn't closed yet.
-
-        """
-        assert self.state is CLOSED, "connection isn't closed yet"
-        exc_type: Type[ConnectionClosed]
-        if (
-            self.close_rcvd is not None
-            and self.close_sent is not None
-            and self.close_rcvd.code in OK_CLOSE_CODES
-            and self.close_sent.code in OK_CLOSE_CODES
-        ):
-            exc_type = ConnectionClosedOK
-        else:
-            exc_type = ConnectionClosedError
-        exc: ConnectionClosed = exc_type(
-            self.close_rcvd,
-            self.close_sent,
-            self.close_rcvd_then_sent,
-        )
-        # Chain to the exception raised in the parser, if any.
-        exc.__cause__ = self.parser_exc
-        return exc
-
-    # Public methods for receiving data.
-
-    def receive_data(self, data: bytes) -> None:
-        """
-        Receive data from the network.
-
-        After calling this method:
-
-        - You must call :meth:`data_to_send` and send this data to the network.
-        - You should call :meth:`events_received` and process resulting events.
-
-        Raises:
-            EOFError: if :meth:`receive_eof` was called earlier.
-
-        """
-        self.reader.feed_data(data)
-        next(self.parser)
-
-    def receive_eof(self) -> None:
-        """
-        Receive the end of the data stream from the network.
-
-        After calling this method:
-
-        - You must call :meth:`data_to_send` and send this data to the network.
-        - You aren't expected to call :meth:`events_received`; it won't return
-          any new events.
-
-        Raises:
-            EOFError: if :meth:`receive_eof` was called earlier.
-
-        """
-        self.reader.feed_eof()
-        next(self.parser)
-
-    # Public methods for sending events.
-
-    def send_continuation(self, data: bytes, fin: bool) -> None:
-        """
-        Send a `Continuation frame`_.
-
-        .. _Continuation frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
-
-        Parameters:
-            data: payload containing the same kind of data
-                as the initial frame.
-            fin: FIN bit; set it to :obj:`True` if this is the last frame
-                of a fragmented message and to :obj:`False` otherwise.
-
-        Raises:
-            ProtocolError: if a fragmented message isn't in progress.
-
-        """
-        if not self.expect_continuation_frame:
-            raise ProtocolError("unexpected continuation frame")
-        self.expect_continuation_frame = not fin
-        self.send_frame(Frame(OP_CONT, data, fin))
-
-    def send_text(self, data: bytes, fin: bool = True) -> None:
-        """
-        Send a `Text frame`_.
-
-        .. _Text frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
-
-        Parameters:
-            data: payload containing text encoded with UTF-8.
-            fin: FIN bit; set it to :obj:`False` if this is the first frame of
-                a fragmented message.
-
-        Raises:
-            ProtocolError: if a fragmented message is in progress.
-
-        """
-        if self.expect_continuation_frame:
-            raise ProtocolError("expected a continuation frame")
-        self.expect_continuation_frame = not fin
-        self.send_frame(Frame(OP_TEXT, data, fin))
-
-    def send_binary(self, data: bytes, fin: bool = True) -> None:
-        """
-        Send a `Binary frame`_.
+# lazy_import doesn't support this use case.
+from .protocol import SEND_EOF, Protocol as Connection, Side, State  # noqa
 
-        .. _Binary frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
 
-        Parameters:
-            data: payload containing arbitrary binary data.
-            fin: FIN bit; set it to :obj:`False` if this is the first frame of
-                a fragmented message.
-
-        Raises:
-            ProtocolError: if a fragmented message is in progress.
-
-        """
-        if self.expect_continuation_frame:
-            raise ProtocolError("expected a continuation frame")
-        self.expect_continuation_frame = not fin
-        self.send_frame(Frame(OP_BINARY, data, fin))
-
-    def send_close(self, code: Optional[int] = None, reason: str = "") -> None:
-        """
-        Send a `Close frame`_.
-
-        .. _Close frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1
-
-        Parameters:
-            code: close code.
-            reason: close reason.
-
-        Raises:
-            ProtocolError: if a fragmented message is being sent, if the code
-                isn't valid, or if a reason is provided without a code
-
-        """
-        if self.expect_continuation_frame:
-            raise ProtocolError("expected a continuation frame")
-        if code is None:
-            if reason != "":
-                raise ProtocolError("cannot send a reason without a code")
-            close = Close(1005, "")
-            data = b""
-        else:
-            close = Close(code, reason)
-            data = close.serialize()
-        # send_frame() guarantees that self.state is OPEN at this point.
-        # 7.1.3. The WebSocket Closing Handshake is Started
-        self.send_frame(Frame(OP_CLOSE, data))
-        self.close_sent = close
-        self.state = CLOSING
-
-    def send_ping(self, data: bytes) -> None:
-        """
-        Send a `Ping frame`_.
-
-        .. _Ping frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
-
-        Parameters:
-            data: payload containing arbitrary binary data.
-
-        """
-        self.send_frame(Frame(OP_PING, data))
-
-    def send_pong(self, data: bytes) -> None:
-        """
-        Send a `Pong frame`_.
-
-        .. _Pong frame:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3
-
-        Parameters:
-            data: payload containing arbitrary binary data.
-
-        """
-        self.send_frame(Frame(OP_PONG, data))
-
-    def fail(self, code: int, reason: str = "") -> None:
-        """
-        `Fail the WebSocket connection`_.
-
-        .. _Fail the WebSocket connection:
-            https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7
-
-        Parameters:
-            code: close code
-            reason: close reason
-
-        Raises:
-            ProtocolError: if the code isn't valid.
-        """
-        # 7.1.7. Fail the WebSocket Connection
-
-        # Send a close frame when the state is OPEN (a close frame was already
-        # sent if it's CLOSING), except when failing the connection because
-        # of an error reading from or writing to the network.
-        if self.state is OPEN:
-            if code != 1006:
-                close = Close(code, reason)
-                data = close.serialize()
-                self.send_frame(Frame(OP_CLOSE, data))
-                self.close_sent = close
-                self.state = CLOSING
-
-        # When failing the connection, a server closes the TCP connection
-        # without waiting for the client to complete the handshake, while a
-        # client waits for the server to close the TCP connection, possibly
-        # after sending a close frame that the client will ignore.
-        if self.side is SERVER and not self.eof_sent:
-            self.send_eof()
-
-        # 7.1.7. Fail the WebSocket Connection "An endpoint MUST NOT continue
-        # to attempt to process data(including a responding Close frame) from
-        # the remote endpoint after being instructed to _Fail the WebSocket
-        # Connection_."
-        self.parser = self.discard()
-        next(self.parser)  # start coroutine
-
-    # Public method for getting incoming events after receiving data.
-
-    def events_received(self) -> List[Event]:
-        """
-        Fetch events generated from data received from the network.
-
-        Call this method immediately after any of the ``receive_*()`` methods.
-
-        Process resulting events, likely by passing them to the application.
-
-        Returns:
-            List[Event]: Events read from the connection.
-        """
-        events, self.events = self.events, []
-        return events
-
-    # Public method for getting outgoing data after receiving data or sending events.
-
-    def data_to_send(self) -> List[bytes]:
-        """
-        Obtain data to send to the network.
-
-        Call this method immediately after any of the ``receive_*()``,
-        ``send_*()``, or :meth:`fail` methods.
-
-        Write resulting data to the connection.
-
-        The empty bytestring :data:`~websockets.connection.SEND_EOF` signals
-        the end of the data stream. When you receive it, half-close the TCP
-        connection.
-
-        Returns:
-            List[bytes]: Data to write to the connection.
-
-        """
-        writes, self.writes = self.writes, []
-        return writes
-
-    def close_expected(self) -> bool:
-        """
-        Tell if the TCP connection is expected to close soon.
-
-        Call this method immediately after any of the ``receive_*()`` or
-        :meth:`fail` methods.
-
-        If it returns :obj:`True`, schedule closing the TCP connection after a
-        short timeout if the other side hasn't already closed it.
-
-        Returns:
-            bool: Whether the TCP connection is expected to close soon.
-
-        """
-        # We expect a TCP close if and only if we sent a close frame:
-        # * Normal closure: once we send a close frame, we expect a TCP close:
-        #   server waits for client to complete the TCP closing handshake;
-        #   client waits for server to initiate the TCP closing handshake.
-        # * Abnormal closure: we always send a close frame and the same logic
-        #   applies, except on EOFError where we don't send a close frame
-        #   because we already received the TCP close, so we don't expect it.
-        # We already got a TCP Close if and only if the state is CLOSED.
-        return self.state is CLOSING or self.handshake_exc is not None
-
-    # Private methods for receiving data.
-
-    def parse(self) -> Generator[None, None, None]:
-        """
-        Parse incoming data into frames.
-
-        :meth:`receive_data` and :meth:`receive_eof` run this generator
-        coroutine until it needs more data or reaches EOF.
-
-        """
-        try:
-            while True:
-                if (yield from self.reader.at_eof()):
-                    if self.debug:
-                        self.logger.debug("< EOF")
-                    # If the WebSocket connection is closed cleanly, with a
-                    # closing handhshake, recv_frame() substitutes parse()
-                    # with discard(). This branch is reached only when the
-                    # connection isn't closed cleanly.
-                    raise EOFError("unexpected end of stream")
-
-                if self.max_size is None:
-                    max_size = None
-                elif self.cur_size is None:
-                    max_size = self.max_size
-                else:
-                    max_size = self.max_size - self.cur_size
-
-                # During a normal closure, execution ends here on the next
-                # iteration of the loop after receiving a close frame. At
-                # this point, recv_frame() replaced parse() by discard().
-                frame = yield from Frame.parse(
-                    self.reader.read_exact,
-                    mask=self.side is SERVER,
-                    max_size=max_size,
-                    extensions=self.extensions,
-                )
-
-                if self.debug:
-                    self.logger.debug("< %s", frame)
-
-                self.recv_frame(frame)
-
-        except ProtocolError as exc:
-            self.fail(1002, str(exc))
-            self.parser_exc = exc
-
-        except EOFError as exc:
-            self.fail(1006, str(exc))
-            self.parser_exc = exc
-
-        except UnicodeDecodeError as exc:
-            self.fail(1007, f"{exc.reason} at position {exc.start}")
-            self.parser_exc = exc
-
-        except PayloadTooBig as exc:
-            self.fail(1009, str(exc))
-            self.parser_exc = exc
-
-        except Exception as exc:
-            self.logger.error("parser failed", exc_info=True)
-            # Don't include exception details, which may be security-sensitive.
-            self.fail(1011)
-            self.parser_exc = exc
-
-        # During an abnormal closure, execution ends here after catching an
-        # exception. At this point, fail() replaced parse() by discard().
-        yield
-        raise AssertionError("parse() shouldn't step after error")
-
-    def discard(self) -> Generator[None, None, None]:
-        """
-        Discard incoming data.
-
-        This coroutine replaces :meth:`parse`:
-
-        - after receiving a close frame, during a normal closure (1.4);
-        - after sending a close frame, during an abnormal closure (7.1.7).
-
-        """
-        # The server close the TCP connection in the same circumstances where
-        # discard() replaces parse(). The client closes the connection later,
-        # after the server closes the connection or a timeout elapses.
-        # (The latter case cannot be handled in this Sans-I/O layer.)
-        assert (self.side is SERVER) == (self.eof_sent)
-        while not (yield from self.reader.at_eof()):
-            self.reader.discard()
-        if self.debug:
-            self.logger.debug("< EOF")
-        # A server closes the TCP connection immediately, while a client
-        # waits for the server to close the TCP connection.
-        if self.side is CLIENT:
-            self.send_eof()
-        self.state = CLOSED
-        # If discard() completes normally, execution ends here.
-        yield
-        # Once the reader reaches EOF, its feed_data/eof() methods raise an
-        # error, so our receive_data/eof() methods don't step the generator.
-        raise AssertionError("discard() shouldn't step after EOF")
-
-    def recv_frame(self, frame: Frame) -> None:
-        """
-        Process an incoming frame.
-
-        """
-        if frame.opcode is OP_TEXT or frame.opcode is OP_BINARY:
-            if self.cur_size is not None:
-                raise ProtocolError("expected a continuation frame")
-            if frame.fin:
-                self.cur_size = None
-            else:
-                self.cur_size = len(frame.data)
-
-        elif frame.opcode is OP_CONT:
-            if self.cur_size is None:
-                raise ProtocolError("unexpected continuation frame")
-            if frame.fin:
-                self.cur_size = None
-            else:
-                self.cur_size += len(frame.data)
-
-        elif frame.opcode is OP_PING:
-            # 5.5.2. Ping: "Upon receipt of a Ping frame, an endpoint MUST
-            # send a Pong frame in response"
-            pong_frame = Frame(OP_PONG, frame.data)
-            self.send_frame(pong_frame)
-
-        elif frame.opcode is OP_PONG:
-            # 5.5.3 Pong: "A response to an unsolicited Pong frame is not
-            # expected."
-            pass
-
-        elif frame.opcode is OP_CLOSE:
-            # 7.1.5.  The WebSocket Connection Close Code
-            # 7.1.6.  The WebSocket Connection Close Reason
-            self.close_rcvd = Close.parse(frame.data)
-            if self.state is CLOSING:
-                assert self.close_sent is not None
-                self.close_rcvd_then_sent = False
-
-            if self.cur_size is not None:
-                raise ProtocolError("incomplete fragmented message")
-
-            # 5.5.1 Close: "If an endpoint receives a Close frame and did
-            # not previously send a Close frame, the endpoint MUST send a
-            # Close frame in response. (When sending a Close frame in
-            # response, the endpoint typically echos the status code it
-            # received.)"
-
-            if self.state is OPEN:
-                # Echo the original data instead of re-serializing it with
-                # Close.serialize() because that fails when the close frame
-                # is empty and Close.parse() synthetizes a 1005 close code.
-                # The rest is identical to send_close().
-                self.send_frame(Frame(OP_CLOSE, frame.data))
-                self.close_sent = self.close_rcvd
-                self.close_rcvd_then_sent = True
-                self.state = CLOSING
-
-            # 7.1.2. Start the WebSocket Closing Handshake: "Once an
-            # endpoint has both sent and received a Close control frame,
-            # that endpoint SHOULD _Close the WebSocket Connection_"
-
-            # A server closes the TCP connection immediately, while a client
-            # waits for the server to close the TCP connection.
-            if self.side is SERVER:
-                self.send_eof()
-
-            # 1.4. Closing Handshake: "after receiving a control frame
-            # indicating the connection should be closed, a peer discards
-            # any further data received."
-            self.parser = self.discard()
-            next(self.parser)  # start coroutine
-
-        else:
-            # This can't happen because Frame.parse() validates opcodes.
-            raise AssertionError(f"unexpected opcode: {frame.opcode:02x}")
-
-        self.events.append(frame)
-
-    # Private methods for sending events.
-
-    def send_frame(self, frame: Frame) -> None:
-        if self.state is not OPEN:
-            raise InvalidState(
-                f"cannot write to a WebSocket in the {self.state.name} state"
-            )
-
-        if self.debug:
-            self.logger.debug("> %s", frame)
-        self.writes.append(
-            frame.serialize(mask=self.side is CLIENT, extensions=self.extensions)
-        )
-
-    def send_eof(self) -> None:
-        assert not self.eof_sent
-        self.eof_sent = True
-        if self.debug:
-            self.logger.debug("> EOF")
-        self.writes.append(SEND_EOF)
+warnings.warn(
+    "websockets.connection was renamed to websockets.protocol "
+    "and Connection was renamed to Protocol",
+    DeprecationWarning,
+)
diff --git a/src/websockets/http11.py b/src/websockets/http11.py
index 6824919..ec4e3b8 100644
--- a/src/websockets/http11.py
+++ b/src/websockets/http11.py
@@ -68,7 +68,7 @@ class Request:
     def exception(self) -> Optional[Exception]:  # pragma: no cover
         warnings.warn(
             "Request.exception is deprecated; "
-            "use ServerConnection.handshake_exc instead",
+            "use ServerProtocol.handshake_exc instead",
             DeprecationWarning,
         )
         return self._exception
@@ -172,7 +172,7 @@ class Response:
     def exception(self) -> Optional[Exception]:  # pragma: no cover
         warnings.warn(
             "Response.exception is deprecated; "
-            "use ClientConnection.handshake_exc instead",
+            "use ClientProtocol.handshake_exc instead",
             DeprecationWarning,
         )
         return self._exception
diff --git a/src/websockets/legacy/protocol.py b/src/websockets/legacy/protocol.py
index 9f2bda1..7881b94 100644
--- a/src/websockets/legacy/protocol.py
+++ b/src/websockets/legacy/protocol.py
@@ -27,7 +27,6 @@ from typing import (
     cast,
 )
 
-from ..connection import State
 from ..datastructures import Headers
 from ..exceptions import (
     ConnectionClosed,
@@ -51,6 +50,7 @@ from ..frames import (
     prepare_ctrl,
     prepare_data,
 )
+from ..protocol import State
 from ..typing import Data, LoggerLike, Subprotocol
 from .compatibility import loop_if_py_lt_38
 from .framing import Frame
diff --git a/src/websockets/legacy/server.py b/src/websockets/legacy/server.py
index f37a16c..eabeb8e 100644
--- a/src/websockets/legacy/server.py
+++ b/src/websockets/legacy/server.py
@@ -25,7 +25,6 @@ from typing import (
     cast,
 )
 
-from ..connection import State
 from ..datastructures import Headers, HeadersLike, MultipleValuesError
 from ..exceptions import (
     AbortHandshake,
@@ -45,6 +44,7 @@ from ..headers import (
     validate_subprotocols,
 )
 from ..http import USER_AGENT
+from ..protocol import State
 from ..typing import ExtensionHeader, LoggerLike, Origin, Subprotocol
 from .compatibility import loop_if_py_lt_38
 from .handshake import build_response, check_request
diff --git a/src/websockets/protocol.py b/src/websockets/protocol.py
new file mode 100644
index 0000000..29d7e15
--- /dev/null
+++ b/src/websockets/protocol.py
@@ -0,0 +1,702 @@
+from __future__ import annotations
+
+import enum
+import logging
+import uuid
+from typing import Generator, List, Optional, Type, Union
+
+from .exceptions import (
+    ConnectionClosed,
+    ConnectionClosedError,
+    ConnectionClosedOK,
+    InvalidState,
+    PayloadTooBig,
+    ProtocolError,
+)
+from .extensions import Extension
+from .frames import (
+    OK_CLOSE_CODES,
+    OP_BINARY,
+    OP_CLOSE,
+    OP_CONT,
+    OP_PING,
+    OP_PONG,
+    OP_TEXT,
+    Close,
+    Frame,
+)
+from .http11 import Request, Response
+from .streams import StreamReader
+from .typing import LoggerLike, Origin, Subprotocol
+
+
+__all__ = [
+    "Protocol",
+    "Side",
+    "State",
+    "SEND_EOF",
+]
+
+Event = Union[Request, Response, Frame]
+"""Events that :meth:`~Protocol.events_received` may return."""
+
+
+class Side(enum.IntEnum):
+    """A WebSocket connection is either a server or a client."""
+
+    SERVER, CLIENT = range(2)
+
+
+SERVER = Side.SERVER
+CLIENT = Side.CLIENT
+
+
+class State(enum.IntEnum):
+    """A WebSocket connection is in one of these four states."""
+
+    CONNECTING, OPEN, CLOSING, CLOSED = range(4)
+
+
+CONNECTING = State.CONNECTING
+OPEN = State.OPEN
+CLOSING = State.CLOSING
+CLOSED = State.CLOSED
+
+
+SEND_EOF = b""
+"""Sentinel signaling that the TCP connection must be half-closed."""
+
+
+class Protocol:
+    """
+    Sans-I/O implementation of a WebSocket connection.
+
+    Args:
+        side: :attr:`~Side.CLIENT` or :attr:`~Side.SERVER`.
+        state: initial state of the WebSocket connection.
+        max_size: maximum size of incoming messages in bytes;
+            :obj:`None` to disable the limit.
+        logger: logger for this connection; depending on ``side``,
+            defaults to ``logging.getLogger("websockets.client")``
+            or ``logging.getLogger("websockets.server")``;
+            see the :doc:`logging guide <../topics/logging>` for details.
+
+    """
+
+    def __init__(
+        self,
+        side: Side,
+        state: State = OPEN,
+        max_size: Optional[int] = 2**20,
+        logger: Optional[LoggerLike] = None,
+    ) -> None:
+        # Unique identifier. For logs.
+        self.id: uuid.UUID = uuid.uuid4()
+        """Unique identifier of the connection. Useful in logs."""
+
+        # Logger or LoggerAdapter for this connection.
+        if logger is None:
+            logger = logging.getLogger(f"websockets.{side.name.lower()}")
+        self.logger: LoggerLike = logger
+        """Logger for this connection."""
+
+        # Track if DEBUG is enabled. Shortcut logging calls if it isn't.
+        self.debug = logger.isEnabledFor(logging.DEBUG)
+
+        # Connection side. CLIENT or SERVER.
+        self.side = side
+
+        # Connection state. Initially OPEN because subclasses handle CONNECTING.
+        self.state = state
+
+        # Maximum size of incoming messages in bytes.
+        self.max_size = max_size
+
+        # Current size of incoming message in bytes. Only set while reading a
+        # fragmented message i.e. a data frames with the FIN bit not set.
+        self.cur_size: Optional[int] = None
+
+        # True while sending a fragmented message i.e. a data frames with the
+        # FIN bit not set.
+        self.expect_continuation_frame = False
+
+        # WebSocket protocol parameters.
+        self.origin: Optional[Origin] = None
+        self.extensions: List[Extension] = []
+        self.subprotocol: Optional[Subprotocol] = None
+
+        # Close code and reason, set when a close frame is sent or received.
+        self.close_rcvd: Optional[Close] = None
+        self.close_sent: Optional[Close] = None
+        self.close_rcvd_then_sent: Optional[bool] = None
+
+        # Track if an exception happened during the handshake.
+        self.handshake_exc: Optional[Exception] = None
+        """
+        Exception to raise if the opening handshake failed.
+
+        :obj:`None` if the opening handshake succeeded.
+
+        """
+
+        # Track if send_eof() was called.
+        self.eof_sent = False
+
+        # Parser state.
+        self.reader = StreamReader()
+        self.events: List[Event] = []
+        self.writes: List[bytes] = []
+        self.parser = self.parse()
+        next(self.parser)  # start coroutine
+        self.parser_exc: Optional[Exception] = None
+
+    @property
+    def state(self) -> State:
+        """
+        WebSocket connection state.
+
+        Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`.
+
+        """
+        return self._state
+
+    @state.setter
+    def state(self, state: State) -> None:
+        if self.debug:
+            self.logger.debug("= connection is %s", state.name)
+        self._state = state
+
+    @property
+    def close_code(self) -> Optional[int]:
+        """
+        `WebSocket close code`_.
+
+        .. _WebSocket close code:
+            https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
+
+        :obj:`None` if the connection isn't closed yet.
+
+        """
+        if self.state is not CLOSED:
+            return None
+        elif self.close_rcvd is None:
+            return 1006
+        else:
+            return self.close_rcvd.code
+
+    @property
+    def close_reason(self) -> Optional[str]:
+        """
+        `WebSocket close reason`_.
+
+        .. _WebSocket close reason:
+            https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6
+
+        :obj:`None` if the connection isn't closed yet.
+
+        """
+        if self.state is not CLOSED:
+            return None
+        elif self.close_rcvd is None:
+            return ""
+        else:
+            return self.close_rcvd.reason
+
+    @property
+    def close_exc(self) -> ConnectionClosed:
+        """
+        Exception to raise when trying to interact with a closed connection.
+
+        Don't raise this exception while the connection :attr:`state`
+        is :attr:`~websockets.protocol.State.CLOSING`; wait until
+        it's :attr:`~websockets.protocol.State.CLOSED`.
+
+        Indeed, the exception includes the close code and reason, which are
+        known only once the connection is closed.
+
+        Raises:
+            AssertionError: if the connection isn't closed yet.
+
+        """
+        assert self.state is CLOSED, "connection isn't closed yet"
+        exc_type: Type[ConnectionClosed]
+        if (
+            self.close_rcvd is not None
+            and self.close_sent is not None
+            and self.close_rcvd.code in OK_CLOSE_CODES
+            and self.close_sent.code in OK_CLOSE_CODES
+        ):
+            exc_type = ConnectionClosedOK
+        else:
+            exc_type = ConnectionClosedError
+        exc: ConnectionClosed = exc_type(
+            self.close_rcvd,
+            self.close_sent,
+            self.close_rcvd_then_sent,
+        )
+        # Chain to the exception raised in the parser, if any.
+        exc.__cause__ = self.parser_exc
+        return exc
+
+    # Public methods for receiving data.
+
+    def receive_data(self, data: bytes) -> None:
+        """
+        Receive data from the network.
+
+        After calling this method:
+
+        - You must call :meth:`data_to_send` and send this data to the network.
+        - You should call :meth:`events_received` and process resulting events.
+
+        Raises:
+            EOFError: if :meth:`receive_eof` was called earlier.
+
+        """
+        self.reader.feed_data(data)
+        next(self.parser)
+
+    def receive_eof(self) -> None:
+        """
+        Receive the end of the data stream from the network.
+
+        After calling this method:
+
+        - You must call :meth:`data_to_send` and send this data to the network.
+        - You aren't expected to call :meth:`events_received`; it won't return
+          any new events.
+
+        Raises:
+            EOFError: if :meth:`receive_eof` was called earlier.
+
+        """
+        self.reader.feed_eof()
+        next(self.parser)
+
+    # Public methods for sending events.
+
+    def send_continuation(self, data: bytes, fin: bool) -> None:
+        """
+        Send a `Continuation frame`_.
+
+        .. _Continuation frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
+
+        Parameters:
+            data: payload containing the same kind of data
+                as the initial frame.
+            fin: FIN bit; set it to :obj:`True` if this is the last frame
+                of a fragmented message and to :obj:`False` otherwise.
+
+        Raises:
+            ProtocolError: if a fragmented message isn't in progress.
+
+        """
+        if not self.expect_continuation_frame:
+            raise ProtocolError("unexpected continuation frame")
+        self.expect_continuation_frame = not fin
+        self.send_frame(Frame(OP_CONT, data, fin))
+
+    def send_text(self, data: bytes, fin: bool = True) -> None:
+        """
+        Send a `Text frame`_.
+
+        .. _Text frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
+
+        Parameters:
+            data: payload containing text encoded with UTF-8.
+            fin: FIN bit; set it to :obj:`False` if this is the first frame of
+                a fragmented message.
+
+        Raises:
+            ProtocolError: if a fragmented message is in progress.
+
+        """
+        if self.expect_continuation_frame:
+            raise ProtocolError("expected a continuation frame")
+        self.expect_continuation_frame = not fin
+        self.send_frame(Frame(OP_TEXT, data, fin))
+
+    def send_binary(self, data: bytes, fin: bool = True) -> None:
+        """
+        Send a `Binary frame`_.
+
+        .. _Binary frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
+
+        Parameters:
+            data: payload containing arbitrary binary data.
+            fin: FIN bit; set it to :obj:`False` if this is the first frame of
+                a fragmented message.
+
+        Raises:
+            ProtocolError: if a fragmented message is in progress.
+
+        """
+        if self.expect_continuation_frame:
+            raise ProtocolError("expected a continuation frame")
+        self.expect_continuation_frame = not fin
+        self.send_frame(Frame(OP_BINARY, data, fin))
+
+    def send_close(self, code: Optional[int] = None, reason: str = "") -> None:
+        """
+        Send a `Close frame`_.
+
+        .. _Close frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1
+
+        Parameters:
+            code: close code.
+            reason: close reason.
+
+        Raises:
+            ProtocolError: if a fragmented message is being sent, if the code
+                isn't valid, or if a reason is provided without a code
+
+        """
+        if self.expect_continuation_frame:
+            raise ProtocolError("expected a continuation frame")
+        if code is None:
+            if reason != "":
+                raise ProtocolError("cannot send a reason without a code")
+            close = Close(1005, "")
+            data = b""
+        else:
+            close = Close(code, reason)
+            data = close.serialize()
+        # send_frame() guarantees that self.state is OPEN at this point.
+        # 7.1.3. The WebSocket Closing Handshake is Started
+        self.send_frame(Frame(OP_CLOSE, data))
+        self.close_sent = close
+        self.state = CLOSING
+
+    def send_ping(self, data: bytes) -> None:
+        """
+        Send a `Ping frame`_.
+
+        .. _Ping frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
+
+        Parameters:
+            data: payload containing arbitrary binary data.
+
+        """
+        self.send_frame(Frame(OP_PING, data))
+
+    def send_pong(self, data: bytes) -> None:
+        """
+        Send a `Pong frame`_.
+
+        .. _Pong frame:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.3
+
+        Parameters:
+            data: payload containing arbitrary binary data.
+
+        """
+        self.send_frame(Frame(OP_PONG, data))
+
+    def fail(self, code: int, reason: str = "") -> None:
+        """
+        `Fail the WebSocket connection`_.
+
+        .. _Fail the WebSocket connection:
+            https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7
+
+        Parameters:
+            code: close code
+            reason: close reason
+
+        Raises:
+            ProtocolError: if the code isn't valid.
+        """
+        # 7.1.7. Fail the WebSocket Connection
+
+        # Send a close frame when the state is OPEN (a close frame was already
+        # sent if it's CLOSING), except when failing the connection because
+        # of an error reading from or writing to the network.
+        if self.state is OPEN:
+            if code != 1006:
+                close = Close(code, reason)
+                data = close.serialize()
+                self.send_frame(Frame(OP_CLOSE, data))
+                self.close_sent = close
+                self.state = CLOSING
+
+        # When failing the connection, a server closes the TCP connection
+        # without waiting for the client to complete the handshake, while a
+        # client waits for the server to close the TCP connection, possibly
+        # after sending a close frame that the client will ignore.
+        if self.side is SERVER and not self.eof_sent:
+            self.send_eof()
+
+        # 7.1.7. Fail the WebSocket Connection "An endpoint MUST NOT continue
+        # to attempt to process data(including a responding Close frame) from
+        # the remote endpoint after being instructed to _Fail the WebSocket
+        # Connection_."
+        self.parser = self.discard()
+        next(self.parser)  # start coroutine
+
+    # Public method for getting incoming events after receiving data.
+
+    def events_received(self) -> List[Event]:
+        """
+        Fetch events generated from data received from the network.
+
+        Call this method immediately after any of the ``receive_*()`` methods.
+
+        Process resulting events, likely by passing them to the application.
+
+        Returns:
+            List[Event]: Events read from the connection.
+        """
+        events, self.events = self.events, []
+        return events
+
+    # Public method for getting outgoing data after receiving data or sending events.
+
+    def data_to_send(self) -> List[bytes]:
+        """
+        Obtain data to send to the network.
+
+        Call this method immediately after any of the ``receive_*()``,
+        ``send_*()``, or :meth:`fail` methods.
+
+        Write resulting data to the connection.
+
+        The empty bytestring :data:`~websockets.protocol.SEND_EOF` signals
+        the end of the data stream. When you receive it, half-close the TCP
+        connection.
+
+        Returns:
+            List[bytes]: Data to write to the connection.
+
+        """
+        writes, self.writes = self.writes, []
+        return writes
+
+    def close_expected(self) -> bool:
+        """
+        Tell if the TCP connection is expected to close soon.
+
+        Call this method immediately after any of the ``receive_*()`` or
+        :meth:`fail` methods.
+
+        If it returns :obj:`True`, schedule closing the TCP connection after a
+        short timeout if the other side hasn't already closed it.
+
+        Returns:
+            bool: Whether the TCP connection is expected to close soon.
+
+        """
+        # We expect a TCP close if and only if we sent a close frame:
+        # * Normal closure: once we send a close frame, we expect a TCP close:
+        #   server waits for client to complete the TCP closing handshake;
+        #   client waits for server to initiate the TCP closing handshake.
+        # * Abnormal closure: we always send a close frame and the same logic
+        #   applies, except on EOFError where we don't send a close frame
+        #   because we already received the TCP close, so we don't expect it.
+        # We already got a TCP Close if and only if the state is CLOSED.
+        return self.state is CLOSING or self.handshake_exc is not None
+
+    # Private methods for receiving data.
+
+    def parse(self) -> Generator[None, None, None]:
+        """
+        Parse incoming data into frames.
+
+        :meth:`receive_data` and :meth:`receive_eof` run this generator
+        coroutine until it needs more data or reaches EOF.
+
+        """
+        try:
+            while True:
+                if (yield from self.reader.at_eof()):
+                    if self.debug:
+                        self.logger.debug("< EOF")
+                    # If the WebSocket connection is closed cleanly, with a
+                    # closing handhshake, recv_frame() substitutes parse()
+                    # with discard(). This branch is reached only when the
+                    # connection isn't closed cleanly.
+                    raise EOFError("unexpected end of stream")
+
+                if self.max_size is None:
+                    max_size = None
+                elif self.cur_size is None:
+                    max_size = self.max_size
+                else:
+                    max_size = self.max_size - self.cur_size
+
+                # During a normal closure, execution ends here on the next
+                # iteration of the loop after receiving a close frame. At
+                # this point, recv_frame() replaced parse() by discard().
+                frame = yield from Frame.parse(
+                    self.reader.read_exact,
+                    mask=self.side is SERVER,
+                    max_size=max_size,
+                    extensions=self.extensions,
+                )
+
+                if self.debug:
+                    self.logger.debug("< %s", frame)
+
+                self.recv_frame(frame)
+
+        except ProtocolError as exc:
+            self.fail(1002, str(exc))
+            self.parser_exc = exc
+
+        except EOFError as exc:
+            self.fail(1006, str(exc))
+            self.parser_exc = exc
+
+        except UnicodeDecodeError as exc:
+            self.fail(1007, f"{exc.reason} at position {exc.start}")
+            self.parser_exc = exc
+
+        except PayloadTooBig as exc:
+            self.fail(1009, str(exc))
+            self.parser_exc = exc
+
+        except Exception as exc:
+            self.logger.error("parser failed", exc_info=True)
+            # Don't include exception details, which may be security-sensitive.
+            self.fail(1011)
+            self.parser_exc = exc
+
+        # During an abnormal closure, execution ends here after catching an
+        # exception. At this point, fail() replaced parse() by discard().
+        yield
+        raise AssertionError("parse() shouldn't step after error")
+
+    def discard(self) -> Generator[None, None, None]:
+        """
+        Discard incoming data.
+
+        This coroutine replaces :meth:`parse`:
+
+        - after receiving a close frame, during a normal closure (1.4);
+        - after sending a close frame, during an abnormal closure (7.1.7).
+
+        """
+        # The server close the TCP connection in the same circumstances where
+        # discard() replaces parse(). The client closes the connection later,
+        # after the server closes the connection or a timeout elapses.
+        # (The latter case cannot be handled in this Sans-I/O layer.)
+        assert (self.side is SERVER) == (self.eof_sent)
+        while not (yield from self.reader.at_eof()):
+            self.reader.discard()
+        if self.debug:
+            self.logger.debug("< EOF")
+        # A server closes the TCP connection immediately, while a client
+        # waits for the server to close the TCP connection.
+        if self.side is CLIENT:
+            self.send_eof()
+        self.state = CLOSED
+        # If discard() completes normally, execution ends here.
+        yield
+        # Once the reader reaches EOF, its feed_data/eof() methods raise an
+        # error, so our receive_data/eof() methods don't step the generator.
+        raise AssertionError("discard() shouldn't step after EOF")
+
+    def recv_frame(self, frame: Frame) -> None:
+        """
+        Process an incoming frame.
+
+        """
+        if frame.opcode is OP_TEXT or frame.opcode is OP_BINARY:
+            if self.cur_size is not None:
+                raise ProtocolError("expected a continuation frame")
+            if frame.fin:
+                self.cur_size = None
+            else:
+                self.cur_size = len(frame.data)
+
+        elif frame.opcode is OP_CONT:
+            if self.cur_size is None:
+                raise ProtocolError("unexpected continuation frame")
+            if frame.fin:
+                self.cur_size = None
+            else:
+                self.cur_size += len(frame.data)
+
+        elif frame.opcode is OP_PING:
+            # 5.5.2. Ping: "Upon receipt of a Ping frame, an endpoint MUST
+            # send a Pong frame in response"
+            pong_frame = Frame(OP_PONG, frame.data)
+            self.send_frame(pong_frame)
+
+        elif frame.opcode is OP_PONG:
+            # 5.5.3 Pong: "A response to an unsolicited Pong frame is not
+            # expected."
+            pass
+
+        elif frame.opcode is OP_CLOSE:
+            # 7.1.5.  The WebSocket Connection Close Code
+            # 7.1.6.  The WebSocket Connection Close Reason
+            self.close_rcvd = Close.parse(frame.data)
+            if self.state is CLOSING:
+                assert self.close_sent is not None
+                self.close_rcvd_then_sent = False
+
+            if self.cur_size is not None:
+                raise ProtocolError("incomplete fragmented message")
+
+            # 5.5.1 Close: "If an endpoint receives a Close frame and did
+            # not previously send a Close frame, the endpoint MUST send a
+            # Close frame in response. (When sending a Close frame in
+            # response, the endpoint typically echos the status code it
+            # received.)"
+
+            if self.state is OPEN:
+                # Echo the original data instead of re-serializing it with
+                # Close.serialize() because that fails when the close frame
+                # is empty and Close.parse() synthetizes a 1005 close code.
+                # The rest is identical to send_close().
+                self.send_frame(Frame(OP_CLOSE, frame.data))
+                self.close_sent = self.close_rcvd
+                self.close_rcvd_then_sent = True
+                self.state = CLOSING
+
+            # 7.1.2. Start the WebSocket Closing Handshake: "Once an
+            # endpoint has both sent and received a Close control frame,
+            # that endpoint SHOULD _Close the WebSocket Connection_"
+
+            # A server closes the TCP connection immediately, while a client
+            # waits for the server to close the TCP connection.
+            if self.side is SERVER:
+                self.send_eof()
+
+            # 1.4. Closing Handshake: "after receiving a control frame
+            # indicating the connection should be closed, a peer discards
+            # any further data received."
+            self.parser = self.discard()
+            next(self.parser)  # start coroutine
+
+        else:
+            # This can't happen because Frame.parse() validates opcodes.
+            raise AssertionError(f"unexpected opcode: {frame.opcode:02x}")
+
+        self.events.append(frame)
+
+    # Private methods for sending events.
+
+    def send_frame(self, frame: Frame) -> None:
+        if self.state is not OPEN:
+            raise InvalidState(
+                f"cannot write to a WebSocket in the {self.state.name} state"
+            )
+
+        if self.debug:
+            self.logger.debug("> %s", frame)
+        self.writes.append(
+            frame.serialize(mask=self.side is CLIENT, extensions=self.extensions)
+        )
+
+    def send_eof(self) -> None:
+        assert not self.eof_sent
+        self.eof_sent = True
+        if self.debug:
+            self.logger.debug("> EOF")
+        self.writes.append(SEND_EOF)
diff --git a/src/websockets/server.py b/src/websockets/server.py
index edd1764..548048c 100644
--- a/src/websockets/server.py
+++ b/src/websockets/server.py
@@ -4,9 +4,9 @@ import base64
 import binascii
 import email.utils
 import http
-from typing import Generator, List, Optional, Sequence, Tuple, cast
+import warnings
+from typing import Any, Generator, List, Optional, Sequence, Tuple, cast
 
-from .connection import CONNECTING, OPEN, SERVER, Connection, State
 from .datastructures import Headers, MultipleValuesError
 from .exceptions import (
     InvalidHandshake,
@@ -26,6 +26,7 @@ from .headers import (
     parse_upgrade,
 )
 from .http11 import Request, Response
+from .protocol import CONNECTING, OPEN, SERVER, Protocol, State
 from .typing import (
     ConnectionOption,
     ExtensionHeader,
@@ -41,10 +42,10 @@ from .utils import accept_key
 from .legacy.server import *  # isort:skip  # noqa
 
 
-__all__ = ["ServerConnection"]
+__all__ = ["ServerProtocol"]
 
 
-class ServerConnection(Connection):
+class ServerProtocol(Protocol):
     """
     Sans-I/O implementation of a WebSocket server connection.
 
@@ -515,3 +516,12 @@ class ServerConnection(Connection):
             self.events.append(request)
 
         yield from super().parse()
+
+
+class ServerConnection(ServerProtocol):
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        warnings.warn(
+            "ServerConnection was renamed to ServerProtocol",
+            DeprecationWarning,
+        )
+        super().__init__(*args, **kwargs)


Test patch:
diff --git a/tests/legacy/test_client_server.py b/tests/legacy/test_client_server.py
index f8a7902..4a45105 100644
--- a/tests/legacy/test_client_server.py
+++ b/tests/legacy/test_client_server.py
@@ -16,7 +16,6 @@ import urllib.error
 import urllib.request
 import warnings
 
-from websockets.connection import State
 from websockets.datastructures import Headers
 from websockets.exceptions import (
     ConnectionClosed,
@@ -35,6 +34,7 @@ from websockets.legacy.client import *
 from websockets.legacy.handshake import build_response
 from websockets.legacy.http import read_response
 from websockets.legacy.server import *
+from websockets.protocol import State
 from websockets.uri import parse_uri
 
 from ..extensions.utils import (
diff --git a/tests/legacy/test_protocol.py b/tests/legacy/test_protocol.py
index 1f830eb..e85402a 100644
--- a/tests/legacy/test_protocol.py
+++ b/tests/legacy/test_protocol.py
@@ -4,7 +4,6 @@ import unittest
 import unittest.mock
 import warnings
 
-from websockets.connection import State
 from websockets.exceptions import ConnectionClosed, InvalidState
 from websockets.frames import (
     OP_BINARY,
@@ -18,6 +17,7 @@ from websockets.frames import (
 from websockets.legacy.compatibility import loop_if_py_lt_38
 from websockets.legacy.framing import Frame
 from websockets.legacy.protocol import WebSocketCommonProtocol, broadcast
+from websockets.protocol import State
 
 from .utils import MS, AsyncioTestCase
 
diff --git a/tests/test_client.py b/tests/test_client.py
index 9ed36c1..718219b 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -3,11 +3,11 @@ import unittest
 import unittest.mock
 
 from websockets.client import *
-from websockets.connection import CONNECTING, OPEN
 from websockets.datastructures import Headers
 from websockets.exceptions import InvalidHandshake, InvalidHeader
 from websockets.frames import OP_TEXT, Frame
 from websockets.http11 import Request, Response
+from websockets.protocol import CONNECTING, OPEN
 from websockets.uri import parse_uri
 from websockets.utils import accept_key
 
@@ -18,13 +18,13 @@ from .extensions.utils import (
     Rsv2Extension,
 )
 from .test_utils import ACCEPT, KEY
-from .utils import DATE
+from .utils import DATE, DeprecationTestCase
 
 
 class ConnectTests(unittest.TestCase):
     def test_send_connect(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("wss://example.com/test"))
+            client = ClientProtocol(parse_uri("wss://example.com/test"))
         request = client.connect()
         self.assertIsInstance(request, Request)
         client.send_request(request)
@@ -44,7 +44,7 @@ class ConnectTests(unittest.TestCase):
 
     def test_connect_request(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("wss://example.com/test"))
+            client = ClientProtocol(parse_uri("wss://example.com/test"))
         request = client.connect()
         self.assertEqual(request.path, "/test")
         self.assertEqual(
@@ -61,7 +61,7 @@ class ConnectTests(unittest.TestCase):
         )
 
     def test_path(self):
-        client = ClientConnection(parse_uri("wss://example.com/endpoint?test=1"))
+        client = ClientProtocol(parse_uri("wss://example.com/endpoint?test=1"))
         request = client.connect()
 
         self.assertEqual(request.path, "/endpoint?test=1")
@@ -76,19 +76,19 @@ class ConnectTests(unittest.TestCase):
             ("wss://example.com:8443/", "example.com:8443"),
         ]:
             with self.subTest(uri=uri):
-                client = ClientConnection(parse_uri(uri))
+                client = ClientProtocol(parse_uri(uri))
                 request = client.connect()
 
                 self.assertEqual(request.headers["Host"], host)
 
     def test_user_info(self):
-        client = ClientConnection(parse_uri("wss://hello:iloveyou@example.com/"))
+        client = ClientProtocol(parse_uri("wss://hello:iloveyou@example.com/"))
         request = client.connect()
 
         self.assertEqual(request.headers["Authorization"], "Basic aGVsbG86aWxvdmV5b3U=")
 
     def test_origin(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             origin="https://example.com",
         )
@@ -97,7 +97,7 @@ class ConnectTests(unittest.TestCase):
         self.assertEqual(request.headers["Origin"], "https://example.com")
 
     def test_extensions(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory()],
         )
@@ -106,7 +106,7 @@ class ConnectTests(unittest.TestCase):
         self.assertEqual(request.headers["Sec-WebSocket-Extensions"], "x-op; op")
 
     def test_subprotocols(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             subprotocols=["chat"],
         )
@@ -118,7 +118,7 @@ class ConnectTests(unittest.TestCase):
 class AcceptRejectTests(unittest.TestCase):
     def test_receive_accept(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("ws://example.com/test"))
+            client = ClientProtocol(parse_uri("ws://example.com/test"))
         client.connect()
         client.receive_data(
             (
@@ -138,7 +138,7 @@ class AcceptRejectTests(unittest.TestCase):
 
     def test_receive_reject(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("ws://example.com/test"))
+            client = ClientProtocol(parse_uri("ws://example.com/test"))
         client.connect()
         client.receive_data(
             (
@@ -159,7 +159,7 @@ class AcceptRejectTests(unittest.TestCase):
 
     def test_accept_response(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("ws://example.com/test"))
+            client = ClientProtocol(parse_uri("ws://example.com/test"))
         client.connect()
         client.receive_data(
             (
@@ -189,7 +189,7 @@ class AcceptRejectTests(unittest.TestCase):
 
     def test_reject_response(self):
         with unittest.mock.patch("websockets.client.generate_key", return_value=KEY):
-            client = ClientConnection(parse_uri("ws://example.com/test"))
+            client = ClientProtocol(parse_uri("ws://example.com/test"))
         client.connect()
         client.receive_data(
             (
@@ -235,7 +235,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_basic(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         client.receive_data(response.serialize())
         [response] = client.events_received()
@@ -243,7 +243,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.state, OPEN)
 
     def test_missing_connection(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Connection"]
         client.receive_data(response.serialize())
@@ -255,7 +255,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Connection header")
 
     def test_invalid_connection(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Connection"]
         response.headers["Connection"] = "close"
@@ -268,7 +268,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "invalid Connection header: close")
 
     def test_missing_upgrade(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Upgrade"]
         client.receive_data(response.serialize())
@@ -280,7 +280,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Upgrade header")
 
     def test_invalid_upgrade(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Upgrade"]
         response.headers["Upgrade"] = "h2c"
@@ -293,7 +293,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "invalid Upgrade header: h2c")
 
     def test_missing_accept(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Sec-WebSocket-Accept"]
         client.receive_data(response.serialize())
@@ -305,7 +305,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Accept header")
 
     def test_multiple_accept(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         response.headers["Sec-WebSocket-Accept"] = ACCEPT
         client.receive_data(response.serialize())
@@ -321,7 +321,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_invalid_accept(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         del response.headers["Sec-WebSocket-Accept"]
         response.headers["Sec-WebSocket-Accept"] = ACCEPT
@@ -336,7 +336,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_no_extensions(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         client.receive_data(response.serialize())
         [response] = client.events_received()
@@ -345,7 +345,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [])
 
     def test_no_extension(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory()],
         )
@@ -358,7 +358,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [OpExtension()])
 
     def test_extension(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientRsv2ExtensionFactory()],
         )
@@ -371,7 +371,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [Rsv2Extension()])
 
     def test_unexpected_extension(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         response.headers["Sec-WebSocket-Extensions"] = "x-op; op"
         client.receive_data(response.serialize())
@@ -383,7 +383,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "no extensions supported")
 
     def test_unsupported_extension(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientRsv2ExtensionFactory()],
         )
@@ -401,7 +401,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_supported_extension_parameters(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory("this")],
         )
@@ -414,7 +414,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [OpExtension("this")])
 
     def test_unsupported_extension_parameters(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory("this")],
         )
@@ -432,7 +432,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_multiple_supported_extension_parameters(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[
                 ClientOpExtensionFactory("this"),
@@ -448,7 +448,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [OpExtension("that")])
 
     def test_multiple_extensions(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory(), ClientRsv2ExtensionFactory()],
         )
@@ -462,7 +462,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [OpExtension(), Rsv2Extension()])
 
     def test_multiple_extensions_order(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             extensions=[ClientOpExtensionFactory(), ClientRsv2ExtensionFactory()],
         )
@@ -476,7 +476,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.extensions, [Rsv2Extension(), OpExtension()])
 
     def test_no_subprotocols(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         client.receive_data(response.serialize())
         [response] = client.events_received()
@@ -485,9 +485,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(client.subprotocol)
 
     def test_no_subprotocol(self):
-        client = ClientConnection(
-            parse_uri("wss://example.com/"), subprotocols=["chat"]
-        )
+        client = ClientProtocol(parse_uri("wss://example.com/"), subprotocols=["chat"])
         response = self.make_accept_response(client)
         client.receive_data(response.serialize())
         [response] = client.events_received()
@@ -496,9 +494,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(client.subprotocol)
 
     def test_subprotocol(self):
-        client = ClientConnection(
-            parse_uri("wss://example.com/"), subprotocols=["chat"]
-        )
+        client = ClientProtocol(parse_uri("wss://example.com/"), subprotocols=["chat"])
         response = self.make_accept_response(client)
         response.headers["Sec-WebSocket-Protocol"] = "chat"
         client.receive_data(response.serialize())
@@ -508,7 +504,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.subprotocol, "chat")
 
     def test_unexpected_subprotocol(self):
-        client = ClientConnection(parse_uri("wss://example.com/"))
+        client = ClientProtocol(parse_uri("wss://example.com/"))
         response = self.make_accept_response(client)
         response.headers["Sec-WebSocket-Protocol"] = "chat"
         client.receive_data(response.serialize())
@@ -520,7 +516,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "no subprotocols supported")
 
     def test_multiple_subprotocols(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             subprotocols=["superchat", "chat"],
         )
@@ -538,7 +534,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_supported_subprotocol(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             subprotocols=["superchat", "chat"],
         )
@@ -551,7 +547,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(client.subprotocol, "chat")
 
     def test_unsupported_subprotocol(self):
-        client = ClientConnection(
+        client = ClientProtocol(
             parse_uri("wss://example.com/"),
             subprotocols=["superchat", "chat"],
         )
@@ -568,7 +564,7 @@ class AcceptRejectTests(unittest.TestCase):
 
 class MiscTests(unittest.TestCase):
     def test_bypass_handshake(self):
-        client = ClientConnection(parse_uri("ws://example.com/test"), state=OPEN)
+        client = ClientProtocol(parse_uri("ws://example.com/test"), state=OPEN)
         client.receive_data(b"\x81\x06Hello!")
         [frame] = client.events_received()
         self.assertEqual(frame, Frame(OP_TEXT, b"Hello!"))
@@ -576,5 +572,17 @@ class MiscTests(unittest.TestCase):
     def test_custom_logger(self):
         logger = logging.getLogger("test")
         with self.assertLogs("test", logging.DEBUG) as logs:
-            ClientConnection(parse_uri("wss://example.com/test"), logger=logger)
+            ClientProtocol(parse_uri("wss://example.com/test"), logger=logger)
         self.assertEqual(len(logs.records), 1)
+
+
+class BackwardsCompatibilityTests(DeprecationTestCase):
+    def test_client_connection_class(self):
+        with self.assertDeprecationWarning(
+            "ClientConnection was renamed to ClientProtocol"
+        ):
+            from websockets.client import ClientConnection
+
+            client = ClientConnection("ws://localhost/")
+
+        self.assertIsInstance(client, ClientProtocol)
diff --git a/tests/test_connection.py b/tests/test_connection.py
index 3858d25..6592d67 100644
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -1,1737 +1,14 @@
-import logging
-import unittest.mock
+from websockets.protocol import Protocol
 
-from websockets.connection import *
-from websockets.connection import CLIENT, CLOSED, CLOSING, SERVER
-from websockets.exceptions import (
-    ConnectionClosedError,
-    ConnectionClosedOK,
-    InvalidState,
-    PayloadTooBig,
-    ProtocolError,
-)
-from websockets.frames import (
-    OP_BINARY,
-    OP_CLOSE,
-    OP_CONT,
-    OP_PING,
-    OP_PONG,
-    OP_TEXT,
-    Close,
-    Frame,
-)
+from .utils import DeprecationTestCase
 
-from .extensions.utils import Rsv2Extension
-from .test_frames import FramesTestCase
 
+class BackwardsCompatibilityTests(DeprecationTestCase):
+    def test_connection_class(self):
+        with self.assertDeprecationWarning(
+            "websockets.connection was renamed to websockets.protocol "
+            "and Connection was renamed to Protocol"
+        ):
+            from websockets.connection import Connection
 
-class ConnectionTestCase(FramesTestCase):
-    def assertFrameSent(self, connection, frame, eof=False):
-        """
-        Outgoing data for ``connection`` contains the given frame.
-
-        ``frame`` may be ``None`` if no frame is expected.
-
-        When ``eof`` is ``True``, the end of the stream is also expected.
-
-        """
-        frames_sent = [
-            None
-            if write is SEND_EOF
-            else self.parse(
-                write,
-                mask=connection.side is CLIENT,
-                extensions=connection.extensions,
-            )
-            for write in connection.data_to_send()
-        ]
-        frames_expected = [] if frame is None else [frame]
-        if eof:
-            frames_expected += [None]
-        self.assertEqual(frames_sent, frames_expected)
-
-    def assertFrameReceived(self, connection, frame):
-        """
-        Incoming data for ``connection`` contains the given frame.
-
-        ``frame`` may be ``None`` if no frame is expected.
-
-        """
-        frames_received = connection.events_received()
-        frames_expected = [] if frame is None else [frame]
-        self.assertEqual(frames_received, frames_expected)
-
-    def assertConnectionClosing(self, connection, code=None, reason=""):
-        """
-        Incoming data caused the "Start the WebSocket Closing Handshake" process.
-
-        """
-        close_frame = Frame(
-            OP_CLOSE,
-            b"" if code is None else Close(code, reason).serialize(),
-        )
-        # A close frame was received.
-        self.assertFrameReceived(connection, close_frame)
-        # A close frame and possibly the end of stream were sent.
-        self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER)
-
-    def assertConnectionFailing(self, connection, code=None, reason=""):
-        """
-        Incoming data caused the "Fail the WebSocket Connection" process.
-
-        """
-        close_frame = Frame(
-            OP_CLOSE,
-            b"" if code is None else Close(code, reason).serialize(),
-        )
-        # No frame was received.
-        self.assertFrameReceived(connection, None)
-        # A close frame and possibly the end of stream were sent.
-        self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER)
-
-
-class MaskingTests(ConnectionTestCase):
-    """
-    Test frame masking.
-
-    5.1.  Overview
-
-    """
-
-    unmasked_text_frame_date = b"\x81\x04Spam"
-    masked_text_frame_data = b"\x81\x84\x00\xff\x00\xff\x53\x8f\x61\x92"
-
-    def test_client_sends_masked_frame(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\xff\x00\xff"):
-            client.send_text(b"Spam", True)
-        self.assertEqual(client.data_to_send(), [self.masked_text_frame_data])
-
-    def test_server_sends_unmasked_frame(self):
-        server = Connection(SERVER)
-        server.send_text(b"Spam", True)
-        self.assertEqual(server.data_to_send(), [self.unmasked_text_frame_date])
-
-    def test_client_receives_unmasked_frame(self):
-        client = Connection(CLIENT)
-        client.receive_data(self.unmasked_text_frame_date)
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, b"Spam"),
-        )
-
-    def test_server_receives_masked_frame(self):
-        server = Connection(SERVER)
-        server.receive_data(self.masked_text_frame_data)
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, b"Spam"),
-        )
-
-    def test_client_receives_masked_frame(self):
-        client = Connection(CLIENT)
-        client.receive_data(self.masked_text_frame_data)
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "incorrect masking")
-        self.assertConnectionFailing(client, 1002, "incorrect masking")
-
-    def test_server_receives_unmasked_frame(self):
-        server = Connection(SERVER)
-        server.receive_data(self.unmasked_text_frame_date)
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "incorrect masking")
-        self.assertConnectionFailing(server, 1002, "incorrect masking")
-
-
-class ContinuationTests(ConnectionTestCase):
-    """
-    Test continuation frames without text or binary frames.
-
-    """
-
-    def test_client_sends_unexpected_continuation(self):
-        client = Connection(CLIENT)
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_continuation(b"", fin=False)
-        self.assertEqual(str(raised.exception), "unexpected continuation frame")
-
-    def test_server_sends_unexpected_continuation(self):
-        server = Connection(SERVER)
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_continuation(b"", fin=False)
-        self.assertEqual(str(raised.exception), "unexpected continuation frame")
-
-    def test_client_receives_unexpected_continuation(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x00\x00")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "unexpected continuation frame")
-        self.assertConnectionFailing(client, 1002, "unexpected continuation frame")
-
-    def test_server_receives_unexpected_continuation(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x00\x80\x00\x00\x00\x00")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "unexpected continuation frame")
-        self.assertConnectionFailing(server, 1002, "unexpected continuation frame")
-
-    def test_client_sends_continuation_after_sending_close(self):
-        client = Connection(CLIENT)
-        # Since it isn't possible to send a close frame in a fragmented
-        # message (see test_client_send_close_in_fragmented_message), in fact,
-        # this is the same test as test_client_sends_unexpected_continuation.
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_continuation(b"", fin=False)
-        self.assertEqual(str(raised.exception), "unexpected continuation frame")
-
-    def test_server_sends_continuation_after_sending_close(self):
-        # Since it isn't possible to send a close frame in a fragmented
-        # message (see test_server_send_close_in_fragmented_message), in fact,
-        # this is the same test as test_server_sends_unexpected_continuation.
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_continuation(b"", fin=False)
-        self.assertEqual(str(raised.exception), "unexpected continuation frame")
-
-    def test_client_receives_continuation_after_receiving_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000)
-        client.receive_data(b"\x00\x00")
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_continuation_after_receiving_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001)
-        server.receive_data(b"\x00\x80\x00\xff\x00\xff")
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-
-class TextTests(ConnectionTestCase):
-    """
-    Test text frames and continuation frames.
-
-    """
-
-    def test_client_sends_text(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_text("😀".encode())
-        self.assertEqual(
-            client.data_to_send(), [b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80"]
-        )
-
-    def test_server_sends_text(self):
-        server = Connection(SERVER)
-        server.send_text("😀".encode())
-        self.assertEqual(server.data_to_send(), [b"\x81\x04\xf0\x9f\x98\x80"])
-
-    def test_client_receives_text(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, "😀".encode()),
-        )
-
-    def test_server_receives_text(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, "😀".encode()),
-        )
-
-    def test_client_receives_text_over_size_limit(self):
-        client = Connection(CLIENT, max_size=3)
-        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
-        self.assertIsInstance(client.parser_exc, PayloadTooBig)
-        self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)")
-        self.assertConnectionFailing(client, 1009, "over size limit (4 > 3 bytes)")
-
-    def test_server_receives_text_over_size_limit(self):
-        server = Connection(SERVER, max_size=3)
-        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
-        self.assertIsInstance(server.parser_exc, PayloadTooBig)
-        self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)")
-        self.assertConnectionFailing(server, 1009, "over size limit (4 > 3 bytes)")
-
-    def test_client_receives_text_without_size_limit(self):
-        client = Connection(CLIENT, max_size=None)
-        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, "😀".encode()),
-        )
-
-    def test_server_receives_text_without_size_limit(self):
-        server = Connection(SERVER, max_size=None)
-        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, "😀".encode()),
-        )
-
-    def test_client_sends_fragmented_text(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_text("😀".encode()[:2], fin=False)
-        self.assertEqual(client.data_to_send(), [b"\x01\x82\x00\x00\x00\x00\xf0\x9f"])
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_continuation("😀😀".encode()[2:6], fin=False)
-        self.assertEqual(
-            client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f"]
-        )
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_continuation("😀".encode()[2:], fin=True)
-        self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\x98\x80"])
-
-    def test_server_sends_fragmented_text(self):
-        server = Connection(SERVER)
-        server.send_text("😀".encode()[:2], fin=False)
-        self.assertEqual(server.data_to_send(), [b"\x01\x02\xf0\x9f"])
-        server.send_continuation("😀😀".encode()[2:6], fin=False)
-        self.assertEqual(server.data_to_send(), [b"\x00\x04\x98\x80\xf0\x9f"])
-        server.send_continuation("😀".encode()[2:], fin=True)
-        self.assertEqual(server.data_to_send(), [b"\x80\x02\x98\x80"])
-
-    def test_client_receives_fragmented_text(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x01\x02\xf0\x9f")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        client.receive_data(b"\x00\x04\x98\x80\xf0\x9f")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
-        )
-        client.receive_data(b"\x80\x02\x98\x80")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, "😀".encode()[2:]),
-        )
-
-    def test_server_receives_fragmented_text(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
-        )
-        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, "😀".encode()[2:]),
-        )
-
-    def test_client_receives_fragmented_text_over_size_limit(self):
-        client = Connection(CLIENT, max_size=3)
-        client.receive_data(b"\x01\x02\xf0\x9f")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        client.receive_data(b"\x80\x02\x98\x80")
-        self.assertIsInstance(client.parser_exc, PayloadTooBig)
-        self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)")
-        self.assertConnectionFailing(client, 1009, "over size limit (2 > 1 bytes)")
-
-    def test_server_receives_fragmented_text_over_size_limit(self):
-        server = Connection(SERVER, max_size=3)
-        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
-        self.assertIsInstance(server.parser_exc, PayloadTooBig)
-        self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)")
-        self.assertConnectionFailing(server, 1009, "over size limit (2 > 1 bytes)")
-
-    def test_client_receives_fragmented_text_without_size_limit(self):
-        client = Connection(CLIENT, max_size=None)
-        client.receive_data(b"\x01\x02\xf0\x9f")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        client.receive_data(b"\x00\x04\x98\x80\xf0\x9f")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
-        )
-        client.receive_data(b"\x80\x02\x98\x80")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, "😀".encode()[2:]),
-        )
-
-    def test_server_receives_fragmented_text_without_size_limit(self):
-        server = Connection(SERVER, max_size=None)
-        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
-        )
-        server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
-        )
-        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, "😀".encode()[2:]),
-        )
-
-    def test_client_sends_unexpected_text(self):
-        client = Connection(CLIENT)
-        client.send_text(b"", fin=False)
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_text(b"", fin=False)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-
-    def test_server_sends_unexpected_text(self):
-        server = Connection(SERVER)
-        server.send_text(b"", fin=False)
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_text(b"", fin=False)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-
-    def test_client_receives_unexpected_text(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x01\x00")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, b"", fin=False),
-        )
-        client.receive_data(b"\x01\x00")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "expected a continuation frame")
-        self.assertConnectionFailing(client, 1002, "expected a continuation frame")
-
-    def test_server_receives_unexpected_text(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x01\x80\x00\x00\x00\x00")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, b"", fin=False),
-        )
-        server.receive_data(b"\x01\x80\x00\x00\x00\x00")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "expected a continuation frame")
-        self.assertConnectionFailing(server, 1002, "expected a continuation frame")
-
-    def test_client_sends_text_after_sending_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        with self.assertRaises(InvalidState):
-            client.send_text(b"")
-
-    def test_server_sends_text_after_sending_close(self):
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        with self.assertRaises(InvalidState):
-            server.send_text(b"")
-
-    def test_client_receives_text_after_receiving_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000)
-        client.receive_data(b"\x81\x00")
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_text_after_receiving_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001)
-        server.receive_data(b"\x81\x80\x00\xff\x00\xff")
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-
-class BinaryTests(ConnectionTestCase):
-    """
-    Test binary frames and continuation frames.
-
-    """
-
-    def test_client_sends_binary(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_binary(b"\x01\x02\xfe\xff")
-        self.assertEqual(
-            client.data_to_send(), [b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff"]
-        )
-
-    def test_server_sends_binary(self):
-        server = Connection(SERVER)
-        server.send_binary(b"\x01\x02\xfe\xff")
-        self.assertEqual(server.data_to_send(), [b"\x82\x04\x01\x02\xfe\xff"])
-
-    def test_client_receives_binary(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x82\x04\x01\x02\xfe\xff")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_BINARY, b"\x01\x02\xfe\xff"),
-        )
-
-    def test_server_receives_binary(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_BINARY, b"\x01\x02\xfe\xff"),
-        )
-
-    def test_client_receives_binary_over_size_limit(self):
-        client = Connection(CLIENT, max_size=3)
-        client.receive_data(b"\x82\x04\x01\x02\xfe\xff")
-        self.assertIsInstance(client.parser_exc, PayloadTooBig)
-        self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)")
-        self.assertConnectionFailing(client, 1009, "over size limit (4 > 3 bytes)")
-
-    def test_server_receives_binary_over_size_limit(self):
-        server = Connection(SERVER, max_size=3)
-        server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff")
-        self.assertIsInstance(server.parser_exc, PayloadTooBig)
-        self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)")
-        self.assertConnectionFailing(server, 1009, "over size limit (4 > 3 bytes)")
-
-    def test_client_sends_fragmented_binary(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_binary(b"\x01\x02", fin=False)
-        self.assertEqual(client.data_to_send(), [b"\x02\x82\x00\x00\x00\x00\x01\x02"])
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_continuation(b"\xee\xff\x01\x02", fin=False)
-        self.assertEqual(
-            client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02"]
-        )
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_continuation(b"\xee\xff", fin=True)
-        self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\xee\xff"])
-
-    def test_server_sends_fragmented_binary(self):
-        server = Connection(SERVER)
-        server.send_binary(b"\x01\x02", fin=False)
-        self.assertEqual(server.data_to_send(), [b"\x02\x02\x01\x02"])
-        server.send_continuation(b"\xee\xff\x01\x02", fin=False)
-        self.assertEqual(server.data_to_send(), [b"\x00\x04\xee\xff\x01\x02"])
-        server.send_continuation(b"\xee\xff", fin=True)
-        self.assertEqual(server.data_to_send(), [b"\x80\x02\xee\xff"])
-
-    def test_client_receives_fragmented_binary(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x02\x02\x01\x02")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_BINARY, b"\x01\x02", fin=False),
-        )
-        client.receive_data(b"\x00\x04\xfe\xff\x01\x02")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, b"\xfe\xff\x01\x02", fin=False),
-        )
-        client.receive_data(b"\x80\x02\xfe\xff")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, b"\xfe\xff"),
-        )
-
-    def test_server_receives_fragmented_binary(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_BINARY, b"\x01\x02", fin=False),
-        )
-        server.receive_data(b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, b"\xee\xff\x01\x02", fin=False),
-        )
-        server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, b"\xfe\xff"),
-        )
-
-    def test_client_receives_fragmented_binary_over_size_limit(self):
-        client = Connection(CLIENT, max_size=3)
-        client.receive_data(b"\x02\x02\x01\x02")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_BINARY, b"\x01\x02", fin=False),
-        )
-        client.receive_data(b"\x80\x02\xfe\xff")
-        self.assertIsInstance(client.parser_exc, PayloadTooBig)
-        self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)")
-        self.assertConnectionFailing(client, 1009, "over size limit (2 > 1 bytes)")
-
-    def test_server_receives_fragmented_binary_over_size_limit(self):
-        server = Connection(SERVER, max_size=3)
-        server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_BINARY, b"\x01\x02", fin=False),
-        )
-        server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff")
-        self.assertIsInstance(server.parser_exc, PayloadTooBig)
-        self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)")
-        self.assertConnectionFailing(server, 1009, "over size limit (2 > 1 bytes)")
-
-    def test_client_sends_unexpected_binary(self):
-        client = Connection(CLIENT)
-        client.send_binary(b"", fin=False)
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_binary(b"", fin=False)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-
-    def test_server_sends_unexpected_binary(self):
-        server = Connection(SERVER)
-        server.send_binary(b"", fin=False)
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_binary(b"", fin=False)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-
-    def test_client_receives_unexpected_binary(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x02\x00")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_BINARY, b"", fin=False),
-        )
-        client.receive_data(b"\x02\x00")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "expected a continuation frame")
-        self.assertConnectionFailing(client, 1002, "expected a continuation frame")
-
-    def test_server_receives_unexpected_binary(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x02\x80\x00\x00\x00\x00")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_BINARY, b"", fin=False),
-        )
-        server.receive_data(b"\x02\x80\x00\x00\x00\x00")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "expected a continuation frame")
-        self.assertConnectionFailing(server, 1002, "expected a continuation frame")
-
-    def test_client_sends_binary_after_sending_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        with self.assertRaises(InvalidState):
-            client.send_binary(b"")
-
-    def test_server_sends_binary_after_sending_close(self):
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        with self.assertRaises(InvalidState):
-            server.send_binary(b"")
-
-    def test_client_receives_binary_after_receiving_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000)
-        client.receive_data(b"\x82\x00")
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_binary_after_receiving_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001)
-        server.receive_data(b"\x82\x80\x00\xff\x00\xff")
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-
-class CloseTests(ConnectionTestCase):
-    """
-    Test close frames.
-
-    See RFC 6544:
-
-    5.5.1. Close
-    7.1.6.  The WebSocket Connection Close Reason
-    7.1.7.  Fail the WebSocket Connection
-
-    """
-
-    def test_close_code(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x04\x03\xe8OK")
-        client.receive_eof()
-        self.assertEqual(client.close_code, 1000)
-
-    def test_close_reason(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe8OK")
-        server.receive_eof()
-        self.assertEqual(server.close_reason, "OK")
-
-    def test_close_code_not_provided(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x00\x00\x00\x00")
-        server.receive_eof()
-        self.assertEqual(server.close_code, 1005)
-
-    def test_close_reason_not_provided(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        client.receive_eof()
-        self.assertEqual(client.close_reason, "")
-
-    def test_close_code_not_available(self):
-        client = Connection(CLIENT)
-        client.receive_eof()
-        self.assertEqual(client.close_code, 1006)
-
-    def test_close_reason_not_available(self):
-        server = Connection(SERVER)
-        server.receive_eof()
-        self.assertEqual(server.close_reason, "")
-
-    def test_close_code_not_available_yet(self):
-        server = Connection(SERVER)
-        self.assertIsNone(server.close_code)
-
-    def test_close_reason_not_available_yet(self):
-        client = Connection(CLIENT)
-        self.assertIsNone(client.close_reason)
-
-    def test_client_sends_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x3c\x3c\x3c\x3c"):
-            client.send_close()
-        self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"])
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_sends_close(self):
-        server = Connection(SERVER)
-        server.send_close()
-        self.assertEqual(server.data_to_send(), [b"\x88\x00"])
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_receives_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x3c\x3c\x3c\x3c"):
-            client.receive_data(b"\x88\x00")
-        self.assertEqual(client.events_received(), [Frame(OP_CLOSE, b"")])
-        self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"])
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_receives_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertEqual(server.events_received(), [Frame(OP_CLOSE, b"")])
-        self.assertEqual(server.data_to_send(), [b"\x88\x00", b""])
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_sends_close_then_receives_close(self):
-        # Client-initiated close handshake on the client side.
-        client = Connection(CLIENT)
-
-        client.send_close()
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, Frame(OP_CLOSE, b""))
-
-        client.receive_data(b"\x88\x00")
-        self.assertFrameReceived(client, Frame(OP_CLOSE, b""))
-        self.assertFrameSent(client, None)
-
-        client.receive_eof()
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None, eof=True)
-
-    def test_server_sends_close_then_receives_close(self):
-        # Server-initiated close handshake on the server side.
-        server = Connection(SERVER)
-
-        server.send_close()
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, Frame(OP_CLOSE, b""))
-
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertFrameReceived(server, Frame(OP_CLOSE, b""))
-        self.assertFrameSent(server, None, eof=True)
-
-        server.receive_eof()
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-    def test_client_receives_close_then_sends_close(self):
-        # Server-initiated close handshake on the client side.
-        client = Connection(CLIENT)
-
-        client.receive_data(b"\x88\x00")
-        self.assertFrameReceived(client, Frame(OP_CLOSE, b""))
-        self.assertFrameSent(client, Frame(OP_CLOSE, b""))
-
-        client.receive_eof()
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None, eof=True)
-
-    def test_server_receives_close_then_sends_close(self):
-        # Client-initiated close handshake on the server side.
-        server = Connection(SERVER)
-
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertFrameReceived(server, Frame(OP_CLOSE, b""))
-        self.assertFrameSent(server, Frame(OP_CLOSE, b""), eof=True)
-
-        server.receive_eof()
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-    def test_client_sends_close_with_code(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_sends_close_with_code(self):
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_receives_close_with_code(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000, "")
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_receives_close_with_code(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001, "")
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_sends_close_with_code_and_reason(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001, "going away")
-        self.assertEqual(
-            client.data_to_send(), [b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away"]
-        )
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_sends_close_with_code_and_reason(self):
-        server = Connection(SERVER)
-        server.send_close(1000, "OK")
-        self.assertEqual(server.data_to_send(), [b"\x88\x04\x03\xe8OK"])
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_receives_close_with_code_and_reason(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x04\x03\xe8OK")
-        self.assertConnectionClosing(client, 1000, "OK")
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_receives_close_with_code_and_reason(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away")
-        self.assertConnectionClosing(server, 1001, "going away")
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_sends_close_with_reason_only(self):
-        client = Connection(CLIENT)
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_close(reason="going away")
-        self.assertEqual(str(raised.exception), "cannot send a reason without a code")
-
-    def test_server_sends_close_with_reason_only(self):
-        server = Connection(SERVER)
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_close(reason="OK")
-        self.assertEqual(str(raised.exception), "cannot send a reason without a code")
-
-    def test_client_receives_close_with_truncated_code(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x01\x03")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "close frame too short")
-        self.assertConnectionFailing(client, 1002, "close frame too short")
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_receives_close_with_truncated_code(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x81\x00\x00\x00\x00\x03")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "close frame too short")
-        self.assertConnectionFailing(server, 1002, "close frame too short")
-        self.assertIs(server.state, CLOSING)
-
-    def test_client_receives_close_with_non_utf8_reason(self):
-        client = Connection(CLIENT)
-
-        client.receive_data(b"\x88\x04\x03\xe8\xff\xff")
-        self.assertIsInstance(client.parser_exc, UnicodeDecodeError)
-        self.assertEqual(
-            str(client.parser_exc),
-            "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte",
-        )
-        self.assertConnectionFailing(client, 1007, "invalid start byte at position 0")
-        self.assertIs(client.state, CLOSING)
-
-    def test_server_receives_close_with_non_utf8_reason(self):
-        server = Connection(SERVER)
-
-        server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe9\xff\xff")
-        self.assertIsInstance(server.parser_exc, UnicodeDecodeError)
-        self.assertEqual(
-            str(server.parser_exc),
-            "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte",
-        )
-        self.assertConnectionFailing(server, 1007, "invalid start byte at position 0")
-        self.assertIs(server.state, CLOSING)
-
-
-class PingTests(ConnectionTestCase):
-    """
-    Test ping. See 5.5.2. Ping in RFC 6544.
-
-    """
-
-    def test_client_sends_ping(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x44\x88\xcc"):
-            client.send_ping(b"")
-        self.assertEqual(client.data_to_send(), [b"\x89\x80\x00\x44\x88\xcc"])
-
-    def test_server_sends_ping(self):
-        server = Connection(SERVER)
-        server.send_ping(b"")
-        self.assertEqual(server.data_to_send(), [b"\x89\x00"])
-
-    def test_client_receives_ping(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x89\x00")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PING, b""),
-        )
-        self.assertFrameSent(
-            client,
-            Frame(OP_PONG, b""),
-        )
-
-    def test_server_receives_ping(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x89\x80\x00\x44\x88\xcc")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PING, b""),
-        )
-        self.assertFrameSent(
-            server,
-            Frame(OP_PONG, b""),
-        )
-
-    def test_client_sends_ping_with_data(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x44\x88\xcc"):
-            client.send_ping(b"\x22\x66\xaa\xee")
-        self.assertEqual(
-            client.data_to_send(), [b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22"]
-        )
-
-    def test_server_sends_ping_with_data(self):
-        server = Connection(SERVER)
-        server.send_ping(b"\x22\x66\xaa\xee")
-        self.assertEqual(server.data_to_send(), [b"\x89\x04\x22\x66\xaa\xee"])
-
-    def test_client_receives_ping_with_data(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x89\x04\x22\x66\xaa\xee")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PING, b"\x22\x66\xaa\xee"),
-        )
-        self.assertFrameSent(
-            client,
-            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
-        )
-
-    def test_server_receives_ping_with_data(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PING, b"\x22\x66\xaa\xee"),
-        )
-        self.assertFrameSent(
-            server,
-            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
-        )
-
-    def test_client_sends_fragmented_ping_frame(self):
-        client = Connection(CLIENT)
-        # This is only possible through a private API.
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_frame(Frame(OP_PING, b"", fin=False))
-        self.assertEqual(str(raised.exception), "fragmented control frame")
-
-    def test_server_sends_fragmented_ping_frame(self):
-        server = Connection(SERVER)
-        # This is only possible through a private API.
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_frame(Frame(OP_PING, b"", fin=False))
-        self.assertEqual(str(raised.exception), "fragmented control frame")
-
-    def test_client_receives_fragmented_ping_frame(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x09\x00")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "fragmented control frame")
-        self.assertConnectionFailing(client, 1002, "fragmented control frame")
-
-    def test_server_receives_fragmented_ping_frame(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x09\x80\x3c\x3c\x3c\x3c")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "fragmented control frame")
-        self.assertConnectionFailing(server, 1002, "fragmented control frame")
-
-    def test_client_sends_ping_after_sending_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        # The spec says: "An endpoint MAY send a Ping frame any time (...)
-        # before the connection is closed" but websockets doesn't support
-        # sending a Ping frame after a Close frame.
-        with self.assertRaises(InvalidState) as raised:
-            client.send_ping(b"")
-        self.assertEqual(
-            str(raised.exception),
-            "cannot write to a WebSocket in the CLOSING state",
-        )
-
-    def test_server_sends_ping_after_sending_close(self):
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        # The spec says: "An endpoint MAY send a Ping frame any time (...)
-        # before the connection is closed" but websockets doesn't support
-        # sending a Ping frame after a Close frame.
-        with self.assertRaises(InvalidState) as raised:
-            server.send_ping(b"")
-        self.assertEqual(
-            str(raised.exception),
-            "cannot write to a WebSocket in the CLOSING state",
-        )
-
-    def test_client_receives_ping_after_receiving_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000)
-        client.receive_data(b"\x89\x04\x22\x66\xaa\xee")
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_ping_after_receiving_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001)
-        server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-
-class PongTests(ConnectionTestCase):
-    """
-    Test pong frames. See 5.5.3. Pong in RFC 6544.
-
-    """
-
-    def test_client_sends_pong(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x44\x88\xcc"):
-            client.send_pong(b"")
-        self.assertEqual(client.data_to_send(), [b"\x8a\x80\x00\x44\x88\xcc"])
-
-    def test_server_sends_pong(self):
-        server = Connection(SERVER)
-        server.send_pong(b"")
-        self.assertEqual(server.data_to_send(), [b"\x8a\x00"])
-
-    def test_client_receives_pong(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x8a\x00")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PONG, b""),
-        )
-
-    def test_server_receives_pong(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x8a\x80\x00\x44\x88\xcc")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PONG, b""),
-        )
-
-    def test_client_sends_pong_with_data(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x44\x88\xcc"):
-            client.send_pong(b"\x22\x66\xaa\xee")
-        self.assertEqual(
-            client.data_to_send(), [b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22"]
-        )
-
-    def test_server_sends_pong_with_data(self):
-        server = Connection(SERVER)
-        server.send_pong(b"\x22\x66\xaa\xee")
-        self.assertEqual(server.data_to_send(), [b"\x8a\x04\x22\x66\xaa\xee"])
-
-    def test_client_receives_pong_with_data(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x8a\x04\x22\x66\xaa\xee")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
-        )
-
-    def test_server_receives_pong_with_data(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
-        )
-
-    def test_client_sends_fragmented_pong_frame(self):
-        client = Connection(CLIENT)
-        # This is only possible through a private API.
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_frame(Frame(OP_PONG, b"", fin=False))
-        self.assertEqual(str(raised.exception), "fragmented control frame")
-
-    def test_server_sends_fragmented_pong_frame(self):
-        server = Connection(SERVER)
-        # This is only possible through a private API.
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_frame(Frame(OP_PONG, b"", fin=False))
-        self.assertEqual(str(raised.exception), "fragmented control frame")
-
-    def test_client_receives_fragmented_pong_frame(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x0a\x00")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "fragmented control frame")
-        self.assertConnectionFailing(client, 1002, "fragmented control frame")
-
-    def test_server_receives_fragmented_pong_frame(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x0a\x80\x3c\x3c\x3c\x3c")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "fragmented control frame")
-        self.assertConnectionFailing(server, 1002, "fragmented control frame")
-
-    def test_client_sends_pong_after_sending_close(self):
-        client = Connection(CLIENT)
-        with self.enforce_mask(b"\x00\x00\x00\x00"):
-            client.send_close(1001)
-        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
-        # websockets doesn't support sending a Pong frame after a Close frame.
-        with self.assertRaises(InvalidState):
-            client.send_pong(b"")
-
-    def test_server_sends_pong_after_sending_close(self):
-        server = Connection(SERVER)
-        server.send_close(1000)
-        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
-        # websockets doesn't support sending a Pong frame after a Close frame.
-        with self.assertRaises(InvalidState):
-            server.send_pong(b"")
-
-    def test_client_receives_pong_after_receiving_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertConnectionClosing(client, 1000)
-        client.receive_data(b"\x8a\x04\x22\x66\xaa\xee")
-        self.assertFrameReceived(client, None)
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_pong_after_receiving_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertConnectionClosing(server, 1001)
-        server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
-        self.assertFrameReceived(server, None)
-        self.assertFrameSent(server, None)
-
-
-class FailTests(ConnectionTestCase):
-    """
-    Test failing the connection.
-
-    See 7.1.7. Fail the WebSocket Connection in RFC 6544.
-
-    """
-
-    def test_client_stops_processing_frames_after_fail(self):
-        client = Connection(CLIENT)
-        client.fail(1002)
-        self.assertConnectionFailing(client, 1002)
-        client.receive_data(b"\x88\x02\x03\xea")
-        self.assertFrameReceived(client, None)
-
-    def test_server_stops_processing_frames_after_fail(self):
-        server = Connection(SERVER)
-        server.fail(1002)
-        self.assertConnectionFailing(server, 1002)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xea")
-        self.assertFrameReceived(server, None)
-
-
-class FragmentationTests(ConnectionTestCase):
-    """
-    Test message fragmentation.
-
-    See 5.4. Fragmentation in RFC 6544.
-
-    """
-
-    def test_client_send_ping_pong_in_fragmented_message(self):
-        client = Connection(CLIENT)
-        client.send_text(b"Spam", fin=False)
-        self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False))
-        client.send_ping(b"Ping")
-        self.assertFrameSent(client, Frame(OP_PING, b"Ping"))
-        client.send_continuation(b"Ham", fin=False)
-        self.assertFrameSent(client, Frame(OP_CONT, b"Ham", fin=False))
-        client.send_pong(b"Pong")
-        self.assertFrameSent(client, Frame(OP_PONG, b"Pong"))
-        client.send_continuation(b"Eggs", fin=True)
-        self.assertFrameSent(client, Frame(OP_CONT, b"Eggs"))
-
-    def test_server_send_ping_pong_in_fragmented_message(self):
-        server = Connection(SERVER)
-        server.send_text(b"Spam", fin=False)
-        self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False))
-        server.send_ping(b"Ping")
-        self.assertFrameSent(server, Frame(OP_PING, b"Ping"))
-        server.send_continuation(b"Ham", fin=False)
-        self.assertFrameSent(server, Frame(OP_CONT, b"Ham", fin=False))
-        server.send_pong(b"Pong")
-        self.assertFrameSent(server, Frame(OP_PONG, b"Pong"))
-        server.send_continuation(b"Eggs", fin=True)
-        self.assertFrameSent(server, Frame(OP_CONT, b"Eggs"))
-
-    def test_client_receive_ping_pong_in_fragmented_message(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x01\x04Spam")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, b"Spam", fin=False),
-        )
-        client.receive_data(b"\x89\x04Ping")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PING, b"Ping"),
-        )
-        self.assertFrameSent(
-            client,
-            Frame(OP_PONG, b"Ping"),
-        )
-        client.receive_data(b"\x00\x03Ham")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, b"Ham", fin=False),
-        )
-        client.receive_data(b"\x8a\x04Pong")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_PONG, b"Pong"),
-        )
-        client.receive_data(b"\x80\x04Eggs")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_CONT, b"Eggs"),
-        )
-
-    def test_server_receive_ping_pong_in_fragmented_message(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, b"Spam", fin=False),
-        )
-        server.receive_data(b"\x89\x84\x00\x00\x00\x00Ping")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PING, b"Ping"),
-        )
-        self.assertFrameSent(
-            server,
-            Frame(OP_PONG, b"Ping"),
-        )
-        server.receive_data(b"\x00\x83\x00\x00\x00\x00Ham")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, b"Ham", fin=False),
-        )
-        server.receive_data(b"\x8a\x84\x00\x00\x00\x00Pong")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_PONG, b"Pong"),
-        )
-        server.receive_data(b"\x80\x84\x00\x00\x00\x00Eggs")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_CONT, b"Eggs"),
-        )
-
-    def test_client_send_close_in_fragmented_message(self):
-        client = Connection(CLIENT)
-        client.send_text(b"Spam", fin=False)
-        self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False))
-        # The spec says: "An endpoint MUST be capable of handling control
-        # frames in the middle of a fragmented message." However, since the
-        # endpoint must not send a data frame after a close frame, a close
-        # frame can't be "in the middle" of a fragmented message.
-        with self.assertRaises(ProtocolError) as raised:
-            client.send_close(1001)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-        client.send_continuation(b"Eggs", fin=True)
-
-    def test_server_send_close_in_fragmented_message(self):
-        server = Connection(CLIENT)
-        server.send_text(b"Spam", fin=False)
-        self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False))
-        # The spec says: "An endpoint MUST be capable of handling control
-        # frames in the middle of a fragmented message." However, since the
-        # endpoint must not send a data frame after a close frame, a close
-        # frame can't be "in the middle" of a fragmented message.
-        with self.assertRaises(ProtocolError) as raised:
-            server.send_close(1000)
-        self.assertEqual(str(raised.exception), "expected a continuation frame")
-
-    def test_client_receive_close_in_fragmented_message(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x01\x04Spam")
-        self.assertFrameReceived(
-            client,
-            Frame(OP_TEXT, b"Spam", fin=False),
-        )
-        # The spec says: "An endpoint MUST be capable of handling control
-        # frames in the middle of a fragmented message." However, since the
-        # endpoint must not send a data frame after a close frame, a close
-        # frame can't be "in the middle" of a fragmented message.
-        client.receive_data(b"\x88\x02\x03\xe8")
-        self.assertIsInstance(client.parser_exc, ProtocolError)
-        self.assertEqual(str(client.parser_exc), "incomplete fragmented message")
-        self.assertConnectionFailing(client, 1002, "incomplete fragmented message")
-
-    def test_server_receive_close_in_fragmented_message(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam")
-        self.assertFrameReceived(
-            server,
-            Frame(OP_TEXT, b"Spam", fin=False),
-        )
-        # The spec says: "An endpoint MUST be capable of handling control
-        # frames in the middle of a fragmented message." However, since the
-        # endpoint must not send a data frame after a close frame, a close
-        # frame can't be "in the middle" of a fragmented message.
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
-        self.assertIsInstance(server.parser_exc, ProtocolError)
-        self.assertEqual(str(server.parser_exc), "incomplete fragmented message")
-        self.assertConnectionFailing(server, 1002, "incomplete fragmented message")
-
-
-class EOFTests(ConnectionTestCase):
-    """
-    Test half-closes on connection termination.
-
-    """
-
-    def test_client_receives_eof(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        self.assertConnectionClosing(client)
-        client.receive_eof()
-        self.assertIs(client.state, CLOSED)
-
-    def test_server_receives_eof(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertConnectionClosing(server)
-        server.receive_eof()
-        self.assertIs(server.state, CLOSED)
-
-    def test_client_receives_eof_between_frames(self):
-        client = Connection(CLIENT)
-        client.receive_eof()
-        self.assertIsInstance(client.parser_exc, EOFError)
-        self.assertEqual(str(client.parser_exc), "unexpected end of stream")
-        self.assertIs(client.state, CLOSED)
-
-    def test_server_receives_eof_between_frames(self):
-        server = Connection(SERVER)
-        server.receive_eof()
-        self.assertIsInstance(server.parser_exc, EOFError)
-        self.assertEqual(str(server.parser_exc), "unexpected end of stream")
-        self.assertIs(server.state, CLOSED)
-
-    def test_client_receives_eof_inside_frame(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x81")
-        client.receive_eof()
-        self.assertIsInstance(client.parser_exc, EOFError)
-        self.assertEqual(
-            str(client.parser_exc),
-            "stream ends after 1 bytes, expected 2 bytes",
-        )
-        self.assertIs(client.state, CLOSED)
-
-    def test_server_receives_eof_inside_frame(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x81")
-        server.receive_eof()
-        self.assertIsInstance(server.parser_exc, EOFError)
-        self.assertEqual(
-            str(server.parser_exc),
-            "stream ends after 1 bytes, expected 2 bytes",
-        )
-        self.assertIs(server.state, CLOSED)
-
-    def test_client_receives_data_after_exception(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(client, 1002, "invalid opcode")
-        client.receive_data(b"\x00\x00")
-        self.assertFrameSent(client, None)
-
-    def test_server_receives_data_after_exception(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(server, 1002, "invalid opcode")
-        server.receive_data(b"\x00\x00")
-        self.assertFrameSent(server, None)
-
-    def test_client_receives_eof_after_exception(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(client, 1002, "invalid opcode")
-        client.receive_eof()
-        self.assertFrameSent(client, None, eof=True)
-
-    def test_server_receives_eof_after_exception(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(server, 1002, "invalid opcode")
-        server.receive_eof()
-        self.assertFrameSent(server, None)
-
-    def test_client_receives_data_and_eof_after_exception(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(client, 1002, "invalid opcode")
-        client.receive_data(b"\x00\x00")
-        client.receive_eof()
-        self.assertFrameSent(client, None, eof=True)
-
-    def test_server_receives_data_and_eof_after_exception(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\xff\xff")
-        self.assertConnectionFailing(server, 1002, "invalid opcode")
-        server.receive_data(b"\x00\x00")
-        server.receive_eof()
-        self.assertFrameSent(server, None)
-
-    def test_client_receives_data_after_eof(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        self.assertConnectionClosing(client)
-        client.receive_eof()
-        with self.assertRaises(EOFError) as raised:
-            client.receive_data(b"\x88\x00")
-        self.assertEqual(str(raised.exception), "stream ended")
-
-    def test_server_receives_data_after_eof(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertConnectionClosing(server)
-        server.receive_eof()
-        with self.assertRaises(EOFError) as raised:
-            server.receive_data(b"\x88\x80\x00\x00\x00\x00")
-        self.assertEqual(str(raised.exception), "stream ended")
-
-    def test_client_receives_eof_after_eof(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        self.assertConnectionClosing(client)
-        client.receive_eof()
-        with self.assertRaises(EOFError) as raised:
-            client.receive_eof()
-        self.assertEqual(str(raised.exception), "stream ended")
-
-    def test_server_receives_eof_after_eof(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertConnectionClosing(server)
-        server.receive_eof()
-        with self.assertRaises(EOFError) as raised:
-            server.receive_eof()
-        self.assertEqual(str(raised.exception), "stream ended")
-
-
-class TCPCloseTests(ConnectionTestCase):
-    """
-    Test expectation of TCP close on connection termination.
-
-    """
-
-    def test_client_default(self):
-        client = Connection(CLIENT)
-        self.assertFalse(client.close_expected())
-
-    def test_server_default(self):
-        server = Connection(SERVER)
-        self.assertFalse(server.close_expected())
-
-    def test_client_sends_close(self):
-        client = Connection(CLIENT)
-        client.send_close()
-        self.assertTrue(client.close_expected())
-
-    def test_server_sends_close(self):
-        server = Connection(SERVER)
-        server.send_close()
-        self.assertTrue(server.close_expected())
-
-    def test_client_receives_close(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        self.assertTrue(client.close_expected())
-
-    def test_client_receives_close_then_eof(self):
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x00")
-        client.receive_eof()
-        self.assertFalse(client.close_expected())
-
-    def test_server_receives_close_then_eof(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        server.receive_eof()
-        self.assertFalse(server.close_expected())
-
-    def test_server_receives_close(self):
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
-        self.assertTrue(server.close_expected())
-
-    def test_client_fails_connection(self):
-        client = Connection(CLIENT)
-        client.fail(1002)
-        self.assertTrue(client.close_expected())
-
-    def test_server_fails_connection(self):
-        server = Connection(SERVER)
-        server.fail(1002)
-        self.assertTrue(server.close_expected())
-
-
-class ConnectionClosedTests(ConnectionTestCase):
-    """
-    Test connection closed exception.
-
-    """
-
-    def test_client_sends_close_then_receives_close(self):
-        # Client-initiated close handshake on the client side complete.
-        client = Connection(CLIENT)
-        client.send_close(1000, "")
-        client.receive_data(b"\x88\x02\x03\xe8")
-        client.receive_eof()
-        exc = client.close_exc
-        self.assertIsInstance(exc, ConnectionClosedOK)
-        self.assertEqual(exc.rcvd, Close(1000, ""))
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertFalse(exc.rcvd_then_sent)
-
-    def test_server_sends_close_then_receives_close(self):
-        # Server-initiated close handshake on the server side complete.
-        server = Connection(SERVER)
-        server.send_close(1000, "")
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8")
-        server.receive_eof()
-        exc = server.close_exc
-        self.assertIsInstance(exc, ConnectionClosedOK)
-        self.assertEqual(exc.rcvd, Close(1000, ""))
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertFalse(exc.rcvd_then_sent)
-
-    def test_client_receives_close_then_sends_close(self):
-        # Server-initiated close handshake on the client side complete.
-        client = Connection(CLIENT)
-        client.receive_data(b"\x88\x02\x03\xe8")
-        client.receive_eof()
-        exc = client.close_exc
-        self.assertIsInstance(exc, ConnectionClosedOK)
-        self.assertEqual(exc.rcvd, Close(1000, ""))
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertTrue(exc.rcvd_then_sent)
-
-    def test_server_receives_close_then_sends_close(self):
-        # Client-initiated close handshake on the server side complete.
-        server = Connection(SERVER)
-        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8")
-        server.receive_eof()
-        exc = server.close_exc
-        self.assertIsInstance(exc, ConnectionClosedOK)
-        self.assertEqual(exc.rcvd, Close(1000, ""))
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertTrue(exc.rcvd_then_sent)
-
-    def test_client_sends_close_then_receives_eof(self):
-        # Client-initiated close handshake on the client side times out.
-        client = Connection(CLIENT)
-        client.send_close(1000, "")
-        client.receive_eof()
-        exc = client.close_exc
-        self.assertIsInstance(exc, ConnectionClosedError)
-        self.assertIsNone(exc.rcvd)
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertIsNone(exc.rcvd_then_sent)
-
-    def test_server_sends_close_then_receives_eof(self):
-        # Server-initiated close handshake on the server side times out.
-        server = Connection(SERVER)
-        server.send_close(1000, "")
-        server.receive_eof()
-        exc = server.close_exc
-        self.assertIsInstance(exc, ConnectionClosedError)
-        self.assertIsNone(exc.rcvd)
-        self.assertEqual(exc.sent, Close(1000, ""))
-        self.assertIsNone(exc.rcvd_then_sent)
-
-    def test_client_receives_eof(self):
-        # Server-initiated close handshake on the client side times out.
-        client = Connection(CLIENT)
-        client.receive_eof()
-        exc = client.close_exc
-        self.assertIsInstance(exc, ConnectionClosedError)
-        self.assertIsNone(exc.rcvd)
-        self.assertIsNone(exc.sent)
-        self.assertIsNone(exc.rcvd_then_sent)
-
-    def test_server_receives_eof(self):
-        # Client-initiated close handshake on the server side times out.
-        server = Connection(SERVER)
-        server.receive_eof()
-        exc = server.close_exc
-        self.assertIsInstance(exc, ConnectionClosedError)
-        self.assertIsNone(exc.rcvd)
-        self.assertIsNone(exc.sent)
-        self.assertIsNone(exc.rcvd_then_sent)
-
-
-class ErrorTests(ConnectionTestCase):
-    """
-    Test other error cases.
-
-    """
-
-    def test_client_hits_internal_error_reading_frame(self):
-        client = Connection(CLIENT)
-        # This isn't supposed to happen, so we're simulating it.
-        with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")):
-            client.receive_data(b"\x81\x00")
-            self.assertIsInstance(client.parser_exc, RuntimeError)
-            self.assertEqual(str(client.parser_exc), "BOOM")
-        self.assertConnectionFailing(client, 1011, "")
-
-    def test_server_hits_internal_error_reading_frame(self):
-        server = Connection(SERVER)
-        # This isn't supposed to happen, so we're simulating it.
-        with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")):
-            server.receive_data(b"\x81\x80\x00\x00\x00\x00")
-            self.assertIsInstance(server.parser_exc, RuntimeError)
-            self.assertEqual(str(server.parser_exc), "BOOM")
-        self.assertConnectionFailing(server, 1011, "")
-
-
-class ExtensionsTests(ConnectionTestCase):
-    """
-    Test how extensions affect frames.
-
-    """
-
-    def test_client_extension_encodes_frame(self):
-        client = Connection(CLIENT)
-        client.extensions = [Rsv2Extension()]
-        with self.enforce_mask(b"\x00\x44\x88\xcc"):
-            client.send_ping(b"")
-        self.assertEqual(client.data_to_send(), [b"\xa9\x80\x00\x44\x88\xcc"])
-
-    def test_server_extension_encodes_frame(self):
-        server = Connection(SERVER)
-        server.extensions = [Rsv2Extension()]
-        server.send_ping(b"")
-        self.assertEqual(server.data_to_send(), [b"\xa9\x00"])
-
-    def test_client_extension_decodes_frame(self):
-        client = Connection(CLIENT)
-        client.extensions = [Rsv2Extension()]
-        client.receive_data(b"\xaa\x00")
-        self.assertEqual(client.events_received(), [Frame(OP_PONG, b"")])
-
-    def test_server_extension_decodes_frame(self):
-        server = Connection(SERVER)
-        server.extensions = [Rsv2Extension()]
-        server.receive_data(b"\xaa\x80\x00\x44\x88\xcc")
-        self.assertEqual(server.events_received(), [Frame(OP_PONG, b"")])
-
-
-class MiscTests(unittest.TestCase):
-    def test_client_default_logger(self):
-        client = Connection(CLIENT)
-        logger = logging.getLogger("websockets.client")
-        self.assertIs(client.logger, logger)
-
-    def test_server_default_logger(self):
-        server = Connection(SERVER)
-        logger = logging.getLogger("websockets.server")
-        self.assertIs(server.logger, logger)
-
-    def test_client_custom_logger(self):
-        logger = logging.getLogger("test")
-        client = Connection(CLIENT, logger=logger)
-        self.assertIs(client.logger, logger)
-
-    def test_server_custom_logger(self):
-        logger = logging.getLogger("test")
-        server = Connection(SERVER, logger=logger)
-        self.assertIs(server.logger, logger)
+        self.assertIs(Connection, Protocol)
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
new file mode 100644
index 0000000..7321d25
--- /dev/null
+++ b/tests/test_protocol.py
@@ -0,0 +1,1737 @@
+import logging
+import unittest.mock
+
+from websockets.exceptions import (
+    ConnectionClosedError,
+    ConnectionClosedOK,
+    InvalidState,
+    PayloadTooBig,
+    ProtocolError,
+)
+from websockets.frames import (
+    OP_BINARY,
+    OP_CLOSE,
+    OP_CONT,
+    OP_PING,
+    OP_PONG,
+    OP_TEXT,
+    Close,
+    Frame,
+)
+from websockets.protocol import *
+from websockets.protocol import CLIENT, CLOSED, CLOSING, SERVER
+
+from .extensions.utils import Rsv2Extension
+from .test_frames import FramesTestCase
+
+
+class ProtocolTestCase(FramesTestCase):
+    def assertFrameSent(self, connection, frame, eof=False):
+        """
+        Outgoing data for ``connection`` contains the given frame.
+
+        ``frame`` may be ``None`` if no frame is expected.
+
+        When ``eof`` is ``True``, the end of the stream is also expected.
+
+        """
+        frames_sent = [
+            None
+            if write is SEND_EOF
+            else self.parse(
+                write,
+                mask=connection.side is CLIENT,
+                extensions=connection.extensions,
+            )
+            for write in connection.data_to_send()
+        ]
+        frames_expected = [] if frame is None else [frame]
+        if eof:
+            frames_expected += [None]
+        self.assertEqual(frames_sent, frames_expected)
+
+    def assertFrameReceived(self, connection, frame):
+        """
+        Incoming data for ``connection`` contains the given frame.
+
+        ``frame`` may be ``None`` if no frame is expected.
+
+        """
+        frames_received = connection.events_received()
+        frames_expected = [] if frame is None else [frame]
+        self.assertEqual(frames_received, frames_expected)
+
+    def assertConnectionClosing(self, connection, code=None, reason=""):
+        """
+        Incoming data caused the "Start the WebSocket Closing Handshake" process.
+
+        """
+        close_frame = Frame(
+            OP_CLOSE,
+            b"" if code is None else Close(code, reason).serialize(),
+        )
+        # A close frame was received.
+        self.assertFrameReceived(connection, close_frame)
+        # A close frame and possibly the end of stream were sent.
+        self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER)
+
+    def assertConnectionFailing(self, connection, code=None, reason=""):
+        """
+        Incoming data caused the "Fail the WebSocket Connection" process.
+
+        """
+        close_frame = Frame(
+            OP_CLOSE,
+            b"" if code is None else Close(code, reason).serialize(),
+        )
+        # No frame was received.
+        self.assertFrameReceived(connection, None)
+        # A close frame and possibly the end of stream were sent.
+        self.assertFrameSent(connection, close_frame, eof=connection.side is SERVER)
+
+
+class MaskingTests(ProtocolTestCase):
+    """
+    Test frame masking.
+
+    5.1.  Overview
+
+    """
+
+    unmasked_text_frame_date = b"\x81\x04Spam"
+    masked_text_frame_data = b"\x81\x84\x00\xff\x00\xff\x53\x8f\x61\x92"
+
+    def test_client_sends_masked_frame(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\xff\x00\xff"):
+            client.send_text(b"Spam", True)
+        self.assertEqual(client.data_to_send(), [self.masked_text_frame_data])
+
+    def test_server_sends_unmasked_frame(self):
+        server = Protocol(SERVER)
+        server.send_text(b"Spam", True)
+        self.assertEqual(server.data_to_send(), [self.unmasked_text_frame_date])
+
+    def test_client_receives_unmasked_frame(self):
+        client = Protocol(CLIENT)
+        client.receive_data(self.unmasked_text_frame_date)
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, b"Spam"),
+        )
+
+    def test_server_receives_masked_frame(self):
+        server = Protocol(SERVER)
+        server.receive_data(self.masked_text_frame_data)
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, b"Spam"),
+        )
+
+    def test_client_receives_masked_frame(self):
+        client = Protocol(CLIENT)
+        client.receive_data(self.masked_text_frame_data)
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "incorrect masking")
+        self.assertConnectionFailing(client, 1002, "incorrect masking")
+
+    def test_server_receives_unmasked_frame(self):
+        server = Protocol(SERVER)
+        server.receive_data(self.unmasked_text_frame_date)
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "incorrect masking")
+        self.assertConnectionFailing(server, 1002, "incorrect masking")
+
+
+class ContinuationTests(ProtocolTestCase):
+    """
+    Test continuation frames without text or binary frames.
+
+    """
+
+    def test_client_sends_unexpected_continuation(self):
+        client = Protocol(CLIENT)
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_continuation(b"", fin=False)
+        self.assertEqual(str(raised.exception), "unexpected continuation frame")
+
+    def test_server_sends_unexpected_continuation(self):
+        server = Protocol(SERVER)
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_continuation(b"", fin=False)
+        self.assertEqual(str(raised.exception), "unexpected continuation frame")
+
+    def test_client_receives_unexpected_continuation(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x00\x00")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "unexpected continuation frame")
+        self.assertConnectionFailing(client, 1002, "unexpected continuation frame")
+
+    def test_server_receives_unexpected_continuation(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x00\x80\x00\x00\x00\x00")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "unexpected continuation frame")
+        self.assertConnectionFailing(server, 1002, "unexpected continuation frame")
+
+    def test_client_sends_continuation_after_sending_close(self):
+        client = Protocol(CLIENT)
+        # Since it isn't possible to send a close frame in a fragmented
+        # message (see test_client_send_close_in_fragmented_message), in fact,
+        # this is the same test as test_client_sends_unexpected_continuation.
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_continuation(b"", fin=False)
+        self.assertEqual(str(raised.exception), "unexpected continuation frame")
+
+    def test_server_sends_continuation_after_sending_close(self):
+        # Since it isn't possible to send a close frame in a fragmented
+        # message (see test_server_send_close_in_fragmented_message), in fact,
+        # this is the same test as test_server_sends_unexpected_continuation.
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_continuation(b"", fin=False)
+        self.assertEqual(str(raised.exception), "unexpected continuation frame")
+
+    def test_client_receives_continuation_after_receiving_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000)
+        client.receive_data(b"\x00\x00")
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_continuation_after_receiving_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001)
+        server.receive_data(b"\x00\x80\x00\xff\x00\xff")
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+
+class TextTests(ProtocolTestCase):
+    """
+    Test text frames and continuation frames.
+
+    """
+
+    def test_client_sends_text(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_text("😀".encode())
+        self.assertEqual(
+            client.data_to_send(), [b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80"]
+        )
+
+    def test_server_sends_text(self):
+        server = Protocol(SERVER)
+        server.send_text("😀".encode())
+        self.assertEqual(server.data_to_send(), [b"\x81\x04\xf0\x9f\x98\x80"])
+
+    def test_client_receives_text(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, "😀".encode()),
+        )
+
+    def test_server_receives_text(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, "😀".encode()),
+        )
+
+    def test_client_receives_text_over_size_limit(self):
+        client = Protocol(CLIENT, max_size=3)
+        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
+        self.assertIsInstance(client.parser_exc, PayloadTooBig)
+        self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)")
+        self.assertConnectionFailing(client, 1009, "over size limit (4 > 3 bytes)")
+
+    def test_server_receives_text_over_size_limit(self):
+        server = Protocol(SERVER, max_size=3)
+        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
+        self.assertIsInstance(server.parser_exc, PayloadTooBig)
+        self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)")
+        self.assertConnectionFailing(server, 1009, "over size limit (4 > 3 bytes)")
+
+    def test_client_receives_text_without_size_limit(self):
+        client = Protocol(CLIENT, max_size=None)
+        client.receive_data(b"\x81\x04\xf0\x9f\x98\x80")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, "😀".encode()),
+        )
+
+    def test_server_receives_text_without_size_limit(self):
+        server = Protocol(SERVER, max_size=None)
+        server.receive_data(b"\x81\x84\x00\x00\x00\x00\xf0\x9f\x98\x80")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, "😀".encode()),
+        )
+
+    def test_client_sends_fragmented_text(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_text("😀".encode()[:2], fin=False)
+        self.assertEqual(client.data_to_send(), [b"\x01\x82\x00\x00\x00\x00\xf0\x9f"])
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_continuation("😀😀".encode()[2:6], fin=False)
+        self.assertEqual(
+            client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f"]
+        )
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_continuation("😀".encode()[2:], fin=True)
+        self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\x98\x80"])
+
+    def test_server_sends_fragmented_text(self):
+        server = Protocol(SERVER)
+        server.send_text("😀".encode()[:2], fin=False)
+        self.assertEqual(server.data_to_send(), [b"\x01\x02\xf0\x9f"])
+        server.send_continuation("😀😀".encode()[2:6], fin=False)
+        self.assertEqual(server.data_to_send(), [b"\x00\x04\x98\x80\xf0\x9f"])
+        server.send_continuation("😀".encode()[2:], fin=True)
+        self.assertEqual(server.data_to_send(), [b"\x80\x02\x98\x80"])
+
+    def test_client_receives_fragmented_text(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x01\x02\xf0\x9f")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        client.receive_data(b"\x00\x04\x98\x80\xf0\x9f")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
+        )
+        client.receive_data(b"\x80\x02\x98\x80")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, "😀".encode()[2:]),
+        )
+
+    def test_server_receives_fragmented_text(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
+        )
+        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, "😀".encode()[2:]),
+        )
+
+    def test_client_receives_fragmented_text_over_size_limit(self):
+        client = Protocol(CLIENT, max_size=3)
+        client.receive_data(b"\x01\x02\xf0\x9f")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        client.receive_data(b"\x80\x02\x98\x80")
+        self.assertIsInstance(client.parser_exc, PayloadTooBig)
+        self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)")
+        self.assertConnectionFailing(client, 1009, "over size limit (2 > 1 bytes)")
+
+    def test_server_receives_fragmented_text_over_size_limit(self):
+        server = Protocol(SERVER, max_size=3)
+        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
+        self.assertIsInstance(server.parser_exc, PayloadTooBig)
+        self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)")
+        self.assertConnectionFailing(server, 1009, "over size limit (2 > 1 bytes)")
+
+    def test_client_receives_fragmented_text_without_size_limit(self):
+        client = Protocol(CLIENT, max_size=None)
+        client.receive_data(b"\x01\x02\xf0\x9f")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        client.receive_data(b"\x00\x04\x98\x80\xf0\x9f")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
+        )
+        client.receive_data(b"\x80\x02\x98\x80")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, "😀".encode()[2:]),
+        )
+
+    def test_server_receives_fragmented_text_without_size_limit(self):
+        server = Protocol(SERVER, max_size=None)
+        server.receive_data(b"\x01\x82\x00\x00\x00\x00\xf0\x9f")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, "😀".encode()[:2], fin=False),
+        )
+        server.receive_data(b"\x00\x84\x00\x00\x00\x00\x98\x80\xf0\x9f")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, "😀😀".encode()[2:6], fin=False),
+        )
+        server.receive_data(b"\x80\x82\x00\x00\x00\x00\x98\x80")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, "😀".encode()[2:]),
+        )
+
+    def test_client_sends_unexpected_text(self):
+        client = Protocol(CLIENT)
+        client.send_text(b"", fin=False)
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_text(b"", fin=False)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+
+    def test_server_sends_unexpected_text(self):
+        server = Protocol(SERVER)
+        server.send_text(b"", fin=False)
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_text(b"", fin=False)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+
+    def test_client_receives_unexpected_text(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x01\x00")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, b"", fin=False),
+        )
+        client.receive_data(b"\x01\x00")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "expected a continuation frame")
+        self.assertConnectionFailing(client, 1002, "expected a continuation frame")
+
+    def test_server_receives_unexpected_text(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x01\x80\x00\x00\x00\x00")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, b"", fin=False),
+        )
+        server.receive_data(b"\x01\x80\x00\x00\x00\x00")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "expected a continuation frame")
+        self.assertConnectionFailing(server, 1002, "expected a continuation frame")
+
+    def test_client_sends_text_after_sending_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        with self.assertRaises(InvalidState):
+            client.send_text(b"")
+
+    def test_server_sends_text_after_sending_close(self):
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        with self.assertRaises(InvalidState):
+            server.send_text(b"")
+
+    def test_client_receives_text_after_receiving_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000)
+        client.receive_data(b"\x81\x00")
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_text_after_receiving_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001)
+        server.receive_data(b"\x81\x80\x00\xff\x00\xff")
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+
+class BinaryTests(ProtocolTestCase):
+    """
+    Test binary frames and continuation frames.
+
+    """
+
+    def test_client_sends_binary(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_binary(b"\x01\x02\xfe\xff")
+        self.assertEqual(
+            client.data_to_send(), [b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff"]
+        )
+
+    def test_server_sends_binary(self):
+        server = Protocol(SERVER)
+        server.send_binary(b"\x01\x02\xfe\xff")
+        self.assertEqual(server.data_to_send(), [b"\x82\x04\x01\x02\xfe\xff"])
+
+    def test_client_receives_binary(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x82\x04\x01\x02\xfe\xff")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_BINARY, b"\x01\x02\xfe\xff"),
+        )
+
+    def test_server_receives_binary(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_BINARY, b"\x01\x02\xfe\xff"),
+        )
+
+    def test_client_receives_binary_over_size_limit(self):
+        client = Protocol(CLIENT, max_size=3)
+        client.receive_data(b"\x82\x04\x01\x02\xfe\xff")
+        self.assertIsInstance(client.parser_exc, PayloadTooBig)
+        self.assertEqual(str(client.parser_exc), "over size limit (4 > 3 bytes)")
+        self.assertConnectionFailing(client, 1009, "over size limit (4 > 3 bytes)")
+
+    def test_server_receives_binary_over_size_limit(self):
+        server = Protocol(SERVER, max_size=3)
+        server.receive_data(b"\x82\x84\x00\x00\x00\x00\x01\x02\xfe\xff")
+        self.assertIsInstance(server.parser_exc, PayloadTooBig)
+        self.assertEqual(str(server.parser_exc), "over size limit (4 > 3 bytes)")
+        self.assertConnectionFailing(server, 1009, "over size limit (4 > 3 bytes)")
+
+    def test_client_sends_fragmented_binary(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_binary(b"\x01\x02", fin=False)
+        self.assertEqual(client.data_to_send(), [b"\x02\x82\x00\x00\x00\x00\x01\x02"])
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_continuation(b"\xee\xff\x01\x02", fin=False)
+        self.assertEqual(
+            client.data_to_send(), [b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02"]
+        )
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_continuation(b"\xee\xff", fin=True)
+        self.assertEqual(client.data_to_send(), [b"\x80\x82\x00\x00\x00\x00\xee\xff"])
+
+    def test_server_sends_fragmented_binary(self):
+        server = Protocol(SERVER)
+        server.send_binary(b"\x01\x02", fin=False)
+        self.assertEqual(server.data_to_send(), [b"\x02\x02\x01\x02"])
+        server.send_continuation(b"\xee\xff\x01\x02", fin=False)
+        self.assertEqual(server.data_to_send(), [b"\x00\x04\xee\xff\x01\x02"])
+        server.send_continuation(b"\xee\xff", fin=True)
+        self.assertEqual(server.data_to_send(), [b"\x80\x02\xee\xff"])
+
+    def test_client_receives_fragmented_binary(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x02\x02\x01\x02")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_BINARY, b"\x01\x02", fin=False),
+        )
+        client.receive_data(b"\x00\x04\xfe\xff\x01\x02")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, b"\xfe\xff\x01\x02", fin=False),
+        )
+        client.receive_data(b"\x80\x02\xfe\xff")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, b"\xfe\xff"),
+        )
+
+    def test_server_receives_fragmented_binary(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_BINARY, b"\x01\x02", fin=False),
+        )
+        server.receive_data(b"\x00\x84\x00\x00\x00\x00\xee\xff\x01\x02")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, b"\xee\xff\x01\x02", fin=False),
+        )
+        server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, b"\xfe\xff"),
+        )
+
+    def test_client_receives_fragmented_binary_over_size_limit(self):
+        client = Protocol(CLIENT, max_size=3)
+        client.receive_data(b"\x02\x02\x01\x02")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_BINARY, b"\x01\x02", fin=False),
+        )
+        client.receive_data(b"\x80\x02\xfe\xff")
+        self.assertIsInstance(client.parser_exc, PayloadTooBig)
+        self.assertEqual(str(client.parser_exc), "over size limit (2 > 1 bytes)")
+        self.assertConnectionFailing(client, 1009, "over size limit (2 > 1 bytes)")
+
+    def test_server_receives_fragmented_binary_over_size_limit(self):
+        server = Protocol(SERVER, max_size=3)
+        server.receive_data(b"\x02\x82\x00\x00\x00\x00\x01\x02")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_BINARY, b"\x01\x02", fin=False),
+        )
+        server.receive_data(b"\x80\x82\x00\x00\x00\x00\xfe\xff")
+        self.assertIsInstance(server.parser_exc, PayloadTooBig)
+        self.assertEqual(str(server.parser_exc), "over size limit (2 > 1 bytes)")
+        self.assertConnectionFailing(server, 1009, "over size limit (2 > 1 bytes)")
+
+    def test_client_sends_unexpected_binary(self):
+        client = Protocol(CLIENT)
+        client.send_binary(b"", fin=False)
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_binary(b"", fin=False)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+
+    def test_server_sends_unexpected_binary(self):
+        server = Protocol(SERVER)
+        server.send_binary(b"", fin=False)
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_binary(b"", fin=False)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+
+    def test_client_receives_unexpected_binary(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x02\x00")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_BINARY, b"", fin=False),
+        )
+        client.receive_data(b"\x02\x00")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "expected a continuation frame")
+        self.assertConnectionFailing(client, 1002, "expected a continuation frame")
+
+    def test_server_receives_unexpected_binary(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x02\x80\x00\x00\x00\x00")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_BINARY, b"", fin=False),
+        )
+        server.receive_data(b"\x02\x80\x00\x00\x00\x00")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "expected a continuation frame")
+        self.assertConnectionFailing(server, 1002, "expected a continuation frame")
+
+    def test_client_sends_binary_after_sending_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        with self.assertRaises(InvalidState):
+            client.send_binary(b"")
+
+    def test_server_sends_binary_after_sending_close(self):
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        with self.assertRaises(InvalidState):
+            server.send_binary(b"")
+
+    def test_client_receives_binary_after_receiving_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000)
+        client.receive_data(b"\x82\x00")
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_binary_after_receiving_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001)
+        server.receive_data(b"\x82\x80\x00\xff\x00\xff")
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+
+class CloseTests(ProtocolTestCase):
+    """
+    Test close frames.
+
+    See RFC 6544:
+
+    5.5.1. Close
+    7.1.6.  The WebSocket Connection Close Reason
+    7.1.7.  Fail the WebSocket Connection
+
+    """
+
+    def test_close_code(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x04\x03\xe8OK")
+        client.receive_eof()
+        self.assertEqual(client.close_code, 1000)
+
+    def test_close_reason(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe8OK")
+        server.receive_eof()
+        self.assertEqual(server.close_reason, "OK")
+
+    def test_close_code_not_provided(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x00\x00\x00\x00")
+        server.receive_eof()
+        self.assertEqual(server.close_code, 1005)
+
+    def test_close_reason_not_provided(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        client.receive_eof()
+        self.assertEqual(client.close_reason, "")
+
+    def test_close_code_not_available(self):
+        client = Protocol(CLIENT)
+        client.receive_eof()
+        self.assertEqual(client.close_code, 1006)
+
+    def test_close_reason_not_available(self):
+        server = Protocol(SERVER)
+        server.receive_eof()
+        self.assertEqual(server.close_reason, "")
+
+    def test_close_code_not_available_yet(self):
+        server = Protocol(SERVER)
+        self.assertIsNone(server.close_code)
+
+    def test_close_reason_not_available_yet(self):
+        client = Protocol(CLIENT)
+        self.assertIsNone(client.close_reason)
+
+    def test_client_sends_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x3c\x3c\x3c\x3c"):
+            client.send_close()
+        self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"])
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_sends_close(self):
+        server = Protocol(SERVER)
+        server.send_close()
+        self.assertEqual(server.data_to_send(), [b"\x88\x00"])
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_receives_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x3c\x3c\x3c\x3c"):
+            client.receive_data(b"\x88\x00")
+        self.assertEqual(client.events_received(), [Frame(OP_CLOSE, b"")])
+        self.assertEqual(client.data_to_send(), [b"\x88\x80\x3c\x3c\x3c\x3c"])
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_receives_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertEqual(server.events_received(), [Frame(OP_CLOSE, b"")])
+        self.assertEqual(server.data_to_send(), [b"\x88\x00", b""])
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_sends_close_then_receives_close(self):
+        # Client-initiated close handshake on the client side.
+        client = Protocol(CLIENT)
+
+        client.send_close()
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, Frame(OP_CLOSE, b""))
+
+        client.receive_data(b"\x88\x00")
+        self.assertFrameReceived(client, Frame(OP_CLOSE, b""))
+        self.assertFrameSent(client, None)
+
+        client.receive_eof()
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None, eof=True)
+
+    def test_server_sends_close_then_receives_close(self):
+        # Server-initiated close handshake on the server side.
+        server = Protocol(SERVER)
+
+        server.send_close()
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, Frame(OP_CLOSE, b""))
+
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertFrameReceived(server, Frame(OP_CLOSE, b""))
+        self.assertFrameSent(server, None, eof=True)
+
+        server.receive_eof()
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+    def test_client_receives_close_then_sends_close(self):
+        # Server-initiated close handshake on the client side.
+        client = Protocol(CLIENT)
+
+        client.receive_data(b"\x88\x00")
+        self.assertFrameReceived(client, Frame(OP_CLOSE, b""))
+        self.assertFrameSent(client, Frame(OP_CLOSE, b""))
+
+        client.receive_eof()
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None, eof=True)
+
+    def test_server_receives_close_then_sends_close(self):
+        # Client-initiated close handshake on the server side.
+        server = Protocol(SERVER)
+
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertFrameReceived(server, Frame(OP_CLOSE, b""))
+        self.assertFrameSent(server, Frame(OP_CLOSE, b""), eof=True)
+
+        server.receive_eof()
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+    def test_client_sends_close_with_code(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_sends_close_with_code(self):
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_receives_close_with_code(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000, "")
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_receives_close_with_code(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001, "")
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_sends_close_with_code_and_reason(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001, "going away")
+        self.assertEqual(
+            client.data_to_send(), [b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away"]
+        )
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_sends_close_with_code_and_reason(self):
+        server = Protocol(SERVER)
+        server.send_close(1000, "OK")
+        self.assertEqual(server.data_to_send(), [b"\x88\x04\x03\xe8OK"])
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_receives_close_with_code_and_reason(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x04\x03\xe8OK")
+        self.assertConnectionClosing(client, 1000, "OK")
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_receives_close_with_code_and_reason(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x8c\x00\x00\x00\x00\x03\xe9going away")
+        self.assertConnectionClosing(server, 1001, "going away")
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_sends_close_with_reason_only(self):
+        client = Protocol(CLIENT)
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_close(reason="going away")
+        self.assertEqual(str(raised.exception), "cannot send a reason without a code")
+
+    def test_server_sends_close_with_reason_only(self):
+        server = Protocol(SERVER)
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_close(reason="OK")
+        self.assertEqual(str(raised.exception), "cannot send a reason without a code")
+
+    def test_client_receives_close_with_truncated_code(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x01\x03")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "close frame too short")
+        self.assertConnectionFailing(client, 1002, "close frame too short")
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_receives_close_with_truncated_code(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x81\x00\x00\x00\x00\x03")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "close frame too short")
+        self.assertConnectionFailing(server, 1002, "close frame too short")
+        self.assertIs(server.state, CLOSING)
+
+    def test_client_receives_close_with_non_utf8_reason(self):
+        client = Protocol(CLIENT)
+
+        client.receive_data(b"\x88\x04\x03\xe8\xff\xff")
+        self.assertIsInstance(client.parser_exc, UnicodeDecodeError)
+        self.assertEqual(
+            str(client.parser_exc),
+            "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte",
+        )
+        self.assertConnectionFailing(client, 1007, "invalid start byte at position 0")
+        self.assertIs(client.state, CLOSING)
+
+    def test_server_receives_close_with_non_utf8_reason(self):
+        server = Protocol(SERVER)
+
+        server.receive_data(b"\x88\x84\x00\x00\x00\x00\x03\xe9\xff\xff")
+        self.assertIsInstance(server.parser_exc, UnicodeDecodeError)
+        self.assertEqual(
+            str(server.parser_exc),
+            "'utf-8' codec can't decode byte 0xff in position 0: invalid start byte",
+        )
+        self.assertConnectionFailing(server, 1007, "invalid start byte at position 0")
+        self.assertIs(server.state, CLOSING)
+
+
+class PingTests(ProtocolTestCase):
+    """
+    Test ping. See 5.5.2. Ping in RFC 6544.
+
+    """
+
+    def test_client_sends_ping(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x44\x88\xcc"):
+            client.send_ping(b"")
+        self.assertEqual(client.data_to_send(), [b"\x89\x80\x00\x44\x88\xcc"])
+
+    def test_server_sends_ping(self):
+        server = Protocol(SERVER)
+        server.send_ping(b"")
+        self.assertEqual(server.data_to_send(), [b"\x89\x00"])
+
+    def test_client_receives_ping(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x89\x00")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PING, b""),
+        )
+        self.assertFrameSent(
+            client,
+            Frame(OP_PONG, b""),
+        )
+
+    def test_server_receives_ping(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x89\x80\x00\x44\x88\xcc")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PING, b""),
+        )
+        self.assertFrameSent(
+            server,
+            Frame(OP_PONG, b""),
+        )
+
+    def test_client_sends_ping_with_data(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x44\x88\xcc"):
+            client.send_ping(b"\x22\x66\xaa\xee")
+        self.assertEqual(
+            client.data_to_send(), [b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22"]
+        )
+
+    def test_server_sends_ping_with_data(self):
+        server = Protocol(SERVER)
+        server.send_ping(b"\x22\x66\xaa\xee")
+        self.assertEqual(server.data_to_send(), [b"\x89\x04\x22\x66\xaa\xee"])
+
+    def test_client_receives_ping_with_data(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x89\x04\x22\x66\xaa\xee")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PING, b"\x22\x66\xaa\xee"),
+        )
+        self.assertFrameSent(
+            client,
+            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
+        )
+
+    def test_server_receives_ping_with_data(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PING, b"\x22\x66\xaa\xee"),
+        )
+        self.assertFrameSent(
+            server,
+            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
+        )
+
+    def test_client_sends_fragmented_ping_frame(self):
+        client = Protocol(CLIENT)
+        # This is only possible through a private API.
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_frame(Frame(OP_PING, b"", fin=False))
+        self.assertEqual(str(raised.exception), "fragmented control frame")
+
+    def test_server_sends_fragmented_ping_frame(self):
+        server = Protocol(SERVER)
+        # This is only possible through a private API.
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_frame(Frame(OP_PING, b"", fin=False))
+        self.assertEqual(str(raised.exception), "fragmented control frame")
+
+    def test_client_receives_fragmented_ping_frame(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x09\x00")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "fragmented control frame")
+        self.assertConnectionFailing(client, 1002, "fragmented control frame")
+
+    def test_server_receives_fragmented_ping_frame(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x09\x80\x3c\x3c\x3c\x3c")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "fragmented control frame")
+        self.assertConnectionFailing(server, 1002, "fragmented control frame")
+
+    def test_client_sends_ping_after_sending_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        # The spec says: "An endpoint MAY send a Ping frame any time (...)
+        # before the connection is closed" but websockets doesn't support
+        # sending a Ping frame after a Close frame.
+        with self.assertRaises(InvalidState) as raised:
+            client.send_ping(b"")
+        self.assertEqual(
+            str(raised.exception),
+            "cannot write to a WebSocket in the CLOSING state",
+        )
+
+    def test_server_sends_ping_after_sending_close(self):
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        # The spec says: "An endpoint MAY send a Ping frame any time (...)
+        # before the connection is closed" but websockets doesn't support
+        # sending a Ping frame after a Close frame.
+        with self.assertRaises(InvalidState) as raised:
+            server.send_ping(b"")
+        self.assertEqual(
+            str(raised.exception),
+            "cannot write to a WebSocket in the CLOSING state",
+        )
+
+    def test_client_receives_ping_after_receiving_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000)
+        client.receive_data(b"\x89\x04\x22\x66\xaa\xee")
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_ping_after_receiving_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001)
+        server.receive_data(b"\x89\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+
+class PongTests(ProtocolTestCase):
+    """
+    Test pong frames. See 5.5.3. Pong in RFC 6544.
+
+    """
+
+    def test_client_sends_pong(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x44\x88\xcc"):
+            client.send_pong(b"")
+        self.assertEqual(client.data_to_send(), [b"\x8a\x80\x00\x44\x88\xcc"])
+
+    def test_server_sends_pong(self):
+        server = Protocol(SERVER)
+        server.send_pong(b"")
+        self.assertEqual(server.data_to_send(), [b"\x8a\x00"])
+
+    def test_client_receives_pong(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x8a\x00")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PONG, b""),
+        )
+
+    def test_server_receives_pong(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x8a\x80\x00\x44\x88\xcc")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PONG, b""),
+        )
+
+    def test_client_sends_pong_with_data(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x44\x88\xcc"):
+            client.send_pong(b"\x22\x66\xaa\xee")
+        self.assertEqual(
+            client.data_to_send(), [b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22"]
+        )
+
+    def test_server_sends_pong_with_data(self):
+        server = Protocol(SERVER)
+        server.send_pong(b"\x22\x66\xaa\xee")
+        self.assertEqual(server.data_to_send(), [b"\x8a\x04\x22\x66\xaa\xee"])
+
+    def test_client_receives_pong_with_data(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x8a\x04\x22\x66\xaa\xee")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
+        )
+
+    def test_server_receives_pong_with_data(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PONG, b"\x22\x66\xaa\xee"),
+        )
+
+    def test_client_sends_fragmented_pong_frame(self):
+        client = Protocol(CLIENT)
+        # This is only possible through a private API.
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_frame(Frame(OP_PONG, b"", fin=False))
+        self.assertEqual(str(raised.exception), "fragmented control frame")
+
+    def test_server_sends_fragmented_pong_frame(self):
+        server = Protocol(SERVER)
+        # This is only possible through a private API.
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_frame(Frame(OP_PONG, b"", fin=False))
+        self.assertEqual(str(raised.exception), "fragmented control frame")
+
+    def test_client_receives_fragmented_pong_frame(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x0a\x00")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "fragmented control frame")
+        self.assertConnectionFailing(client, 1002, "fragmented control frame")
+
+    def test_server_receives_fragmented_pong_frame(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x0a\x80\x3c\x3c\x3c\x3c")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "fragmented control frame")
+        self.assertConnectionFailing(server, 1002, "fragmented control frame")
+
+    def test_client_sends_pong_after_sending_close(self):
+        client = Protocol(CLIENT)
+        with self.enforce_mask(b"\x00\x00\x00\x00"):
+            client.send_close(1001)
+        self.assertEqual(client.data_to_send(), [b"\x88\x82\x00\x00\x00\x00\x03\xe9"])
+        # websockets doesn't support sending a Pong frame after a Close frame.
+        with self.assertRaises(InvalidState):
+            client.send_pong(b"")
+
+    def test_server_sends_pong_after_sending_close(self):
+        server = Protocol(SERVER)
+        server.send_close(1000)
+        self.assertEqual(server.data_to_send(), [b"\x88\x02\x03\xe8"])
+        # websockets doesn't support sending a Pong frame after a Close frame.
+        with self.assertRaises(InvalidState):
+            server.send_pong(b"")
+
+    def test_client_receives_pong_after_receiving_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertConnectionClosing(client, 1000)
+        client.receive_data(b"\x8a\x04\x22\x66\xaa\xee")
+        self.assertFrameReceived(client, None)
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_pong_after_receiving_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertConnectionClosing(server, 1001)
+        server.receive_data(b"\x8a\x84\x00\x44\x88\xcc\x22\x22\x22\x22")
+        self.assertFrameReceived(server, None)
+        self.assertFrameSent(server, None)
+
+
+class FailTests(ProtocolTestCase):
+    """
+    Test failing the connection.
+
+    See 7.1.7. Fail the WebSocket Connection in RFC 6544.
+
+    """
+
+    def test_client_stops_processing_frames_after_fail(self):
+        client = Protocol(CLIENT)
+        client.fail(1002)
+        self.assertConnectionFailing(client, 1002)
+        client.receive_data(b"\x88\x02\x03\xea")
+        self.assertFrameReceived(client, None)
+
+    def test_server_stops_processing_frames_after_fail(self):
+        server = Protocol(SERVER)
+        server.fail(1002)
+        self.assertConnectionFailing(server, 1002)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xea")
+        self.assertFrameReceived(server, None)
+
+
+class FragmentationTests(ProtocolTestCase):
+    """
+    Test message fragmentation.
+
+    See 5.4. Fragmentation in RFC 6544.
+
+    """
+
+    def test_client_send_ping_pong_in_fragmented_message(self):
+        client = Protocol(CLIENT)
+        client.send_text(b"Spam", fin=False)
+        self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False))
+        client.send_ping(b"Ping")
+        self.assertFrameSent(client, Frame(OP_PING, b"Ping"))
+        client.send_continuation(b"Ham", fin=False)
+        self.assertFrameSent(client, Frame(OP_CONT, b"Ham", fin=False))
+        client.send_pong(b"Pong")
+        self.assertFrameSent(client, Frame(OP_PONG, b"Pong"))
+        client.send_continuation(b"Eggs", fin=True)
+        self.assertFrameSent(client, Frame(OP_CONT, b"Eggs"))
+
+    def test_server_send_ping_pong_in_fragmented_message(self):
+        server = Protocol(SERVER)
+        server.send_text(b"Spam", fin=False)
+        self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False))
+        server.send_ping(b"Ping")
+        self.assertFrameSent(server, Frame(OP_PING, b"Ping"))
+        server.send_continuation(b"Ham", fin=False)
+        self.assertFrameSent(server, Frame(OP_CONT, b"Ham", fin=False))
+        server.send_pong(b"Pong")
+        self.assertFrameSent(server, Frame(OP_PONG, b"Pong"))
+        server.send_continuation(b"Eggs", fin=True)
+        self.assertFrameSent(server, Frame(OP_CONT, b"Eggs"))
+
+    def test_client_receive_ping_pong_in_fragmented_message(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x01\x04Spam")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, b"Spam", fin=False),
+        )
+        client.receive_data(b"\x89\x04Ping")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PING, b"Ping"),
+        )
+        self.assertFrameSent(
+            client,
+            Frame(OP_PONG, b"Ping"),
+        )
+        client.receive_data(b"\x00\x03Ham")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, b"Ham", fin=False),
+        )
+        client.receive_data(b"\x8a\x04Pong")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_PONG, b"Pong"),
+        )
+        client.receive_data(b"\x80\x04Eggs")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_CONT, b"Eggs"),
+        )
+
+    def test_server_receive_ping_pong_in_fragmented_message(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, b"Spam", fin=False),
+        )
+        server.receive_data(b"\x89\x84\x00\x00\x00\x00Ping")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PING, b"Ping"),
+        )
+        self.assertFrameSent(
+            server,
+            Frame(OP_PONG, b"Ping"),
+        )
+        server.receive_data(b"\x00\x83\x00\x00\x00\x00Ham")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, b"Ham", fin=False),
+        )
+        server.receive_data(b"\x8a\x84\x00\x00\x00\x00Pong")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_PONG, b"Pong"),
+        )
+        server.receive_data(b"\x80\x84\x00\x00\x00\x00Eggs")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_CONT, b"Eggs"),
+        )
+
+    def test_client_send_close_in_fragmented_message(self):
+        client = Protocol(CLIENT)
+        client.send_text(b"Spam", fin=False)
+        self.assertFrameSent(client, Frame(OP_TEXT, b"Spam", fin=False))
+        # The spec says: "An endpoint MUST be capable of handling control
+        # frames in the middle of a fragmented message." However, since the
+        # endpoint must not send a data frame after a close frame, a close
+        # frame can't be "in the middle" of a fragmented message.
+        with self.assertRaises(ProtocolError) as raised:
+            client.send_close(1001)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+        client.send_continuation(b"Eggs", fin=True)
+
+    def test_server_send_close_in_fragmented_message(self):
+        server = Protocol(CLIENT)
+        server.send_text(b"Spam", fin=False)
+        self.assertFrameSent(server, Frame(OP_TEXT, b"Spam", fin=False))
+        # The spec says: "An endpoint MUST be capable of handling control
+        # frames in the middle of a fragmented message." However, since the
+        # endpoint must not send a data frame after a close frame, a close
+        # frame can't be "in the middle" of a fragmented message.
+        with self.assertRaises(ProtocolError) as raised:
+            server.send_close(1000)
+        self.assertEqual(str(raised.exception), "expected a continuation frame")
+
+    def test_client_receive_close_in_fragmented_message(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x01\x04Spam")
+        self.assertFrameReceived(
+            client,
+            Frame(OP_TEXT, b"Spam", fin=False),
+        )
+        # The spec says: "An endpoint MUST be capable of handling control
+        # frames in the middle of a fragmented message." However, since the
+        # endpoint must not send a data frame after a close frame, a close
+        # frame can't be "in the middle" of a fragmented message.
+        client.receive_data(b"\x88\x02\x03\xe8")
+        self.assertIsInstance(client.parser_exc, ProtocolError)
+        self.assertEqual(str(client.parser_exc), "incomplete fragmented message")
+        self.assertConnectionFailing(client, 1002, "incomplete fragmented message")
+
+    def test_server_receive_close_in_fragmented_message(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x01\x84\x00\x00\x00\x00Spam")
+        self.assertFrameReceived(
+            server,
+            Frame(OP_TEXT, b"Spam", fin=False),
+        )
+        # The spec says: "An endpoint MUST be capable of handling control
+        # frames in the middle of a fragmented message." However, since the
+        # endpoint must not send a data frame after a close frame, a close
+        # frame can't be "in the middle" of a fragmented message.
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe9")
+        self.assertIsInstance(server.parser_exc, ProtocolError)
+        self.assertEqual(str(server.parser_exc), "incomplete fragmented message")
+        self.assertConnectionFailing(server, 1002, "incomplete fragmented message")
+
+
+class EOFTests(ProtocolTestCase):
+    """
+    Test half-closes on connection termination.
+
+    """
+
+    def test_client_receives_eof(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        self.assertConnectionClosing(client)
+        client.receive_eof()
+        self.assertIs(client.state, CLOSED)
+
+    def test_server_receives_eof(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertConnectionClosing(server)
+        server.receive_eof()
+        self.assertIs(server.state, CLOSED)
+
+    def test_client_receives_eof_between_frames(self):
+        client = Protocol(CLIENT)
+        client.receive_eof()
+        self.assertIsInstance(client.parser_exc, EOFError)
+        self.assertEqual(str(client.parser_exc), "unexpected end of stream")
+        self.assertIs(client.state, CLOSED)
+
+    def test_server_receives_eof_between_frames(self):
+        server = Protocol(SERVER)
+        server.receive_eof()
+        self.assertIsInstance(server.parser_exc, EOFError)
+        self.assertEqual(str(server.parser_exc), "unexpected end of stream")
+        self.assertIs(server.state, CLOSED)
+
+    def test_client_receives_eof_inside_frame(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x81")
+        client.receive_eof()
+        self.assertIsInstance(client.parser_exc, EOFError)
+        self.assertEqual(
+            str(client.parser_exc),
+            "stream ends after 1 bytes, expected 2 bytes",
+        )
+        self.assertIs(client.state, CLOSED)
+
+    def test_server_receives_eof_inside_frame(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x81")
+        server.receive_eof()
+        self.assertIsInstance(server.parser_exc, EOFError)
+        self.assertEqual(
+            str(server.parser_exc),
+            "stream ends after 1 bytes, expected 2 bytes",
+        )
+        self.assertIs(server.state, CLOSED)
+
+    def test_client_receives_data_after_exception(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(client, 1002, "invalid opcode")
+        client.receive_data(b"\x00\x00")
+        self.assertFrameSent(client, None)
+
+    def test_server_receives_data_after_exception(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(server, 1002, "invalid opcode")
+        server.receive_data(b"\x00\x00")
+        self.assertFrameSent(server, None)
+
+    def test_client_receives_eof_after_exception(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(client, 1002, "invalid opcode")
+        client.receive_eof()
+        self.assertFrameSent(client, None, eof=True)
+
+    def test_server_receives_eof_after_exception(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(server, 1002, "invalid opcode")
+        server.receive_eof()
+        self.assertFrameSent(server, None)
+
+    def test_client_receives_data_and_eof_after_exception(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(client, 1002, "invalid opcode")
+        client.receive_data(b"\x00\x00")
+        client.receive_eof()
+        self.assertFrameSent(client, None, eof=True)
+
+    def test_server_receives_data_and_eof_after_exception(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\xff\xff")
+        self.assertConnectionFailing(server, 1002, "invalid opcode")
+        server.receive_data(b"\x00\x00")
+        server.receive_eof()
+        self.assertFrameSent(server, None)
+
+    def test_client_receives_data_after_eof(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        self.assertConnectionClosing(client)
+        client.receive_eof()
+        with self.assertRaises(EOFError) as raised:
+            client.receive_data(b"\x88\x00")
+        self.assertEqual(str(raised.exception), "stream ended")
+
+    def test_server_receives_data_after_eof(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertConnectionClosing(server)
+        server.receive_eof()
+        with self.assertRaises(EOFError) as raised:
+            server.receive_data(b"\x88\x80\x00\x00\x00\x00")
+        self.assertEqual(str(raised.exception), "stream ended")
+
+    def test_client_receives_eof_after_eof(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        self.assertConnectionClosing(client)
+        client.receive_eof()
+        with self.assertRaises(EOFError) as raised:
+            client.receive_eof()
+        self.assertEqual(str(raised.exception), "stream ended")
+
+    def test_server_receives_eof_after_eof(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertConnectionClosing(server)
+        server.receive_eof()
+        with self.assertRaises(EOFError) as raised:
+            server.receive_eof()
+        self.assertEqual(str(raised.exception), "stream ended")
+
+
+class TCPCloseTests(ProtocolTestCase):
+    """
+    Test expectation of TCP close on connection termination.
+
+    """
+
+    def test_client_default(self):
+        client = Protocol(CLIENT)
+        self.assertFalse(client.close_expected())
+
+    def test_server_default(self):
+        server = Protocol(SERVER)
+        self.assertFalse(server.close_expected())
+
+    def test_client_sends_close(self):
+        client = Protocol(CLIENT)
+        client.send_close()
+        self.assertTrue(client.close_expected())
+
+    def test_server_sends_close(self):
+        server = Protocol(SERVER)
+        server.send_close()
+        self.assertTrue(server.close_expected())
+
+    def test_client_receives_close(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        self.assertTrue(client.close_expected())
+
+    def test_client_receives_close_then_eof(self):
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x00")
+        client.receive_eof()
+        self.assertFalse(client.close_expected())
+
+    def test_server_receives_close_then_eof(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        server.receive_eof()
+        self.assertFalse(server.close_expected())
+
+    def test_server_receives_close(self):
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x80\x3c\x3c\x3c\x3c")
+        self.assertTrue(server.close_expected())
+
+    def test_client_fails_connection(self):
+        client = Protocol(CLIENT)
+        client.fail(1002)
+        self.assertTrue(client.close_expected())
+
+    def test_server_fails_connection(self):
+        server = Protocol(SERVER)
+        server.fail(1002)
+        self.assertTrue(server.close_expected())
+
+
+class ConnectionClosedTests(ProtocolTestCase):
+    """
+    Test connection closed exception.
+
+    """
+
+    def test_client_sends_close_then_receives_close(self):
+        # Client-initiated close handshake on the client side complete.
+        client = Protocol(CLIENT)
+        client.send_close(1000, "")
+        client.receive_data(b"\x88\x02\x03\xe8")
+        client.receive_eof()
+        exc = client.close_exc
+        self.assertIsInstance(exc, ConnectionClosedOK)
+        self.assertEqual(exc.rcvd, Close(1000, ""))
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertFalse(exc.rcvd_then_sent)
+
+    def test_server_sends_close_then_receives_close(self):
+        # Server-initiated close handshake on the server side complete.
+        server = Protocol(SERVER)
+        server.send_close(1000, "")
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8")
+        server.receive_eof()
+        exc = server.close_exc
+        self.assertIsInstance(exc, ConnectionClosedOK)
+        self.assertEqual(exc.rcvd, Close(1000, ""))
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertFalse(exc.rcvd_then_sent)
+
+    def test_client_receives_close_then_sends_close(self):
+        # Server-initiated close handshake on the client side complete.
+        client = Protocol(CLIENT)
+        client.receive_data(b"\x88\x02\x03\xe8")
+        client.receive_eof()
+        exc = client.close_exc
+        self.assertIsInstance(exc, ConnectionClosedOK)
+        self.assertEqual(exc.rcvd, Close(1000, ""))
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertTrue(exc.rcvd_then_sent)
+
+    def test_server_receives_close_then_sends_close(self):
+        # Client-initiated close handshake on the server side complete.
+        server = Protocol(SERVER)
+        server.receive_data(b"\x88\x82\x00\x00\x00\x00\x03\xe8")
+        server.receive_eof()
+        exc = server.close_exc
+        self.assertIsInstance(exc, ConnectionClosedOK)
+        self.assertEqual(exc.rcvd, Close(1000, ""))
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertTrue(exc.rcvd_then_sent)
+
+    def test_client_sends_close_then_receives_eof(self):
+        # Client-initiated close handshake on the client side times out.
+        client = Protocol(CLIENT)
+        client.send_close(1000, "")
+        client.receive_eof()
+        exc = client.close_exc
+        self.assertIsInstance(exc, ConnectionClosedError)
+        self.assertIsNone(exc.rcvd)
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertIsNone(exc.rcvd_then_sent)
+
+    def test_server_sends_close_then_receives_eof(self):
+        # Server-initiated close handshake on the server side times out.
+        server = Protocol(SERVER)
+        server.send_close(1000, "")
+        server.receive_eof()
+        exc = server.close_exc
+        self.assertIsInstance(exc, ConnectionClosedError)
+        self.assertIsNone(exc.rcvd)
+        self.assertEqual(exc.sent, Close(1000, ""))
+        self.assertIsNone(exc.rcvd_then_sent)
+
+    def test_client_receives_eof(self):
+        # Server-initiated close handshake on the client side times out.
+        client = Protocol(CLIENT)
+        client.receive_eof()
+        exc = client.close_exc
+        self.assertIsInstance(exc, ConnectionClosedError)
+        self.assertIsNone(exc.rcvd)
+        self.assertIsNone(exc.sent)
+        self.assertIsNone(exc.rcvd_then_sent)
+
+    def test_server_receives_eof(self):
+        # Client-initiated close handshake on the server side times out.
+        server = Protocol(SERVER)
+        server.receive_eof()
+        exc = server.close_exc
+        self.assertIsInstance(exc, ConnectionClosedError)
+        self.assertIsNone(exc.rcvd)
+        self.assertIsNone(exc.sent)
+        self.assertIsNone(exc.rcvd_then_sent)
+
+
+class ErrorTests(ProtocolTestCase):
+    """
+    Test other error cases.
+
+    """
+
+    def test_client_hits_internal_error_reading_frame(self):
+        client = Protocol(CLIENT)
+        # This isn't supposed to happen, so we're simulating it.
+        with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")):
+            client.receive_data(b"\x81\x00")
+            self.assertIsInstance(client.parser_exc, RuntimeError)
+            self.assertEqual(str(client.parser_exc), "BOOM")
+        self.assertConnectionFailing(client, 1011, "")
+
+    def test_server_hits_internal_error_reading_frame(self):
+        server = Protocol(SERVER)
+        # This isn't supposed to happen, so we're simulating it.
+        with unittest.mock.patch("struct.unpack", side_effect=RuntimeError("BOOM")):
+            server.receive_data(b"\x81\x80\x00\x00\x00\x00")
+            self.assertIsInstance(server.parser_exc, RuntimeError)
+            self.assertEqual(str(server.parser_exc), "BOOM")
+        self.assertConnectionFailing(server, 1011, "")
+
+
+class ExtensionsTests(ProtocolTestCase):
+    """
+    Test how extensions affect frames.
+
+    """
+
+    def test_client_extension_encodes_frame(self):
+        client = Protocol(CLIENT)
+        client.extensions = [Rsv2Extension()]
+        with self.enforce_mask(b"\x00\x44\x88\xcc"):
+            client.send_ping(b"")
+        self.assertEqual(client.data_to_send(), [b"\xa9\x80\x00\x44\x88\xcc"])
+
+    def test_server_extension_encodes_frame(self):
+        server = Protocol(SERVER)
+        server.extensions = [Rsv2Extension()]
+        server.send_ping(b"")
+        self.assertEqual(server.data_to_send(), [b"\xa9\x00"])
+
+    def test_client_extension_decodes_frame(self):
+        client = Protocol(CLIENT)
+        client.extensions = [Rsv2Extension()]
+        client.receive_data(b"\xaa\x00")
+        self.assertEqual(client.events_received(), [Frame(OP_PONG, b"")])
+
+    def test_server_extension_decodes_frame(self):
+        server = Protocol(SERVER)
+        server.extensions = [Rsv2Extension()]
+        server.receive_data(b"\xaa\x80\x00\x44\x88\xcc")
+        self.assertEqual(server.events_received(), [Frame(OP_PONG, b"")])
+
+
+class MiscTests(unittest.TestCase):
+    def test_client_default_logger(self):
+        client = Protocol(CLIENT)
+        logger = logging.getLogger("websockets.client")
+        self.assertIs(client.logger, logger)
+
+    def test_server_default_logger(self):
+        server = Protocol(SERVER)
+        logger = logging.getLogger("websockets.server")
+        self.assertIs(server.logger, logger)
+
+    def test_client_custom_logger(self):
+        logger = logging.getLogger("test")
+        client = Protocol(CLIENT, logger=logger)
+        self.assertIs(client.logger, logger)
+
+    def test_server_custom_logger(self):
+        logger = logging.getLogger("test")
+        server = Protocol(SERVER, logger=logger)
+        self.assertIs(server.logger, logger)
diff --git a/tests/test_server.py b/tests/test_server.py
index f140449..ba7e05d 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -3,11 +3,11 @@ import logging
 import unittest
 import unittest.mock
 
-from websockets.connection import CONNECTING, OPEN
 from websockets.datastructures import Headers
 from websockets.exceptions import InvalidHeader, InvalidOrigin, InvalidUpgrade
 from websockets.frames import OP_TEXT, Frame
 from websockets.http11 import Request, Response
+from websockets.protocol import CONNECTING, OPEN
 from websockets.server import *
 
 from .extensions.utils import (
@@ -17,12 +17,12 @@ from .extensions.utils import (
     ServerRsv2ExtensionFactory,
 )
 from .test_utils import ACCEPT, KEY
-from .utils import DATE
+from .utils import DATE, DeprecationTestCase
 
 
 class ConnectTests(unittest.TestCase):
     def test_receive_connect(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         server.receive_data(
             (
                 f"GET /test HTTP/1.1\r\n"
@@ -40,7 +40,7 @@ class ConnectTests(unittest.TestCase):
         self.assertFalse(server.close_expected())
 
     def test_connect_request(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         server.receive_data(
             (
                 f"GET /test HTTP/1.1\r\n"
@@ -84,7 +84,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_send_accept(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         with unittest.mock.patch("email.utils.formatdate", return_value=DATE):
             response = server.accept(self.make_request())
         self.assertIsInstance(response, Response)
@@ -104,7 +104,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.state, OPEN)
 
     def test_send_reject(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         with unittest.mock.patch("email.utils.formatdate", return_value=DATE):
             response = server.reject(http.HTTPStatus.NOT_FOUND, "Sorry folks.\n")
         self.assertIsInstance(response, Response)
@@ -126,7 +126,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.state, CONNECTING)
 
     def test_accept_response(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         with unittest.mock.patch("email.utils.formatdate", return_value=DATE):
             response = server.accept(self.make_request())
         self.assertIsInstance(response, Response)
@@ -146,7 +146,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(response.body)
 
     def test_reject_response(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         with unittest.mock.patch("email.utils.formatdate", return_value=DATE):
             response = server.reject(http.HTTPStatus.NOT_FOUND, "Sorry folks.\n")
         self.assertIsInstance(response, Response)
@@ -166,17 +166,17 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(response.body, b"Sorry folks.\n")
 
     def test_basic(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         response = server.accept(request)
 
         self.assertEqual(response.status_code, 101)
 
     def test_unexpected_exception(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         with unittest.mock.patch(
-            "websockets.server.ServerConnection.process_request",
+            "websockets.server.ServerProtocol.process_request",
             side_effect=Exception("BOOM"),
         ):
             response = server.accept(request)
@@ -187,7 +187,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "BOOM")
 
     def test_missing_connection(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Connection"]
         response = server.accept(request)
@@ -199,7 +199,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Connection header")
 
     def test_invalid_connection(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Connection"]
         request.headers["Connection"] = "close"
@@ -212,7 +212,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "invalid Connection header: close")
 
     def test_missing_upgrade(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Upgrade"]
         response = server.accept(request)
@@ -224,7 +224,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Upgrade header")
 
     def test_invalid_upgrade(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Upgrade"]
         request.headers["Upgrade"] = "h2c"
@@ -237,7 +237,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "invalid Upgrade header: h2c")
 
     def test_missing_key(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Sec-WebSocket-Key"]
         response = server.accept(request)
@@ -248,7 +248,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Key header")
 
     def test_multiple_key(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         request.headers["Sec-WebSocket-Key"] = KEY
         response = server.accept(request)
@@ -263,7 +263,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_invalid_key(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Sec-WebSocket-Key"]
         request.headers["Sec-WebSocket-Key"] = "not Base64 data!"
@@ -277,7 +277,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_truncated_key(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Sec-WebSocket-Key"]
         request.headers["Sec-WebSocket-Key"] = KEY[
@@ -293,7 +293,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_missing_version(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Sec-WebSocket-Version"]
         response = server.accept(request)
@@ -304,7 +304,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Sec-WebSocket-Version header")
 
     def test_multiple_version(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         request.headers["Sec-WebSocket-Version"] = "11"
         response = server.accept(request)
@@ -319,7 +319,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_invalid_version(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         del request.headers["Sec-WebSocket-Version"]
         request.headers["Sec-WebSocket-Version"] = "11"
@@ -333,7 +333,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_no_origin(self):
-        server = ServerConnection(origins=["https://example.com"])
+        server = ServerProtocol(origins=["https://example.com"])
         request = self.make_request()
         response = server.accept(request)
 
@@ -343,7 +343,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(str(raised.exception), "missing Origin header")
 
     def test_origin(self):
-        server = ServerConnection(origins=["https://example.com"])
+        server = ServerProtocol(origins=["https://example.com"])
         request = self.make_request()
         request.headers["Origin"] = "https://example.com"
         response = server.accept(request)
@@ -352,7 +352,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.origin, "https://example.com")
 
     def test_unexpected_origin(self):
-        server = ServerConnection(origins=["https://example.com"])
+        server = ServerProtocol(origins=["https://example.com"])
         request = self.make_request()
         request.headers["Origin"] = "https://other.example.com"
         response = server.accept(request)
@@ -365,7 +365,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_multiple_origin(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             origins=["https://example.com", "https://other.example.com"]
         )
         request = self.make_request()
@@ -384,7 +384,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_supported_origin(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             origins=["https://example.com", "https://other.example.com"]
         )
         request = self.make_request()
@@ -395,7 +395,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.origin, "https://other.example.com")
 
     def test_unsupported_origin(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             origins=["https://example.com", "https://other.example.com"]
         )
         request = self.make_request()
@@ -410,7 +410,7 @@ class AcceptRejectTests(unittest.TestCase):
         )
 
     def test_no_origin_accepted(self):
-        server = ServerConnection(origins=[None])
+        server = ServerProtocol(origins=[None])
         request = self.make_request()
         response = server.accept(request)
 
@@ -418,7 +418,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(server.origin)
 
     def test_no_extensions(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         response = server.accept(request)
 
@@ -427,7 +427,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [])
 
     def test_no_extension(self):
-        server = ServerConnection(extensions=[ServerOpExtensionFactory()])
+        server = ServerProtocol(extensions=[ServerOpExtensionFactory()])
         request = self.make_request()
         response = server.accept(request)
 
@@ -436,7 +436,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [])
 
     def test_extension(self):
-        server = ServerConnection(extensions=[ServerOpExtensionFactory()])
+        server = ServerProtocol(extensions=[ServerOpExtensionFactory()])
         request = self.make_request()
         request.headers["Sec-WebSocket-Extensions"] = "x-op; op"
         response = server.accept(request)
@@ -446,7 +446,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [OpExtension()])
 
     def test_unexpected_extension(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         request.headers["Sec-WebSocket-Extensions"] = "x-op; op"
         response = server.accept(request)
@@ -456,7 +456,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [])
 
     def test_unsupported_extension(self):
-        server = ServerConnection(extensions=[ServerRsv2ExtensionFactory()])
+        server = ServerProtocol(extensions=[ServerRsv2ExtensionFactory()])
         request = self.make_request()
         request.headers["Sec-WebSocket-Extensions"] = "x-op; op"
         response = server.accept(request)
@@ -466,7 +466,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [])
 
     def test_supported_extension_parameters(self):
-        server = ServerConnection(extensions=[ServerOpExtensionFactory("this")])
+        server = ServerProtocol(extensions=[ServerOpExtensionFactory("this")])
         request = self.make_request()
         request.headers["Sec-WebSocket-Extensions"] = "x-op; op=this"
         response = server.accept(request)
@@ -476,7 +476,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [OpExtension("this")])
 
     def test_unsupported_extension_parameters(self):
-        server = ServerConnection(extensions=[ServerOpExtensionFactory("this")])
+        server = ServerProtocol(extensions=[ServerOpExtensionFactory("this")])
         request = self.make_request()
         request.headers["Sec-WebSocket-Extensions"] = "x-op; op=that"
         response = server.accept(request)
@@ -486,7 +486,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [])
 
     def test_multiple_supported_extension_parameters(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             extensions=[
                 ServerOpExtensionFactory("this"),
                 ServerOpExtensionFactory("that"),
@@ -501,7 +501,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [OpExtension("that")])
 
     def test_multiple_extensions(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             extensions=[ServerOpExtensionFactory(), ServerRsv2ExtensionFactory()]
         )
         request = self.make_request()
@@ -516,7 +516,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [OpExtension(), Rsv2Extension()])
 
     def test_multiple_extensions_order(self):
-        server = ServerConnection(
+        server = ServerProtocol(
             extensions=[ServerOpExtensionFactory(), ServerRsv2ExtensionFactory()]
         )
         request = self.make_request()
@@ -531,7 +531,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.extensions, [Rsv2Extension(), OpExtension()])
 
     def test_no_subprotocols(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         response = server.accept(request)
 
@@ -540,7 +540,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(server.subprotocol)
 
     def test_no_subprotocol(self):
-        server = ServerConnection(subprotocols=["chat"])
+        server = ServerProtocol(subprotocols=["chat"])
         request = self.make_request()
         response = server.accept(request)
 
@@ -549,7 +549,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(server.subprotocol)
 
     def test_subprotocol(self):
-        server = ServerConnection(subprotocols=["chat"])
+        server = ServerProtocol(subprotocols=["chat"])
         request = self.make_request()
         request.headers["Sec-WebSocket-Protocol"] = "chat"
         response = server.accept(request)
@@ -559,7 +559,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.subprotocol, "chat")
 
     def test_unexpected_subprotocol(self):
-        server = ServerConnection()
+        server = ServerProtocol()
         request = self.make_request()
         request.headers["Sec-WebSocket-Protocol"] = "chat"
         response = server.accept(request)
@@ -569,7 +569,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertIsNone(server.subprotocol)
 
     def test_multiple_subprotocols(self):
-        server = ServerConnection(subprotocols=["superchat", "chat"])
+        server = ServerProtocol(subprotocols=["superchat", "chat"])
         request = self.make_request()
         request.headers["Sec-WebSocket-Protocol"] = "superchat"
         request.headers["Sec-WebSocket-Protocol"] = "chat"
@@ -580,7 +580,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.subprotocol, "superchat")
 
     def test_supported_subprotocol(self):
-        server = ServerConnection(subprotocols=["superchat", "chat"])
+        server = ServerProtocol(subprotocols=["superchat", "chat"])
         request = self.make_request()
         request.headers["Sec-WebSocket-Protocol"] = "chat"
         response = server.accept(request)
@@ -590,7 +590,7 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertEqual(server.subprotocol, "chat")
 
     def test_unsupported_subprotocol(self):
-        server = ServerConnection(subprotocols=["superchat", "chat"])
+        server = ServerProtocol(subprotocols=["superchat", "chat"])
         request = self.make_request()
         request.headers["Sec-WebSocket-Protocol"] = "otherchat"
         response = server.accept(request)
@@ -602,7 +602,7 @@ class AcceptRejectTests(unittest.TestCase):
 
 class MiscTests(unittest.TestCase):
     def test_bypass_handshake(self):
-        server = ServerConnection(state=OPEN)
+        server = ServerProtocol(state=OPEN)
         server.receive_data(b"\x81\x86\x00\x00\x00\x00Hello!")
         [frame] = server.events_received()
         self.assertEqual(frame, Frame(OP_TEXT, b"Hello!"))
@@ -610,5 +610,17 @@ class MiscTests(unittest.TestCase):
     def test_custom_logger(self):
         logger = logging.getLogger("test")
         with self.assertLogs("test", logging.DEBUG) as logs:
-            ServerConnection(logger=logger)
+            ServerProtocol(logger=logger)
         self.assertEqual(len(logs.records), 1)
+
+
+class BackwardsCompatibilityTests(DeprecationTestCase):
+    def test_server_connection_class(self):
+        with self.assertDeprecationWarning(
+            "ServerConnection was renamed to ServerProtocol"
+        ):
+            from websockets.server import ServerConnection
+
+            server = ServerConnection("ws://localhost/")
+
+        self.assertIsInstance(server, ServerProtocol)
diff --git a/tests/utils.py b/tests/utils.py
index ac891a0..92c7548 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,11 +1,18 @@
+import contextlib
 import email.utils
 import unittest
+import warnings
 
 
 DATE = email.utils.formatdate(usegmt=True)
 
 
 class GeneratorTestCase(unittest.TestCase):
+    """
+    Base class for testing generator-based coroutines.
+
+    """
+
     def assertGeneratorRunning(self, gen):
         """
         Check that a generator-based coroutine hasn't completed yet.
@@ -21,3 +28,25 @@ class GeneratorTestCase(unittest.TestCase):
         with self.assertRaises(StopIteration) as raised:
             next(gen)
         return raised.exception.value
+
+
+class DeprecationTestCase(unittest.TestCase):
+    """
+    Base class for testing deprecations.
+
+    """
+
+    @contextlib.contextmanager
+    def assertDeprecationWarning(self, message):
+        """
+        Check that a deprecation warning was raised with the given message.
+
+        """
+        with warnings.catch_warnings(record=True) as recorded_warnings:
+            warnings.simplefilter("always")
+            yield
+
+        self.assertEqual(len(recorded_warnings), 1)
+        warning = recorded_warnings[0]
+        self.assertEqual(warning.category, DeprecationWarning)
+        self.assertEqual(str(warning.message), message)