Topics to Cover
- netcat
- TCP socket (server).
- TCP socket (client).
- string ↔ bytes: encode() and decode()
- select()
Netcat
Netcat is a great network utility for reading from and writing to network
connections using the TCP and UDP protocol. The basic syntax is as follows:
nc [options] ip_address port
There are many options (see here
for more detail), but in this course, we will mainly use the following options:
-l: listen mode, for inbound connects
-u: UDP mode
-n: numeric-only IP addresses, no DNS
-v: verbose (typically used along with -n options)
TCP connection
Suppose Alice on a host with IP address 111.111.111.111 wants to connect to Bob
on another host with IP address 222.222.222.222.
In this case:
Alice plays the role of the client,
and Bob plays the role of the server.
The following diagram summarizes the above description:
Alice (111.111.111.111) Bob (222.222.222.222)
(step 1) nc -lnv 9000
(step 2) nc 222.222.222.222 9000
UDP connection
UDP connections can be established essentially in the same manner. But, we need
to add "u" option to tell the nc program that we are using the UDP protocol.
Alice (111.111.111.111) Bob (222.222.222.222)
(step 1) nc -ulnv 9000
(step 2) nc -u 222.222.222.222 9000
Activity 1
Bring up two terminals. Have a netcat chat between the two terminals. Try a TCP
connection, and then a UDP connection.
- In this case, the server is the localhost. So, you can use IP
address
127.0.0.1.
- For the server port number, you can use 9000 or a different port number
that you choose.
TCP server socket with Python: bind, listen, accept
#!/usr/bin/python3
# serv_simple.py
from socket import *
sock = socket()
sock.bind(("0.0.0.0",8000))
sock.listen()
(newsock, addr) = sock.accept()
print("connection from", addr)
data = newsock.recv(1024)
print(data)
s = input()
newsock.send(s.encode())
newsock.close()
|
For a server socket, you need to do the following.
- (socket) Create a socket by calling
socket().
- (bind) You need to call
bind() to bind the socket to a specific port so that the
clients can connect the server by using that port.
- (listen) Call
listen() to look for any connection attempt from a remote
client.
- (accept) Now, you can call
accept() to accept any connection request.
After the connection has been established, one can use send() and
recv() to exchange the data. You need to use the data of
bytes type.
One can close the socket by calling close().
|
Type conversion between string objects and bytes objects
Use encode() and decode() to perform to conversion.
>>> s = "hello world"
>>> type(s)
<class 'str'>
>>> b = s.encode()
>>> b
b'hello world'
>>> type(b)
<class 'bytes'>
>>> t = b.decode()
>>> t
'hello world'
>>> type(t)
<class 'str'>
Note: 0.0.0.0 vs 127.0.0.1
- 0.0.0.0.
IP address "0.0.0.0" indicates that the socket should listen on any IP
addresses available. This address is usually used with
bind(). For example, if the socket is bound to 0.0.0.0, and the
host have two network interfaces, then the socket will listen on both
interfaces. The netstat command will show as follows:
$ netstat -an | grep 8000
Proto Local Address Foreign Address State
tcp 0.0.0.0:8000 0.0.0.0:* LISTEN
- 127.0.0.1. IP address "127.0.0.1" means a local host. So, you don't know
the exact IP address of the host, but wants to connect to a socket on the
local host, you can just use the address "127.0.0.1".
The netstat command will show as follows:
$ netstat -an | grep 8000
Proto Local Address Foreign Address State
tcp 0.0.0.0:8000 0.0.0.0:* LISTEN
tcp 127.0.0.1:60874 127.0.0.1:8000 ESTABLISHED
tcp 127.0.0.1:8000 127.0.0.1:60874 ESTABLISHED
However, when the server has IP address 192.168.172.4 and the client has the IP
address 192.168.172.5, then the netstat on the server side will show as follows:
$ netstat -an | grep 8000
Proto Local Address Foreign Address State
tcp 0.0.0.0:8000 0.0.0.0:* LISTEN
tcp 192.168.172.4:8000 192.168.172.5:40728 ESTABLISHED
A sample run
~$ ./serv_simple.py
connection from ('127.0.0.1', 54214)
b'Hello!\n'
hello back
|
~$ nc 127.0.0.1 8000
Hello!
hello back
|
TCP client socket with Python: connecting to a server
Again, the syntax is quite similar to what you do in the C programming language.
- (socket) Create a socket by calling
socket().
- (connect) Connect to a server by calling
connect().
Like the server socket, after the connection has been established, one can use
send() and recv() to exchange the data. You need to
use the data of bytes type.
One can close the socket by calling close().
Note:
If it's not a server socket (i.e., if it's a client socket), you don't need to
call bind(), in which case the OS will choose a random port for
the client socket.
#!/usr/bin/python3
# conn_simple.py
from socket import *
sock = socket()
sock.connect(("127.0.0.1", 8000))
sock.send(b"Hello!\n")
data = sock.recv(1024)
print(data)
|
|
A sample run:
~$ nc -l 8000
Hello!
test
|
~$ ./conn_simple.py
b'test\n'
|
|
select()
Now, let's try to write a program that implements a simple 1:1 chatting.
Concurrent tasks --- use select()
For this, we need to perform the following tasks concurrently.
- Wait for the use input. If the user types in something, we need to send it
through the socket.
- Wait for the socket data. If the data comes in, we need to print it so
that the user can see it.
For this, we need to use select() function contained in the module
select.
stdin as a Socket
As you learned from the systems programming course, Linux treats every I/O as a
file each of which the unique file identifier. Similar concepts are applied here
-- that is, you can treat sys.stdin as a socket.
Code
#!/usr/bin/python3
# serv_tcp.py
import socket
import sys
import select
sock = socket.socket()
sock.bind(("0.0.0.0",8000))
sock.listen()
(newsock, addr) = sock.accept()
print("connection from", addr)
while True:
socklist = [newsock, sys.stdin] ## focus here!!!
(r_sockets, w_sockets, e_sockets) = select.select(socklist, [], [])
if newsock in r_sockets: # we have something to read from new sock!
data = newsock.recv(1024)
if not data: # no data means the connection has been closed.
break
print(data)
if sys.stdin in r_sockets: # we have something to read from sys.stdin!
s = input()
s += "\n"
newsock.send(s.encode())
newsock.close()
sock.close()
Note:
- Line 6:
import select was used.
- Line 15: The list for reading sockets was prepared.
- Line 16: Note how
select.select() is used.
- Line 20: If
recv() returns the empty bytes object, it means
that the connection has been close.
- Line 26:
input() takes away a new line character. So, we need
to add it manually.
Test the code
Test this program
- In one terminal, run
./serv_tcp.py.
- In the other terminal, run
nc 127.0.0.1 8000.
Activity 2
Create a UDP netcat program for the server part. To test your program, launch a
UDP client as follows:
nc -u 127.0.0.1 9000
Refer to the socket
manual page.
- You need to give an additional parameter when calling
socket() to create a UDP socket.
- You still need to bind the socket to the service specific port (in our
case, it's 9000).
- However, UDP doesn't need to have the 3-way handshake as in TCP. So, you
don't need to call
listen() or accept().
- For UDP,
sendto() and recvfrom() should be used.
- Use
select() appropriately.
- Have the program quit when the user types in "quit". Be sure to use
strip() function for the input string to get rid of whitespace.