First of all, you should be aware of Ralf Brown's Interrupt List
You can use the INT 21h stdin functions without blocking -- you can use AH=0Bh to see whether a character is available.
However, the INT 21h stdin functions are designed for stream-of-char style input as in C stdin. The next step to a lower level would be the INT 16h keyboard functions. You would use either AH=11h/10h/12h or AH=01h/00h/02h (depending on whether your absurdly old hardware supports extended keystrokes or not (or if you need them in the first place)), to, respectively, see if a keystroke is available, get a keystroke (with scancode), and check the state of the modifier keys for the keystroke (shift/ctrl/alt). If you want to know how long a key is held down, just use AH=03h to set the repeat rate/delay as needed (although technically this doesn't distinguish between holding a key down and a user mashing at the exact repeat rate.
If you want to do anything more sophisticated, you can just replace the BIOS keyboard interrupt handler--this is DOS. Basically, what happens when a key is pressed or released is that the keyboard controller generates an interrupt, and the BIOS keyboard interrupt handler uses the ports you mentioned to retrieve and acknowledge the key press/release and then ultimately stores the keystroke into a buffer for you to retrieve via INT 16h (repeats are presumably handled by the timer interrupt). (Obviously, this assumes that are NOT using a USB keyboard.) Fortunately, I found a page
that gives you an example of replacing the keyboard interrupt handler (see Using a Low Level Keyboard Driver
and Writing the Low Level Keyboard Driver
on that page. In fact, the author's example creating an array in exactly the way you want. (For the record, my search term was "replace dos keyboard interrupt".)
Technically, none of this is really specific to x86 assembly--it's specific to using the IBM PC compatible architecture without a modern OS.