There are several ways software running on the CPU on a PC can communicate with the rest of the hardware.
The simplest to understand are I/O ports. The software uses the OUT instruction to write 8, 16, or 32 bits at a time to a I/O port. In the same way, the software uses the IN instruction to read from a I/O port. The I/O ports live in a separate 16-bit address space, unrelated to the addresses space used by the main memory. Each piece of hardware which uses I/O ports has a range of I/O addresses; writing to and reading from each of these addresses has a different effect (and often reading from and writing to the same address has different effects too).
Another way is memory-mapped I/O, where part of the memory address space is mapped to a piece of hardware. Instead of going to the main memory, the reads and writes from and to that region of memory go to the corresponding hardware. What they mean depends on the hardware and on the region (it is not uncommon to have more than one memory-mapped I/O region in the same piece of hardware); reading and writing to it could have a special effect (as is common with I/O ports) or it could simply read and write directly to memory on the hardware (for instance, a video card's frame buffer).
On the other direction (the rest of the hardware talking to the software running on the CPU), there are also several ways. The simplest one would be to wait for the software to ask (either via an I/O port or memory-mapped I/O); used alone, this can be very inefficient.
Another way is for the hardware to write directly to the main memory. This is very efficient for transfering large amounts of data, and can be used in the other direction too (to read from the main memory). However, the software still has to know when the transfer is finished.
The last way is interrupts. The CPU receives an interrupt request, together with the interrupt number. The CPU interrupts (thus the name) what it was doing, and switches to another part of the code (which part depends on the interrupt number). Usually each hardware corresponds to a single interrupt number, but interrupt numbers are often shared by several interrupt sources. There are also special kinds of interrupt which do not have a number; for instance, the NMI.
For a practical example, let's take a simple network interface card. This network card has a set of registers, which can be acessed either via I/O ports or memory-mapped I/O. It can also read from and write to the main memory, and has an interrupt pin.
To send a packet to the network, the network card driver first writes the full packet to the memory, at an address aligned to a multiple of 4 bytes. It then writes to a couple of registers on the network interface card, telling it the memory address, the packet size, and some other information. The network card then reads the packet from the memory, sends it to the network, and signals the interrupt. The interrupt controller (a separate piece of hardware) sends the interrupt request to the CPU and tells it the interrupt number. In the interrupt handler, the driver reads a register from the card which tells it the interrupt was about a packet being sent, reads another register to find out which packet was sent, and knows it can now reuse the memory where it had written the packet.
To receive packets from the network, the network card driver allocates a block of memory to be used as storage for the packets, and writes several registers on the card telling it the memory address of the block of memory, its size, and some other information. When a packet is received from the network, the network card writes it to that block of memory, together with the packet's size and some other information. It updates some registers which tell both it and the driver how much free space is there in that block of memory, where the free space begins, and where the used space begins (the buffer is circular, so after getting to the end it rolls over to the beginning). Finally, it signals its interrupt. The driver will read the registers and the memory to obtain the packet(s), and will update the registers to tell the card that space is now free again.
There are other things the card can tell the driver by writing its registers and signalling an interrupt; for instance, that the network cable was unplugged, that there was a transmission error, and so on.