tl;dr; the X11 protocol has no idea of processes, signals or any kind of IPC; both the server and any of its clients may be on different machines and operating system, which may simply have no concept of processes at all (at the time the X11 protocol was designed, Lisp machines were still a thing). The X11 itself protocol may run on top of unix sockets, tcp/ip or be tunneled through ssh, etc.
There are 3 ways to "close" an X11 window:
ClientMessage event with the
WM_DELETE_WINDOW atom to that window. The application that created that window may act on it (eg.
xeyes will just exit; other may close that window and continue running) or may ignore it. This is what typically happens when you press the
X button or press some "standard" key combo like
Forcefully destroy that window with
XDestroyWindow. The app may not expect it and may ignore any
DestroyNotify event, and still try to do operations on that window as if it were still existing, which will cause it to receive an
XErrorEvent with its code set to
BadWindow. The default error handler from Xlib (set with
XSetErrorHandler) will print an error message and do a clean
exit(3) in that case.
XKillClient on that window, which will forcefully close the client which has created it (instead of a window you can use any X11 resource, eg. the
XID of a pixmap). This is what
xkill(1) does . This has nothing to do with
kill(1) and does not send any signal to any process. Unless the client has taken special measures (see
XSetCloseDownMode(3)) all the resources it has created (windows, pixmaps, graphic contexts) will be destroyed. Again, the default Xlib io error handler (set with
XSetIOErrorHandler) will print an error message and do a clean
exit(3) in this case, too. The difference from pt. 2. is that the handler set with
XSetIOErrorHandler cannot return.
Some apps quite annoyingly treat 2. and 3. as a "crash" (eg. firefox which will show its infamous "Well this is embarasssing" message on the next start, unless
browser.sessionstore.resume_from_crash is set to false).
Also, an app has no way to know if it was kicked out the server with
XKillClient or if the server itself has closed unexpectedly or crashed.
Another thing that it's worth rehashing is that the X11 protocol is cooperative by design; there are no barriers and checks between X11 clients; any client can kick any other client out with
XKillClient, destroy or resize its windows, grab the keyboard or mouse all to itself, set the override redirect flag to get the window manager out of the way, etc.
 unless you're using a reparenting wm, you should use
xkill -frame for it to work.