Except socket error msg

Tutorial on how to code simple network servers and clients using low level Socket api in python.

Socket programming in Python

This is a quick guide/tutorial on socket programming in python. Socket programming python is very similar to C.

To summarise the basics, sockets are the fundamental «things» behind any kind of network communications done by your computer.

For example when you type www.google.com in your web browser, it opens a socket and connects to google.com to fetch the page and show it to you. Same with any chat client like gtalk or skype.

Any network communication goes through a socket.

In this tutorial we shall be programming Tcp sockets in python. You can also program udp sockets in python.

Coding a simple socket client

First we shall learn how to code a simple socket client in python. A client connects to a remote host using sockets and sends and receives some data.

1. Creating a socket

This first thing to do is create a socket. The socket.socket function does this.
Quick Example :

#Socket client example in python

import socket	#for sockets

#create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print 'Socket Created'

Function socket.socket creates a socket and returns a socket descriptor which can be used in other socket related functions

The above code will create a socket with the following properties …

Address Family : AF_INET (this is IP version 4 or IPv4)
Type : SOCK_STREAM (this means connection oriented TCP protocol)

Error handling

If any of the socket functions fail then python throws an exception called socket.error which must be caught.

#handling errors in python socket programs

import socket	#for sockets
import sys	#for exit

try:
	#create an AF_INET, STREAM socket (TCP)
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
	print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
	sys.exit();

print 'Socket Created'

Ok , so you have created a socket successfully. But what next ? Next we shall try to connect to some server using this socket. We can connect to www.google.com

Note

Apart from SOCK_STREAM type of sockets there is another type called SOCK_DGRAM which indicates the UDP protocol. This type of socket is non-connection socket. In this tutorial we shall stick to SOCK_STREAM or TCP sockets.

2. Connect to a Server

We connect to a remote server on a certain port number. So we need 2 things , IP address and port number to connect to. So you need to know the IP address of the remote server you are connecting to. Here we used the ip address of google.com as a sample.

First get the IP address of the remote host/url

Before connecting to a remote host, its ip address is needed. In python the getting the ip address is quite simple.

import socket	#for sockets
import sys	#for exit

try:
	#create an AF_INET, STREAM socket (TCP)
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
	print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
	sys.exit();

print 'Socket Created'

host = 'www.google.com'

try:
	remote_ip = socket.gethostbyname( host )

except socket.gaierror:
	#could not resolve
	print 'Hostname could not be resolved. Exiting'
	sys.exit()
	
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the ip address of the remote host/system, we can connect to ip on a certain ‘port’ using the connect function.

Quick example

import socket	#for sockets
import sys	#for exit

try:
	#create an AF_INET, STREAM socket (TCP)
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
	print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
	sys.exit();

print 'Socket Created'

host = 'www.google.com'
port = 80

try:
	remote_ip = socket.gethostbyname( host )

except socket.gaierror:
	#could not resolve
	print 'Hostname could not be resolved. Exiting'
	sys.exit()
	
print 'Ip address of ' + host + ' is ' + remote_ip

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program

$ python client.py
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a socket and then connects. Try connecting to a port different from port 80 and you should not be able to connect which indicates that the port is not open for connection. This logic can be used to build a port scanner.

OK, so we are now connected. Lets do the next thing , sending some data to the remote server.

Free Tip

The concept of «connections» apply to SOCK_STREAM/TCP type of sockets. Connection means a reliable «stream» of data such that there can be multiple such streams each having communication of its own. Think of this as a pipe which is not interfered by data from other pipes. Another important property of stream connections is that packets have an «order» or «sequence».

Other sockets like UDP , ICMP , ARP dont have a concept of «connection». These are non-connection based communication. Which means you keep sending or receiving packets from anybody and everybody.

3. Sending Data

Function sendall will simply send data.
Lets send some data to google.com

import socket	#for sockets
import sys	#for exit

try:
	#create an AF_INET, STREAM socket (TCP)
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
	print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
	sys.exit();

print 'Socket Created'

host = 'www.google.com'
port = 80

try:
	remote_ip = socket.gethostbyname( host )

except socket.gaierror:
	#could not resolve
	print 'Hostname could not be resolved. Exiting'
	sys.exit()
	
print 'Ip address of ' + host + ' is ' + remote_ip

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1rnrn"

try :
	#Set the whole string
	s.sendall(message)
except socket.error:
	#Send failed
	print 'Send failed'
	sys.exit()

print 'Message send successfully'

In the above example , we first connect to an ip address and then send the string message «GET / HTTP/1.1rnrn» to it. The message is actually an «http command» to fetch the mainpage of a website.

Now that we have send some data , its time to receive a reply from the server. So lets do it.

4. Receiving Data

Function recv is used to receive data on a socket. In the following example we shall send the same message as the last example and receive a reply from the server.

#Socket client example in python

import socket	#for sockets
import sys	#for exit

#create an INET, STREAMing socket
try:
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
	print 'Failed to create socket'
	sys.exit()
	
print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
	remote_ip = socket.gethostbyname( host )

except socket.gaierror:
	#could not resolve
	print 'Hostname could not be resolved. Exiting'
	sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1rnrn"

try :
	#Set the whole string
	s.sendall(message)
except socket.error:
	#Send failed
	print 'Send failed'
	sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Here is the output of the above code :

$ python client.py
Socket Created
Ip address of www.google.com is 74.125.236.81
Socket Connected to www.google.com on ip 74.125.236.81
Message send successfully
HTTP/1.1 302 Found
Location: http://www.google.co.in/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
Set-Cookie: PREF=ID=51f26964398d27b0:FF=0:TM=1343026094:LM=1343026094:S=pa0PqX9FCPvyhBHJ; expires=Wed, 23-Jul-2014 06:48:14 GMT; path=/; domain=.google.com

Google.com replied with the content of the page we requested. Quite simple!
Now that we have received our reply, its time to close the socket.

5. Close socket

Function close is used to close the socket.

s.close()

Thats it.

Lets Revise

So in the above example we learned how to :
1. Create a socket
2. Connect to remote server
3. Send some data
4. Receive a reply

Its useful to know that your web browser also does the same thing when you open www.google.com
This kind of socket activity represents a CLIENT. A client is a system that connects to a remote system to fetch data.

The other kind of socket activity is called a SERVER. A server is a system that uses sockets to receive incoming connections and provide them with data. It is just the opposite of Client. So www.google.com is a server and your web browser is a client. Or more technically www.google.com is a HTTP Server and your web browser is an HTTP client.

Now its time to do some server tasks using sockets.

Coding socket servers in Python

OK now onto server things. Servers basically do the following :

1. Open a socket
2. Bind to a address(and port).
3. Listen for incoming connections.
4. Accept connections
5. Read/Send

We have already learnt how to open a socket. So the next thing would be to bind it.

1. Bind a socket

Function bind can be used to bind a socket to a particular address and port. It needs a sockaddr_in structure similar to connect function.

Quick example

import socket
import sys

HOST = ''	# Symbolic name meaning all available interfaces
PORT = 8888	# Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()
	
print 'Socket bind complete'

Now that bind is done, its time to make the socket listen to connections. We bind a socket to a particular IP address and a certain port number. By doing this we ensure that all incoming data which is directed towards this port number is received by this application.

This makes it obvious that you cannot have 2 sockets bound to the same port. There are exceptions to this rule but we shall look into that in some other article.

2. Listen for incoming connections

After binding a socket to a port the next thing we need to do is listen for connections. For this we need to put the socket in listening mode. Function socket_listen is used to put the socket in listening mode. Just add the following line after bind.

s.listen(10)
print 'Socket now listening'

The parameter of the function listen is called backlog. It controls the number of incoming connections that are kept «waiting» if the program is already busy. So by specifying 10, it means that if 10 connections are already waiting to be processed, then the 11th connection request shall be rejected. This will be more clear after checking socket_accept.

Now comes the main part of accepting new connections.

3. Accept connection

Function socket_accept is used for this.

import socket
import sys

HOST = ''	# Symbolic name meaning all available interfaces
PORT = 8888	# Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()
	
print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#wait to accept a connection - blocking call
conn, addr = s.accept()

#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Output

Run the program. It should show

$ python server.py
Socket created
Socket bind complete
Socket now listening

So now this program is waiting for incoming connections on port 8888. Dont close this program , keep it running.
Now a client can connect to it on this port. We shall use the telnet client for testing this. Open a terminal and type

$ telnet localhost 8888

It will immediately show

$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.

And the server output will show

$ python server.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So we can see that the client connected to the server. Try the above steps till you get it working perfect.

We accepted an incoming connection but closed it immediately. This was not very productive. There are lots of things that can be done after an incoming connection is established. Afterall the connection was established for the purpose of communication. So lets reply to the client.

Function sendall can be used to send something to the socket of the incoming connection and the client should see it. Here is an example :

import socket
import sys

HOST = ''	# Symbolic name meaning all available interfaces
PORT = 8888	# Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()
	
print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#wait to accept a connection - blocking call
conn, addr = s.accept()

print 'Connected with ' + addr[0] + ':' + str(addr[1])

#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)

conn.close()
s.close()

Run the above code in 1 terminal. And connect to this server using telnet from another terminal and you should see this :

$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So the client(telnet) received a reply from server.

We can see that the connection is closed immediately after that simply because the server program ends after accepting and sending reply. A server like www.google.com is always up to accept incoming connections.

It means that a server is supposed to be running all the time. After all its a server meant to serve.

So we need to keep our server RUNNING non-stop. The simplest way to do this is to put the accept in a loop so that it can receive incoming connections all the time.

4. Live Server — accepting multiple connections

So a live server will be alive always. Lets code this up

import socket
import sys

HOST = ''	# Symbolic name meaning all available interfaces
PORT = 5000	# Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()
	
print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
	conn, addr = s.accept()
	print 'Connected with ' + addr[0] + ':' + str(addr[1])
	
	data = conn.recv(1024)
	reply = 'OK...' + data
	if not data: 
		break
	
	conn.sendall(reply)

conn.close()
s.close()

We havent done a lot there. Just put the socket_accept in a loop.

Now run the server program in 1 terminal , and open 3 other terminals.
From each of the 3 terminal do a telnet to the server port.

Each of the telnet terminal would show :

$ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

And the server terminal would show

$ python server.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now the server is running nonstop and the telnet terminals are also connected nonstop. Now close the server program. All telnet terminals would show «Connection closed by foreign host.»

Good so far. But still the above code does not establish an effective communication channel between the server and the client.

The server program accepts connections in a loop and just send them a reply, after that it does nothing with them. Also it is not able to handle more than 1 connection at a time.

So now its time to handle the connections, and handle multiple connections together.

5. Handling Multiple Connections

To handle every connection we need a separate handling code to run along with the main server accepting connections. One way to achieve this is using threads.

The main server program accepts a connection and creates a new thread to handle communication for the connection, and then the server goes back to accept more connections.

We shall now use threads to create handlers for each connection the server accepts.

import socket
import sys
from thread import *

HOST = ''	# Symbolic name meaning all available interfaces
PORT = 8888	# Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
	s.bind((HOST, PORT))
except socket.error , msg:
	print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()
	
print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
	#Sending message to connected client
	conn.send('Welcome to the server. Type something and hit entern') #send only takes string
	
	#infinite loop so that function do not terminate and thread do not end.
	while True:
		
		#Receiving from client
		data = conn.recv(1024)
		reply = 'OK...' + data
		if not data: 
			break
	
		conn.sendall(reply)
	
	#came out of loop
	conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
	conn, addr = s.accept()
	print 'Connected with ' + addr[0] + ':' + str(addr[1])
	
	#start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
	start_new_thread(clientthread ,(conn,))

s.close()

Run the above server and open 3 terminals like before. Now the server will create a thread for each client connecting to it.

The telnet terminals would show :

$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal might look like this

$ python server.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

The above connection handler takes some input from the client and replies back with the same.

So now we have a server thats communicative. Thats useful now.

Conclusion

By now you must have learned the basics of socket programming in python. You can try out some experiments like writing a chat client or something similar.

When testing the code you might face this error

Bind failed. Error Code : 98 Message Address already in use

When it comes up, simply change the port number and the server would run fine.

If you think that the tutorial needs some addons or improvements or any of the code snippets above dont work then feel free to make a comment below so that it gets fixed.

This module provides access to the BSD socket interface. It is available on
all modern Unix systems, Windows, Mac OS X, BeOS, OS/2, and probably additional
platforms.

Note

Some behavior may be platform dependent, since calls are made to the operating
system socket APIs.

For an introduction to socket programming (in C), see the following papers: An
Introductory 4.3BSD Interprocess Communication Tutorial, by Stuart Sechrest and
An Advanced 4.3BSD Interprocess Communication Tutorial, by Samuel J. Leffler et
al, both in the UNIX Programmer’s Manual, Supplementary Documents 1 (sections
PS1:7 and PS1:8). The platform-specific reference material for the various
socket-related system calls are also a valuable source of information on the
details of socket semantics. For Unix, refer to the manual pages; for Windows,
see the WinSock (or Winsock 2) specification. For IPv6-ready APIs, readers may
want to refer to RFC 3493 titled Basic Socket Interface Extensions for IPv6.

The Python interface is a straightforward transliteration of the Unix system
call and library interface for sockets to Python’s object-oriented style: the
socket() function returns a socket object whose methods implement
the various socket system calls. Parameter types are somewhat higher-level than
in the C interface: as with read() and write() operations on Python
files, buffer allocation on receive operations is automatic, and buffer length
is implicit on send operations.

Socket addresses are represented as follows: A single string is used for the
AF_UNIX address family. A pair (host, port) is used for the
AF_INET address family, where host is a string representing either a
hostname in Internet domain notation like ‘daring.cwi.nl’ or an IPv4 address
like ‘100.50.200.5’, and port is an integral port number. For
AF_INET6 address family, a four-tuple (host, port, flowinfo,
scopeid) is used, where flowinfo and scopeid represents sin6_flowinfo
and sin6_scope_id member in struct sockaddr_in6 in C. For
socket module methods, flowinfo and scopeid can be omitted just for
backward compatibility. Note, however, omission of scopeid can cause problems
in manipulating scoped IPv6 addresses. Other address families are currently not
supported. The address format required by a particular socket object is
automatically selected based on the address family specified when the socket
object was created.

For IPv4 addresses, two special forms are accepted instead of a host address:
the empty string represents INADDR_ANY, and the string
‘<broadcast>’ represents INADDR_BROADCAST. The behavior is not
available for IPv6 for backward compatibility, therefore, you may want to avoid
these if you intend to support IPv6 with your Python programs.

If you use a hostname in the host portion of IPv4/v6 socket address, the
program may show a nondeterministic behavior, as Python uses the first address
returned from the DNS resolution. The socket address will be resolved
differently into an actual IPv4/v6 address, depending on the results from DNS
resolution and/or the host configuration. For deterministic behavior use a
numeric address in host portion.

New in version 2.5: AF_NETLINK sockets are represented as pairs pid, groups.

New in version 2.6: Linux-only support for TIPC is also available using the AF_TIPC
address family. TIPC is an open, non-IP based networked protocol designed
for use in clustered computer environments. Addresses are represented by a
tuple, and the fields depend on the address type. The general tuple form is
(addr_type, v1, v2, v3 [, scope]), where:

  • addr_type is one of TIPC_ADDR_NAMESEQ, TIPC_ADDR_NAME, or
    TIPC_ADDR_ID.

  • scope is one of TIPC_ZONE_SCOPE, TIPC_CLUSTER_SCOPE, and
    TIPC_NODE_SCOPE.

  • If addr_type is TIPC_ADDR_NAME, then v1 is the server type, v2 is
    the port identifier, and v3 should be 0.

    If addr_type is TIPC_ADDR_NAMESEQ, then v1 is the server type, v2
    is the lower port number, and v3 is the upper port number.

    If addr_type is TIPC_ADDR_ID, then v1 is the node, v2 is the
    reference, and v3 should be set to 0.

All errors raise exceptions. The normal exceptions for invalid argument types
and out-of-memory conditions can be raised; errors related to socket or address
semantics raise the error socket.error.

Non-blocking mode is supported through setblocking(). A
generalization of this based on timeouts is supported through
settimeout().

The module socket exports the following constants and functions:

exception socket.error¶

This exception is raised for socket-related errors. The accompanying value is
either a string telling what went wrong or a pair (errno, string)
representing an error returned by a system call, similar to the value
accompanying os.error. See the module errno, which contains names
for the error codes defined by the underlying operating system.

Changed in version 2.6: socket.error is now a child class of IOError.

exception socket.herror¶

This exception is raised for address-related errors, i.e. for functions that use
h_errno in the C API, including gethostbyname_ex() and
gethostbyaddr().

The accompanying value is a pair (h_errno, string) representing an error
returned by a library call. string represents the description of h_errno, as
returned by the hstrerror() C function.

exception socket.gaierror¶

This exception is raised for address-related errors, for getaddrinfo() and
getnameinfo(). The accompanying value is a pair (error, string)
representing an error returned by a library call. string represents the
description of error, as returned by the gai_strerror() C function. The
error value will match one of the EAI_* constants defined in this
module.

exception socket.timeout¶

This exception is raised when a timeout occurs on a socket which has had
timeouts enabled via a prior call to settimeout(). The accompanying value
is a string whose value is currently always “timed out”.

New in version 2.3.

socket.AF_UNIX¶
socket.AF_INET¶
socket.AF_INET6¶

These constants represent the address (and protocol) families, used for the
first argument to socket(). If the AF_UNIX constant is not
defined then this protocol is unsupported.

socket.SOCK_STREAM¶
socket.SOCK_DGRAM¶
socket.SOCK_RAW¶
socket.SOCK_RDM¶
socket.SOCK_SEQPACKET¶

These constants represent the socket types, used for the second argument to
socket(). (Only SOCK_STREAM and SOCK_DGRAM appear to be
generally useful.)

SO_*
socket.SOMAXCONN¶
MSG_*
SOL_*
IPPROTO_*
IPPORT_*
INADDR_*
IP_*
IPV6_*
EAI_*
AI_*
NI_*
TCP_*

Many constants of these forms, documented in the Unix documentation on sockets
and/or the IP protocol, are also defined in the socket module. They are
generally used in arguments to the setsockopt() and getsockopt()
methods of socket objects. In most cases, only those symbols that are defined
in the Unix header files are defined; for a few symbols, default values are
provided.

SIO_*
RCVALL_*

Constants for Windows’ WSAIoctl(). The constants are used as arguments to the
ioctl() method of socket objects.

New in version 2.6.

TIPC_*

TIPC related constants, matching the ones exported by the C socket API. See
the TIPC documentation for more information.

New in version 2.6.

socket.has_ipv6¶

This constant contains a boolean value which indicates if IPv6 is supported on
this platform.

New in version 2.3.

socket.create_connection(address[, timeout[, source_address]]

Convenience function. Connect to address (a 2-tuple (host, port)),
and return the socket object. Passing the optional timeout parameter will
set the timeout on the socket instance before attempting to connect. If no
timeout is supplied, the global default timeout setting returned by
getdefaulttimeout() is used.

If supplied, source_address must be a 2-tuple (host, port) for the
socket to bind to as its source address before connecting. If host or port
are ‘’ or 0 respectively the OS default behavior will be used.

New in version 2.6.

Changed in version 2.7: source_address was added.

socket.getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0

Translate the host/port argument into a sequence of 5-tuples that contain
all the necessary arguments for creating a socket connected to that service.
host is a domain name, a string representation of an IPv4/v6 address
or None. port is a string service name such as ‘http’, a numeric
port number or None. By passing None as the value of host
and port, you can pass NULL to the underlying C API.

The family, socktype and proto arguments can be optionally specified
in order to narrow the list of addresses returned. Passing zero as a
value for each of these arguments selects the full range of results.
The flags argument can be one or several of the AI_* constants,
and will influence how results are computed and returned.
For example, AI_NUMERICHOST will disable domain name resolution
and will raise an error if host is a domain name.

The function returns a list of 5-tuples with the following structure:

(family, socktype, proto, canonname, sockaddr)

In these tuples, family, socktype, proto are all integers and are
meant to be passed to the socket() function. canonname will be
a string representing the canonical name of the host if
AI_CANONNAME is part of the flags argument; else canonname
will be empty. sockaddr is a tuple describing a socket address, whose
format depends on the returned family (a (address, port) 2-tuple for
AF_INET, a (address, port, flow info, scope id) 4-tuple for
AF_INET6), and is meant to be passed to the socket.connect()
method.

The following example fetches address information for a hypothetical TCP
connection to www.python.org on port 80 (results may differ on your
system if IPv6 isn’t enabled):

>>> socket.getaddrinfo("www.python.org", 80, 0, 0, socket.SOL_TCP)
[(2, 1, 6, '', ('82.94.164.162', 80)),
 (10, 1, 6, '', ('2001:888:2000:d::a2', 80, 0, 0))]

New in version 2.2.

socket.getfqdn([name]

Return a fully qualified domain name for name. If name is omitted or empty,
it is interpreted as the local host. To find the fully qualified name, the
hostname returned by gethostbyaddr() is checked, followed by aliases for the
host, if available. The first name which includes a period is selected. In
case no fully qualified domain name is available, the hostname as returned by
gethostname() is returned.

New in version 2.0.

socket.gethostbyname(hostname

Translate a host name to IPv4 address format. The IPv4 address is returned as a
string, such as ‘100.50.200.5’. If the host name is an IPv4 address itself
it is returned unchanged. See gethostbyname_ex() for a more complete
interface. gethostbyname() does not support IPv6 name resolution, and
getaddrinfo() should be used instead for IPv4/v6 dual stack support.

socket.gethostbyname_ex(hostname

Translate a host name to IPv4 address format, extended interface. Return a
triple (hostname, aliaslist, ipaddrlist) where hostname is the primary
host name responding to the given ip_address, aliaslist is a (possibly
empty) list of alternative host names for the same address, and ipaddrlist is
a list of IPv4 addresses for the same interface on the same host (often but not
always a single address). gethostbyname_ex() does not support IPv6 name
resolution, and getaddrinfo() should be used instead for IPv4/v6 dual
stack support.

socket.gethostname()¶

Return a string containing the hostname of the machine where the Python
interpreter is currently executing.

If you want to know the current machine’s IP address, you may want to use
gethostbyname(gethostname()). This operation assumes that there is a
valid address-to-host mapping for the host, and the assumption does not
always hold.

Note: gethostname() doesn’t always return the fully qualified domain
name; use getfqdn() (see above).

socket.gethostbyaddr(ip_address

Return a triple (hostname, aliaslist, ipaddrlist) where hostname is the
primary host name responding to the given ip_address, aliaslist is a
(possibly empty) list of alternative host names for the same address, and
ipaddrlist is a list of IPv4/v6 addresses for the same interface on the same
host (most likely containing only a single address). To find the fully qualified
domain name, use the function getfqdn(). gethostbyaddr() supports
both IPv4 and IPv6.

socket.getnameinfo(sockaddr, flags

Translate a socket address sockaddr into a 2-tuple (host, port). Depending
on the settings of flags, the result can contain a fully-qualified domain name
or numeric address representation in host. Similarly, port can contain a
string port name or a numeric port number.

New in version 2.2.

socket.getprotobyname(protocolname

Translate an Internet protocol name (for example, ‘icmp’) to a constant
suitable for passing as the (optional) third argument to the socket()
function. This is usually only needed for sockets opened in “raw” mode
(SOCK_RAW); for the normal socket modes, the correct protocol is chosen
automatically if the protocol is omitted or zero.

socket.getservbyname(servicename[, protocolname]

Translate an Internet service name and protocol name to a port number for that
service. The optional protocol name, if given, should be ‘tcp’ or
‘udp’, otherwise any protocol will match.

socket.getservbyport(port[, protocolname]

Translate an Internet port number and protocol name to a service name for that
service. The optional protocol name, if given, should be ‘tcp’ or
‘udp’, otherwise any protocol will match.

socket.socket([family[, type[, proto]]]

Create a new socket using the given address family, socket type and protocol
number. The address family should be AF_INET (the default),
AF_INET6 or AF_UNIX. The socket type should be
SOCK_STREAM (the default), SOCK_DGRAM or perhaps one of the
other SOCK_ constants. The protocol number is usually zero and may be
omitted in that case.

socket.socketpair([family[, type[, proto]]]

Build a pair of connected socket objects using the given address family, socket
type, and protocol number. Address family, socket type, and protocol number are
as for the socket() function above. The default family is AF_UNIX
if defined on the platform; otherwise, the default is AF_INET.
Availability: Unix.

New in version 2.4.

socket.fromfd(fd, family, type[, proto]

Duplicate the file descriptor fd (an integer as returned by a file object’s
fileno() method) and build a socket object from the result. Address
family, socket type and protocol number are as for the socket() function
above. The file descriptor should refer to a socket, but this is not checked —
subsequent operations on the object may fail if the file descriptor is invalid.
This function is rarely needed, but can be used to get or set socket options on
a socket passed to a program as standard input or output (such as a server
started by the Unix inet daemon). The socket is assumed to be in blocking mode.
Availability: Unix.

socket.ntohl(x

Convert 32-bit positive integers from network to host byte order. On machines
where the host byte order is the same as network byte order, this is a no-op;
otherwise, it performs a 4-byte swap operation.

socket.ntohs(x

Convert 16-bit positive integers from network to host byte order. On machines
where the host byte order is the same as network byte order, this is a no-op;
otherwise, it performs a 2-byte swap operation.

socket.htonl(x

Convert 32-bit positive integers from host to network byte order. On machines
where the host byte order is the same as network byte order, this is a no-op;
otherwise, it performs a 4-byte swap operation.

socket.htons(x

Convert 16-bit positive integers from host to network byte order. On machines
where the host byte order is the same as network byte order, this is a no-op;
otherwise, it performs a 2-byte swap operation.

socket.inet_aton(ip_string

Convert an IPv4 address from dotted-quad string format (for example,
‘123.45.67.89’) to 32-bit packed binary format, as a string four characters in
length. This is useful when conversing with a program that uses the standard C
library and needs objects of type struct in_addr, which is the C type
for the 32-bit packed binary this function returns.

inet_aton() also accepts strings with less than three dots; see the
Unix manual page inet(3) for details.

If the IPv4 address string passed to this function is invalid,
socket.error will be raised. Note that exactly what is valid depends on
the underlying C implementation of inet_aton().

inet_aton() does not support IPv6, and inet_pton() should be used
instead for IPv4/v6 dual stack support.

socket.inet_ntoa(packed_ip

Convert a 32-bit packed IPv4 address (a string four characters in length) to its
standard dotted-quad string representation (for example, ‘123.45.67.89’). This
is useful when conversing with a program that uses the standard C library and
needs objects of type struct in_addr, which is the C type for the
32-bit packed binary data this function takes as an argument.

If the string passed to this function is not exactly 4 bytes in length,
socket.error will be raised. inet_ntoa() does not support IPv6, and
inet_ntop() should be used instead for IPv4/v6 dual stack support.

socket.inet_pton(address_family, ip_string

Convert an IP address from its family-specific string format to a packed, binary
format. inet_pton() is useful when a library or network protocol calls for
an object of type struct in_addr (similar to inet_aton()) or
struct in6_addr.

Supported values for address_family are currently AF_INET and
AF_INET6. If the IP address string ip_string is invalid,
socket.error will be raised. Note that exactly what is valid depends on
both the value of address_family and the underlying implementation of
inet_pton().

Availability: Unix (maybe not all platforms).

New in version 2.3.

socket.inet_ntop(address_family, packed_ip

Convert a packed IP address (a string of some number of characters) to its
standard, family-specific string representation (for example, ‘7.10.0.5’ or
‘5aef:2b::8’) inet_ntop() is useful when a library or network protocol
returns an object of type struct in_addr (similar to inet_ntoa())
or struct in6_addr.

Supported values for address_family are currently AF_INET and
AF_INET6. If the string packed_ip is not the correct length for the
specified address family, ValueError will be raised. A
socket.error is raised for errors from the call to inet_ntop().

Availability: Unix (maybe not all platforms).

New in version 2.3.

socket.getdefaulttimeout()¶

Return the default timeout in floating seconds for new socket objects. A value
of None indicates that new socket objects have no timeout. When the socket
module is first imported, the default is None.

New in version 2.3.

socket.setdefaulttimeout(timeout

Set the default timeout in floating seconds for new socket objects. A value of
None indicates that new socket objects have no timeout. When the socket
module is first imported, the default is None.

New in version 2.3.

socket.SocketType¶

This is a Python type object that represents the socket object type. It is the
same as type(socket(…)).

See also

Module SocketServer
Classes that simplify writing network servers.
Module ssl
A TLS/SSL wrapper for socket objects.

17.2.1. Socket Objects¶

Socket objects have the following methods. Except for makefile() these
correspond to Unix system calls applicable to sockets.

socket.accept()¶

Accept a connection. The socket must be bound to an address and listening for
connections. The return value is a pair (conn, address) where conn is a
new socket object usable to send and receive data on the connection, and
address is the address bound to the socket on the other end of the connection.

socket.bind(address

Bind the socket to address. The socket must not already be bound. (The format
of address depends on the address family — see above.)

Note

This method has historically accepted a pair of parameters for AF_INET
addresses instead of only a tuple. This was never intentional and is no longer
available in Python 2.0 and later.

socket.close()¶

Close the socket. All future operations on the socket object will fail. The
remote end will receive no more data (after queued data is flushed). Sockets are
automatically closed when they are garbage-collected.

socket.connect(address

Connect to a remote socket at address. (The format of address depends on the
address family — see above.)

Note

This method has historically accepted a pair of parameters for AF_INET
addresses instead of only a tuple. This was never intentional and is no longer
available in Python 2.0 and later.

socket.connect_ex(address

Like connect(address), but return an error indicator instead of raising an
exception for errors returned by the C-level connect() call (other
problems, such as “host not found,” can still raise exceptions). The error
indicator is 0 if the operation succeeded, otherwise the value of the
errno variable. This is useful to support, for example, asynchronous
connects.

Note

This method has historically accepted a pair of parameters for AF_INET
addresses instead of only a tuple. This was never intentional and is no longer
available in Python 2.0 and later.

socket.fileno()¶

Return the socket’s file descriptor (a small integer). This is useful with
select.select().

Under Windows the small integer returned by this method cannot be used where a
file descriptor can be used (such as os.fdopen()). Unix does not have
this limitation.

socket.getpeername()¶

Return the remote address to which the socket is connected. This is useful to
find out the port number of a remote IPv4/v6 socket, for instance. (The format
of the address returned depends on the address family — see above.) On some
systems this function is not supported.

socket.getsockname()¶

Return the socket’s own address. This is useful to find out the port number of
an IPv4/v6 socket, for instance. (The format of the address returned depends on
the address family — see above.)

socket.getsockopt(level, optname[, buflen]

Return the value of the given socket option (see the Unix man page
getsockopt(2)). The needed symbolic constants (SO_* etc.)
are defined in this module. If buflen is absent, an integer option is assumed
and its integer value is returned by the function. If buflen is present, it
specifies the maximum length of the buffer used to receive the option in, and
this buffer is returned as a string. It is up to the caller to decode the
contents of the buffer (see the optional built-in module struct for a way
to decode C structures encoded as strings).

socket.ioctl(control, option
Platform : Windows

The ioctl() method is a limited interface to the WSAIoctl system
interface. Please refer to the Win32 documentation for more
information.

On other platforms, the generic fcntl.fcntl() and fcntl.ioctl()
functions may be used; they accept a socket object as their first argument.

New in version 2.6.

socket.listen(backlog

Listen for connections made to the socket. The backlog argument specifies the
maximum number of queued connections and should be at least 0; the maximum value
is system-dependent (usually 5), the minimum value is forced to 0.

socket.makefile([mode[, bufsize]]

Return a file object associated with the socket. (File objects are
described in File Objects.) The file object
references a dup()ped version of the socket file descriptor, so the
file object and socket object may be closed or garbage-collected independently.
The socket must be in blocking mode (it can not have a timeout). The optional
mode and bufsize arguments are interpreted the same way as by the built-in
file() function.

Note

On Windows, the file-like object created by makefile() cannot be
used where a file object with a file descriptor is expected, such as the
stream arguments of subprocess.Popen().

socket.recv(bufsize[, flags]

Receive data from the socket. The return value is a string representing the
data received. The maximum amount of data to be received at once is specified
by bufsize. See the Unix manual page recv(2) for the meaning of
the optional argument flags; it defaults to zero.

Note

For best match with hardware and network realities, the value of bufsize
should be a relatively small power of 2, for example, 4096.

socket.recvfrom(bufsize[, flags]

Receive data from the socket. The return value is a pair (string, address)
where string is a string representing the data received and address is the
address of the socket sending the data. See the Unix manual page
recv(2) for the meaning of the optional argument flags; it defaults
to zero. (The format of address depends on the address family — see above.)

socket.recvfrom_into(buffer[, nbytes[, flags]]

Receive data from the socket, writing it into buffer instead of creating a
new string. The return value is a pair (nbytes, address) where nbytes is
the number of bytes received and address is the address of the socket sending
the data. See the Unix manual page recv(2) for the meaning of the
optional argument flags; it defaults to zero. (The format of address
depends on the address family — see above.)

New in version 2.5.

socket.recv_into(buffer[, nbytes[, flags]]

Receive up to nbytes bytes from the socket, storing the data into a buffer
rather than creating a new string. If nbytes is not specified (or 0),
receive up to the size available in the given buffer. Returns the number of
bytes received. See the Unix manual page recv(2) for the meaning
of the optional argument flags; it defaults to zero.

New in version 2.5.

socket.send(string[, flags]

Send data to the socket. The socket must be connected to a remote socket. The
optional flags argument has the same meaning as for recv() above.
Returns the number of bytes sent. Applications are responsible for checking that
all data has been sent; if only some of the data was transmitted, the
application needs to attempt delivery of the remaining data.

socket.sendall(string[, flags]

Send data to the socket. The socket must be connected to a remote socket. The
optional flags argument has the same meaning as for recv() above.
Unlike send(), this method continues to send data from string until
either all data has been sent or an error occurs. None is returned on
success. On error, an exception is raised, and there is no way to determine how
much data, if any, was successfully sent.

socket.sendto(string[, flags], address

Send data to the socket. The socket should not be connected to a remote socket,
since the destination socket is specified by address. The optional flags
argument has the same meaning as for recv() above. Return the number of
bytes sent. (The format of address depends on the address family — see
above.)

socket.setblocking(flag

Set blocking or non-blocking mode of the socket: if flag is 0, the socket is
set to non-blocking, else to blocking mode. Initially all sockets are in
blocking mode. In non-blocking mode, if a recv() call doesn’t find any
data, or if a send() call can’t immediately dispose of the data, a
error exception is raised; in blocking mode, the calls block until they
can proceed. s.setblocking(0) is equivalent to s.settimeout(0.0);
s.setblocking(1) is equivalent to s.settimeout(None).

socket.settimeout(value

Set a timeout on blocking socket operations. The value argument can be a
nonnegative float expressing seconds, or None. If a float is given,
subsequent socket operations will raise a timeout exception if the
timeout period value has elapsed before the operation has completed. Setting
a timeout of None disables timeouts on socket operations.
s.settimeout(0.0) is equivalent to s.setblocking(0);
s.settimeout(None) is equivalent to s.setblocking(1).

New in version 2.3.

socket.gettimeout()¶

Return the timeout in floating seconds associated with socket operations, or
None if no timeout is set. This reflects the last call to
setblocking() or settimeout().

New in version 2.3.

Some notes on socket blocking and timeouts: A socket object can be in one of
three modes: blocking, non-blocking, or timeout. Sockets are always created in
blocking mode. In blocking mode, operations block until complete or
the system returns an error (such as connection timed out). In
non-blocking mode, operations fail (with an error that is unfortunately
system-dependent) if they cannot be completed immediately. In timeout mode,
operations fail if they cannot be completed within the timeout specified for the
socket or if the system returns an error. The setblocking()
method is simply a shorthand for certain settimeout() calls.

Timeout mode internally sets the socket in non-blocking mode. The blocking and
timeout modes are shared between file descriptors and socket objects that refer
to the same network endpoint. A consequence of this is that file objects
returned by the makefile() method must only be used when the
socket is in blocking mode; in timeout or non-blocking mode file operations
that cannot be completed immediately will fail.

Note that the connect() operation is subject to the timeout
setting, and in general it is recommended to call settimeout()
before calling connect() or pass a timeout parameter to
create_connection(). The system network stack may return a connection
timeout error of its own regardless of any Python socket timeout setting.

socket.setsockopt(level, optname, value

Set the value of the given socket option (see the Unix manual page
setsockopt(2)). The needed symbolic constants are defined in the
socket module (SO_* etc.). The value can be an integer or a
string representing a buffer. In the latter case it is up to the caller to
ensure that the string contains the proper bits (see the optional built-in
module struct for a way to encode C structures as strings).

socket.shutdown(how

Shut down one or both halves of the connection. If how is SHUT_RD,
further receives are disallowed. If how is SHUT_WR, further sends
are disallowed. If how is SHUT_RDWR, further sends and receives are
disallowed. Depending on the platform, shutting down one half of the connection
can also close the opposite half (e.g. on Mac OS X, shutdown(SHUT_WR) does
not allow further reads on the other end of the connection).

Note that there are no methods read() or write(); use
recv() and send() without flags argument instead.

Socket objects also have these (read-only) attributes that correspond to the
values given to the socket constructor.

socket.family¶

The socket family.

New in version 2.5.

socket.type¶

The socket type.

New in version 2.5.

socket.proto¶

The socket protocol.

New in version 2.5.

17.2.2. Example¶

Here are four minimal example programs using the TCP/IP protocol: a server that
echoes all data that it receives back (servicing only one client), and a client
using it. Note that a server must perform the sequence socket(),
bind(), listen(), accept() (possibly
repeating the accept() to service more than one client), while a
client only needs the sequence socket(), connect(). Also
note that the server does not send()/recv() on the
socket it is listening on but on the new socket returned by
accept().

The first two examples support IPv4 only.

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()
# Echo client program
import socket

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

The next two examples are identical to the above two, but support both IPv4 and
IPv6. The server side will listen to the first address family available (it
should listen to both instead). On most of IPv6-ready systems, IPv6 will take
precedence and the server may not accept IPv4 traffic. The client side will try
to connect to the all addresses returned as a result of the name resolution, and
sends traffic to the first one connected successfully.

# Echo server program
import socket
import sys

HOST = None               # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
                              socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error, msg:
        s = None
        continue
    try:
        s.bind(sa)
        s.listen(1)
    except socket.error, msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()
# Echo client program
import socket
import sys

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error, msg:
        s = None
        continue
    try:
        s.connect(sa)
    except socket.error, msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

The last example shows how to write a very simple network sniffer with raw
sockets on Windows. The example requires administrator privileges to modify
the interface:

import socket

# the public network interface
HOST = socket.gethostbyname(socket.gethostname())

# create a raw socket and bind it to the public interface
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((HOST, 0))

# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# receive a package
print s.recvfrom(65565)

# disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

Основы сетевого программирования

35 мин на чтение

(52.704 символов)

Основные понятия сетевого программирования

Чем сетевые приложения отличаются от обычных?

alt_text

Сетевыми приложениями мы будем называть любые приложения, которые обмениваются данными, используя компьютерную сеть. Это довольно широкое определение, и, конечно, мы не сможем рассмотреть все многообразие обширного мира сетевых технологий, который, вдобавок развивается очень быстро и новые технологии, приемы и методики появляются чуть ли не каждый день. Поэтому в данном пособии мы сконцентрируемся на освоении базовых схем обмена информацией по сети, которые лежат в основе всех более продвинутых вещей. Используя полученные знания вы сами сможете строить все более и более сложные схемы взаимодействия разных приложений и разных компонентов одного приложения по сети.

Сетевые приложения могут обмениваться информацией с другими, сторонними приложениями либо строить взаимодействие по сети между компонентами одного и того же приложения, написанного одним автором или одной командой.

Возможность обмениваться данными по сети открывает перед разработчиком широкий круг возможностей.

Вы можете обращаться к сторонним сервисам, имеющим открытое API. Сегодня существует множество сервисов, постоянно действующих в сети и предоставляющих способ обмена данными в автоматизированном формате через специальную схему взаимодействия, то есть публичный интерфейс, или API. Например, ваша программа может обратиться к погодному сервису и получить данные о погоде в определенном месте или городе.

Также вы можете сами разработать такой публичный сервис. Если ваше приложение может выдавать информацию по запросу неограниченному кругу лиц, вы можете опубликовать в Интернете приложение, которое будет обрабатывать входящие соединения и отвечать на запросы. Нужно только спроектировать API вашего сервиса, реализовать и задокументировать его.

Можно строить распределенные приложения. Сейчас довольно распространены приложения, основой функционирования которых является взаимодействие множества компонентов, запущенных на совершенно независимых компьютерах сети. Так работают, например, пиринговые сети, системы распределенных вычислений или ботнеты.

Добавьте к любой вашей программе возможность самостоятельно обновляться по сети. Почти все программы имеют подсистему автоматической и регулярной проверки сервера разработчика программы на предмет выхода новой версии. При наличии такой можно предложить пользователю скачать ее, либо обновиться самостоятельно.

Можно использовать централизованную схему клиент-сервер. В таком случае ваша программа может быть разделена на две логические части — клиентскую, которая запускается на компьютере пользователя и предоставляет ему интерфейс, и серверную, которая работает на сервере, принадлежащем разработчику, и может заниматься, например, доступом к базе данных. Логика работы программы тоже может быть разделена на две части.

Можно организовывать централизованное хранилище данных. Это удобно, если, например, вам нужно собирать данные от пользователей вашей программы в одном месте, либо предоставить пользователям возможность обмениваться сообщениями.

Если вы организуете взаимодействие с клиентами посредством нескольких каналов, можно позаботиться об омниканальности — возможности сохранять информацию о всех взаимодействиях с пользователем по любым каналам, создавая ощущение бесшовности. Такую схему активно используют, например, банки. Вы можете зайти в мобильное приложение, совершить там какую-то операцию, а затем зайти в личный кабинет на веб-сайте банка и продолжить работу с того же места.

Выводы:

  1. Сетевые приложения — это программы, которые обмениваются данными через сеть.
  2. В подавляющем большинстве случаев обмен идет на основе протоколов TCP/IP.
  3. В настоящее время все более или менее развитые приложения являются сетевыми в той или иной мере.
  4. Приложения обмениваются данными с другими либо между своими компонентами.
  5. Можно обращаться к сторонним сервисам
  6. Создание публичного сервиса — это тоже задача сетевого программирования.
  7. Многопользовательские приложения очень распространены в определенных предметных областях.
  8. Автоматические обновления — это возможность, которая есть почти во всех программных продуктах.
  9. Одна из частных задач сетевого программирования — удаленное хранение данных
  10. Для экономики и бизнеса важна омниканальность взаимодействия с клиентами и пользователями.

В чем сложности создания сетевых приложений?

Разработка сетевых приложений — отдельная дисциплина информатики, требующая от программиста некоторых дополнительных знаний и умений. Естественно, для создания приложений, использующих возможности компьютерной сети необходимо знать основы функционирования таких сетей, понимать главные сетевые протоколы, понятие маршрутизации и адресации. В подавляющем большинстве случаев на практике используются протоколы семейства TCP/IP. Это сейчас универсальный механизм передачи данных. Но сами сетевые приложения могут интенсивно использовать разные конкретные протоколы, находящиеся на разных уровнях модели OSI.

Например, в этой теме мы в основном будем говорить об использовании TCP и UDP сокетов, то есть будем работать на транспортном уровне модели OSI. Но можно работать и на других уровнях с использованием других протоколов. Так веб приложения наиболее активно полагаются на протокол HTTP и его защищенную модификацию — HTTPS. Поэтому так важно знать все многообразие существующих схем и протоколов обмена данными. Ведь при проектировании приложения нужно выбрать тот протокол, уровень, который наилучшим образом подходит для решений стоящей перед разработчиками прикладной задачи.

Кроме того, при проектировании и реализации сетевых приложений на вас, как на разработчике лежит задача продумывания конкретного обмена данными. Используемый протокол регламентирует порядок передачи данных, но конкретную схему, последовательность обмена создает разработчик конкретного приложения. Вам надо определиться, какие данные будут передаваться по сети, в каком формате, в каком порядке. Будете ли вы использовать, например, JSON или XML, может, стоит придумать свой формат данных? После соединения, какой модуль начинает передачу, сколько итераций передачи будет проходить за одно соединение, как обозначить конец передачи, нужно ли регламентировать объем передаваемой информации, нужно ли использовать шифрование данных — это все примеры вопросов, которые необходимо будет решить в ходе разработки. Фактически, каждое сетевое приложение — это по сути еще один, очень конкретный протокол передачи, которые для себя придумывают разработчики специально для этого приложения.

Еще одна область, непосредственно связанная с сетевым обменом — это параллельное программирование. Сетевые приложения обычно очень активно используют многопоточности или другие средства обеспечения многозадачности. Какие-то операции должны выполняться в фоне, пока выполняются другие. Такое приложение становится гораздо более сложным по своей структуре. И необходимо отдельно заботиться о том, чтобы оно работало корректно при всех возможных условиях. Это происходит за счет обеспечения потокобезопасности, использования правильных архитектурных и дизайнерских шаблонов, специальных алгоритмов и структур данных.

Отдельный вопрос, непосредственно связанный с сетевым программированием — обеспечение безопасности и конфиденциальности обмена данными. Любое приложение, принимающее данные по сети должно быть рассчитано на то, что его может вызвать и передать данные любое приложение или пользователь. В том числе, с неизвестными или деструктивными целями. Поэтому в сетевых приложениях необходимо применять проверки входных данных, валидацию всей принимаемой информации, возможно — механизмы аутентификации пользователей. Если же ваше приложение отправляет данные — здесь тоже возможны риски того, что они попадут не по назначению. Так можно применять те же механизмы аутентификации, шифрования трафика. Особенно аккуратным надо быть, если ваше приложение собирает, хранит или передает персональные данные пользователей. В этом случае могут применяться определенные юридические нормы и обязательства, которые разработчики обязаны соблюдать.

Надо помнить, что сетевые приложения используют не только протоколы и соглашения передачи данных, но и конкретную физическую сетевую инфраструктуру. И у нее есть определенные параметры, которые необходимо учитывать — полоса пропускания, надежность, доступность. Как будет работать приложение, если в какой-то момент перестанет быть доступна сеть? Какая скорость передачи данных нужна для бесперебойной работы серверной части приложения? На сколько одновременных подключений рассчитан каждый программный модуль? За этим всем нужно следить не только на этапе проектирования, принятия решений, но и в процессе работы приложения — организовывать постоянный мониторинг, продумывать вопросы управления нагрузкой, дублирования, репликации данных и так далее.

Выводы:

  1. Нужно знать основы организации компьютерной сети.
  2. Для некоторых приложений необходимо знать особенности конкретных сетевых протоколов, например, HTTP.
  3. Необходимо также отдельно заботиться о согласованности обмена информацией между компонентами приложения.
  4. Написание многопоточных приложений требует специальных усилий по обеспечению потокобезопасности.
  5. Также не нужно забывать о вопросах безопасности, конфиденциальности, валидации получаемых данных.
  6. Также существуют вопросы управления нагрузкой ваших сервисов и сетевой инфраструктуры.
  7. Необходимо также помнить о доступности сети и ограниченности полосы пропускания.

Какие основные подходы к их построению?

При создании сетевых приложений первый вопрос, который должен решить для себя разработчик — создание высокоуровневой архитектуры приложений. Из каких частей (модулей) оно будет состоять, как именно они будут обмениваться данными и с кем. Будет ли обмен происходить только между модулями самого приложения или будут предусмотрены обращения к внешним сервисам?

Не затрагивая пока вопросов использования сторонних сервисов и программ, рассмотрим два самых популярных архитектурных паттерна для сетевых приложений. В принципе, любое сетевое приложение может быть либо клиент-серверным, либо распределенным.

alt_text

Самая популярная архитектура сетевых приложений — клиент серверная. Она подразумевает, что приложение состоит из серверной части и клиентской. Сервером мы будем называть именно часть программной системы, модуль, который постоянно (резидентно) выполняется и ждет запросов от клиентов. Когда запрос поступает, сервер его обрабатывает, понимает, что клиент хочет получить и выдает ему ответ.

При этом может быть одновременно запущенно несколько экземпляров клиентского модуля программы. Но как правило, они все идентичны (во всяком случае, они должны использовать идентичный протокол обмена данными), поэтому нет смысла их рассматривать отдельно, мы будем говорить об одном клиентском модуле.

Клиент в такой схеме — это модуль программы, который непосредственно взаимодействует с пользователем программы и в зависимости от его действий может инициировать соединение с сервером, чтобы произвести определенные операции.

Сервер непосредственно с клиентом не взаимодействует. Его задача — выполнять запросы клиентов. Он в такой схеме является центральным элементом. Распределение функционала между клиентом и сервером — другими словами, какие операции вашей программы должны относиться к клиентской части, а какие к серверной — тоже предмет проектирования. Определенно одно — все, что касается пользовательского интерфеса — это прерогатива клиентской части. В зависимости от задачи вы можете делать клиент более “тонким”, то есть оставить только интерфейс и больше ничего (тогда при любых действиях пользователя клиент будет запрашивать сервер и просить его выполнять операции), либо более “толстым” — то есть выносить на клиент часть непосредственного фукнционала приложения.

Достоинством клиент-серверной архитектуры является ее простота и понятность. Приложение явно делиться на четко обозначенные модули, между ними налаживается довольно типичная схема обмена данными. Но у нее есть и недостатки. Если по каким-то причинам сервер становится недоступен, то полноценно пользоваться приложением нельзя. То есть сервер — это потенциальная точка отказа.

Тем не менее, на основе клиент-серверного принципа работают большинство сетевых приложений, почи все сетевые службы, на работу с ним ориентированы большинство библиотек и фреймворков. Так что инструменты разработки обычно помогают реализовывать именно такую архитектуру приложений.

alt_text

Альтернативой клиент-серверным приложениям выступают распределенные. В них программа не делится на клиент и сервер, а состоит из множества однотипных модулей, которые совмещают в себе функции и клиента и сервера. Такие модули, будучи запущенными одновременно, могут подсоединяться друг к другу и выполнять обмен данными в произвольном порядке.

Типичным примером распределенных приложений являются пиринговые сети. В них каждый экземпляр приложения может подключаться к любому другому и налаживать многосторонний обмен информацией. Такие приложения образуют сеть подключений, подобно тому, как организован сам Интернет. Такое приложение не зависит от работы центрального сервера и может продолжать функционировать даже если большая часть этой сети будет отсутствовать или будет недоступной.

Несмотря на все достоинства, проектировать, реализовывать и поддерживать распределенные приложения может быть значительно сложнее, чем клиент-серверные. Это происходит потому, что клиент-сервер, более четко определенный протокол, ему нужно лишь следовать и все будет работать так, как предполагается. А в распределенных приложениях все приходится проектировать и продумывать с нуля.

Выводы:

  1. Есть две главные архитектуры построения сетевых приложений — клиент-серверная и распределенная.
  2. Сервер — это компьютер, программа или процесс, обеспечивающий доступ к информационным ресурсам.
  3. Клиент обычно инициирует соединение и делает запрос к ресурсу.
  4. Клиент-серверная архитектура является централизованной со всеми присущими недостатками и преимуществами.
  5. Распределенная архитектура может обойти некоторые ограничения централизованной.
  6. Распределенные приложения сложнее проектировать и управлять ими.

Основы взаимодействия через сокеты

Что такое TCP-сокеты?

alt_text

Со́кет (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью.

Сокеты — это самый базовый механизм сетевого взаимодействия программ, абстрагированный от конкретной реализации сети. Сокеты работают на транспортном уровне модели OSI — там же, где и протокол TCP и UDP.

Каждый сетевой интерфейс IP-сети имеет уникальный в этой сети адрес (IP-адрес). Упрощенно можно считать, что каждый компьютер в сети Интернет имеет собственный IP-адрес. При этом в рамках одного сетевого интерфейса может быть несколько (до 65536) сетевых портов. Для установления сетевого соединения приложение клиента должно выбрать свободный порт и установить соединение с серверным приложением, которое слушает (listen) порт с определенным номером на удаленном сетевом интерфейсе. Пара IP-адрес и порт характеризуют сокет (гнездо) — начальную (конечную) точку сетевой коммуникации.

Сокеты могут применяться для связи процессов как на удаленной машине, так и на локальной. В первом случае, естественно, необходимо, чтобы удаленная машина была доступна по сети. Это можно проверить при помощи команды пинг. Во втором случае сокеты могут выступать как механизм межпроцессного взаимодействия. Или вы можете использовать одну машину для всех процессов-компонентов вашей программной системы, например, для отладки в процессе разработки.

Для создания соединения TCP/IP необходимо два сокета: один на локальной машине, а другой — на удаленной. Таким образом, каждое сетевое соединение имеет IP-адрес и порт на локальной машине, а также IP-адрес и порт на удаленной машине. Как правило, порт локальной машины (исходящий порт) не так важен и его номер не особенно используется в практике. Но порт серверного сокета — это важная информация

Сокеты могут быть клиентские и серверные. Серверный сокет — это функция в программе, которая сидит на определенном порту и “слушает” входящие соединения. Процедура создания серверного сокета аналогична вводу текста из консоли: программа блокируется до тех пор, пока пользователь не ввел что-то. Когда это случилось, программа разблокируется и может продолжать выполнение и обработку полученных данных. Также и серверный сокет: ждет, когда к нему подключится клиент и тогда продолжает выполнение программы и может считывать данные из сокета (которые послал клиент) и отправлять данные в сокет. Клиентский же сокет, наоборот, сразу пытается подключиться к определенном узлу сети (это может быть локальная машина, или, чаще, удаленный компьютер) и на определенный сетевой порт. Если на этой машине на этом порту “сидит” серверный сокет, то подключение происходит успешно. Если же данный сокет никем не прослушивается, то процедура подключения возвращает ошибку.

В языке программирования Python существует стандартный модуль socket, который реализует все необходимые функции для организации обмена сообщениями через сокеты. Для его использования его достаточно импортировать (так как это модуль стандартной библиотеки, устанавливать его не нужно, он идет в поставке с дистрибутивом Python):

Для начала построения сетевого взаимодействия необходимо создать сокет:

Здесь ничего особенного нет и данная часть является общей и для клиентских и для серверных сокетов. Дальше мы будем писать код отдельно для сервера и для клиента.

Существует несколько видов сокетов, которые немного различаются по сфере применения и деталях реализации. Самыми распространенными являются Интернет-сокеты. Они используются для пересылки информации между процессами. Есть еще сокеты UNIX, они не используют Интернет-протоколы для обмена сообщениями, и используются для организации межпроцессного взаимодействия.

Также среди Интернер сокетов существуют потоковые и датаграммные сокеты.
Датаграммные сокеты называют “сокеты без соединения”, они используют протокол UDP вместо TCP. Потоковые сокеты обеспечивают гарантированную доставку, очередность сообщений, они более надежны. Протокол HTTP использует именно потоковые сокеты для соединения клиента с сервером. UDP обычно используется для передачи потокового медиа, когда скорость критичнее риска потери единичных пакетов.

Выводы:

  1. Сокеты — это базовый механизм сетевого взаимодействия программ.
  2. Для работы сокетов не нужно специальное программное обеспечение, они работают на уровне операционных систем.
  3. Сокет состоит из адреса хоста и номера сетевого порта.
  4. Для соединения необходимо создать два сокета — в двух модуля программы, которые нужно соединить.
  5. Стандартный модуль Python socket позволяет создавать сокеты и работать с ними.
  6. Еще отдельный вид сокетов применяется для организации межпроцессного взаимодействия в *nix системах.
  7. Сокеты могут использовать протокол TCP либо UDP.

Каковы правила использования номеров портов?

Для эффективного использования сетевых сокетов необходимо вспомнить концепцию сетевых портов, так как сокеты их активно используют.

alt_text

IP-адрес или любой другой способ адресации хоста позволяет нам идентифицировать узел сети. Номер порта позволяет указать конкретное приложение на этом хосте, которому предназначен пакет. Номер порта нужен, так как на любом компьютере может быть одновременно запущено множество приложений, осуществляющих обмен данными по сети. Если использовать аналогию с почтовыми адресами, то IP-адрес — это номер дома, а порт — это номер квартиры в этом доме.

Номер порта — это всего лишь 16-битное число, которое указывается в пакете, передающемся по сети. Не нужно путать сетевой порт с физическими разъемами, это чисто программная концепция.

Так как на номер порта отведено 16 бит, существует всего 65536 возможных портов. Причем, номера портов отдельно считаются по протоколам TCP и UDP. Таким образом, на компьютере одновременно может существовать более 130 тысяч процессов, обменивающихся данными. На практике, свободных портов всегда в избытке и хватает даже для работы множества высоконагруженных серверов.

Но не все номера портов созданы равными. Первые 1024 являются “системными” и используются в основном стандартными приложениями. Существует общепринятое соглашение, какие сетевые службы используют системные порты. Например, служба веб-сервера по умолчанию использует 80 порт для соединений по протоколу HTTP и 443 — для протокола HTTPS. Служба SSH использует порт номер 22. И так далее. Любая стандартная сетевая служба имеет некоторый порт по умолчанию. Кстати, хорошим показателем практикующего администратора является запоминание часто используемых номеров стандартных портов. Специально это учить не нужно, только если вы не хотите блеснуть знаниями на собеседовании.

Для использования системных портов обычно требуются повышенные привилегии. Это сделано для того, чтобы обычные пользователи не “забивали” стандартные порты и не мешали работе системных сетевых служб. Мы вообще не рекомендуем использовать системные порты. Остальные могут использоваться совершенно произвольно и их более чем достаточно для повседневных нужд.

Кстати, хоть сетевые службы используют определенные стандартные порты, они вполне могут быть переназначены на свободные. Служба не “привязана” к номеру порта, это легко регулируется настройками. Например, строго рекомендуется при настройке службы SSH менять стандартный 22 порт на случайный для повышения безопасности.

Порты назначаются процессу при попытке открыть серверный сокет. В этот момент происходит связывание сокета с номером порта, который выбрал программист. Помните, что если вы пытаетесь использовать занятый порт, то это спровоцирует программную ошибку. Поэтому в реальных приложениях стоит сначала проверять, свободен ли порт или нет, либо (что гораздо проще) обрабатывать исключение при попытке связаться с занятым портом.

Сетевые администраторы могут в целях безопасности блокировать соединения на некоторые порты. Так же поступают и программы-файерволлы. Это требуется для повышения безопасности сервера. Вообще, по умолчанию, все порты “закрыты”, то есть подключения к ним блокируется файерволлом. При необходимости системный администратор может “открыть” обмен данными по определенному номеру порта в настройках файерволла. Это следует учитывать при попытках подключения к удаленным машинам.

Выводы:

  1. Порт — это всего лишь число в IP-пакете.
  2. Номер порта нужен, чтобы обратиться к определенному процессу на конкретном хосте.
  3. Всего существует 65536 TCP-портов и 65536 UDP-портов.
  4. Первые 1024 порта являются системными — их можно использовать только администраторам.
  5. Распространенные сетевые службы имеют стандартные номера портов, их лучше не занимать.
  6. Порт назначается при открытии серверного сокета. Можно занять только свободный порт.
  7. Системные администраторы, программы-файерволлы могут заблокировать, “закрыть” обмен данными по номерам портов.

Почему стоит начать именно с изучения сокетов?

alt_text

Сокеты, которые мы рассматриваем как базовый инструмент сетевого взаимодействия, относятся к транспортному уровню модели OSI. Мы можем рассматривать механизмы передачи данных на любом уровне. Но начинаем изучение сетевых приложений именно с транспортного уровня и на это есть свои причины.

Более высокоуровневые механизмы требуют установленного и настроенного специального программного обеспечения. Чтобы написать веб-приложение, нам нужен веб-клиент и веб-сервер, настроенные и готовые к работе. Такая же ситуация с любой другой службой Интернета. Конечно, на практике большинство популярных сетевых приложений используют более высокоуровневые протоколы, например, тот же HTTP.

Использование сокетов позволяет строить приложения, обменивающиеся данными по сети, но при этом не требующие специального программного обеспечения. Даже если вы планируете использовать в профессиональной деятельности прикладные протоколы, все они в основе своей работы используют механизм сокетов, так что его понимание будет полезно всем.

С другой стороны, более низкоуровневые протоколы не абстрагируются от конкретной реализации сети. Например, на физическом уровне мы должны будем иметь дело с протоколами передачи данных по витой паре, беспроводному каналу, решать проблемы помех, интерференции сигналов и прочие технические вопросы. Это не позволит нам сконцентрироваться на сутевых вопросах создания сетевых алгоритмов.

Поэтому транспортные сокеты — это компромиссный вариант между прикладными и физическими протоколами.

Выводы:

  1. Сокеты не требуют специального программного обеспечения.
  2. Сокеты не зависят от конкретной физической реализации сети.
  3. Сокеты хороши для понимания основ сетевого взаимодействия.

Как организуется обмен данными через TCP-сокеты?

alt_text

Для соединения двух программ необходимо создать два сокета — один серверный и один клиентский. Они обычно создаются в разных процессах. Причем эти процессы могут быть запущены как на одной машине, так и на разных, на механизм взаимодействия это не влияет.

Работа с сокетами происходит через механизм системных вызовов. Все эти вызовы аналогичны вызовам, осуществляющим операции с файлами. Список этих вызовов определен в стандарте POSIX и не зависит от используемой операционной системы или языка программирования. В большинстве языков программирования присутствуют стандартные библиотеки, которые реализуют интерфейс к этим системным вызовам. Поэтому работа с сокетами достаточно просто и бесшовно организуется на любом языке программирования и на любой операционной системе.

Давайте рассмотрим общую схему взаимодействия через сокеты и последовательность действий, которые нужно совершить, чтобы организовать соединение. Первым всегда создается серверный сокет. Он назначается на определенный порт и начинает его ожидать (прослушивать) входящие соединения. Создание сокета, связывание сокета с портом и начало прослушивания — это и есть системные вызовы.

Причем при выполнении системных вызовов могут случиться нестандартные ситуации, то есть исключения. Обычно это происходит при связывании с портом. Если вы попытаетесь занять системный порт, не обладая парами администратора, или попытаетесь занять порт, уже занятый другим процессом, возникнет программная ошибка времени выполнения. Такие ситуации надо обрабатывать в программе.

Прослушивание порта — это блокирующая операция. Это значит, что программа при начале прослушивания останавливается и ждет входящих подключений. Как только поступит подключение, программа возобновится и продолжит выполнение со следующей инструкции.

В другом процессе создается клиентский сокет. Клиент может подключиться к серверному сокету по адресу и номеру порта. Если этот порт является открытым (то есть его прослушивает какой-то процесс), то произойдет соединение и на сервере выполнится метод accept. Важно следить за тем, чтобы клиент подключился именно к нужному порту. На практике из-за этого происходит много ошибок.

Соединение с серверным сокетом — это тоже системный вызов. При этом вызове тоже часто случаются ошибки. Естественно, если не удастся установить сетевое соединение с удаленным хостом по адресу, это будет ошибка. Такое случается, если вы неправильно указали IP-адрес, либо удаленная машина недоступна по сети. Еще ошибка может возникнуть, если порт, к которому мы хотим подключиться свободен, то есть не прослушивается никаким процессом. Также не стоит забывать, что порт на удаленной машине может быть закрыт настройками файерволла.

После этого устанавливается двунаправленное соединение, которое можно использовать для чтения и записи данных. Интерфейс взаимодействия с сокетом очень похож на файловые операции, только при записи информации, она не сохраняется на диск, а посылается в сокет, откуда может быть прочитана другой стороной. Чтение информации из сокета — это также блокирующая операция.

Следует помнить, что сокеты предоставляют потоковый интерфес передачи данных. Это значит, что через сокет не получится посылать сообщения “пакетами”. По сути, сокет предоставляет два непрерывных потока — один от сервера к клиенту, другой — от клиента к серверу. Мы не можем прочитать отдельное сообщение из этого потока. При чтении мы указываем количество бит, которые хотим прочитать. И процесс будет ждать, пока другой не отправит в сокет необходимое количество (ну либо не закроет сокет). Более понятны особенности работы в потоковом режиме станут на практике.

После обмена данными, сокеты рекомендуется закрывать. Закрытие сокета — это тоже специальный системный вызов. Закрывать нужно как клиентский, так и серверный сокет. Если вы забудете закрыть сокет, то операционная система все еще будет считать соответствующий порт занятым. После завершения процесса, который открыл сокет, все открытые дескрипторы (файлы, устройства, сокеты) автоматически закрываются операционной системой. Но при этом может пройти определенной время, в течении которого сокет будет считаться занятым. Это доставляет довольно много проблем, например, при отладке программ, когда приходится часто подключаться и переподключаться к одному и тому же порту. Так что серверные сокеты надо закрывать обязательно.

Еще одно замечание. Так как сокет — это пара адрес-порт, и сокета для соединения нужно два, получается что и порта тоже нужно два? Формально, да, у нас есть клиент — его адрес и номер порта, и сервер — его адрес и номер порта. Первые называются исходящими (так как запрос на соединение происходит от них), а вторые — входящие (запрос приходит на них, на сервер). Для того, чтобы соединение прошло успешно, клиент должен знать сокет сервера. А вот сервер не должен знать сокет клиента. При установке соединения, адрес и порт клиента отправляются серверу для информации. И если адрес клиента иногда используется для его идентификации (я тебя по IP вычислю), то исходящий порт обычно не нужен. Но откуда вообще берется этот номер? Он генерируется операционной системой случайно из незанятых несистемных портов. При желании его можно посмотреть, но пользы от этого номера немного.

Выводы:

  1. Серверный сокет назначается на определенный свободный порт и ждет входящих соединений.
  2. Клиентский сокет сразу соединяется с северным. Тот должен уже существовать.
  3. Сокет — это двунаправленное соединение. В него можно читать и писать, как в файл.
  4. Сокет — это битовый потоковый протокол, строки нужно определенным образом кодировать в битовый массив пред отправкой.
  5. После использования сокет нужно закрыть, иначе порт будет считаться занятым.
  6. Существует входящий и исходящий номер порта. Но исходящий номер назначается случайно и редко используется.

Простейшие клиент и сервер

Что мы хотим сделать?

alt_text

Давайте приступим к практическому знакомству с обменом информацией между процессами через сокеты. Для этого мы создадим простейшую пару клиент-сервер. Это будут две программы на Python, которые связываются друг с другом, передают друг другу какую-то информацию и закрываются.

Мы начнем с самой простой схемы передачи данных, а затем будем ее усложнять и на этих примерах будет очень легко понять, как работают сокеты, как они устроены и для чего могут быть применены.

Мы будем организовывать один набор сокетов. Так что один из них должен быть клиентским, а другой — серверным. Поэтому один процесс мы будем называть сервером, а другой — клиентом. Однако, такое разделение условно и одна и та же программа может выступать и клиентом для одного взаимодействия и сервером — для другого. Можно сказать, что клиент и сервер — это просто роли в сетевом взаимодействии. Инициирует соединение всегда клиент, это и определяет его роль в сетевом взаимодействии. То есть, то процесс, который первый начинает “стучаться” — тот и клиент.

При разработке сетевых приложений вам придется много раз запускать оба этих процесса. Вполне естественно, что часто они будут завершаться ошибками. Помните, что для соединения всегда первым должен быть запущен сервер, а затем клиент.

Как создать простой эхо-сервер?

Для начала нам нужно определиться, какой порт будет использовать наш сервер. Мы можем использовать любой несистемный номер порта. Лучше заранее убедиться, что он в данный момент не используется в вашей системе. Например, если вы используете сервер баз данных MS SQL он обычно занимает порт 5432. Мы в учебных примерах будет использовать номер 9090 — это запоминающееся, но ничем не особенное число. Вы можете использовать другой.

Для начала импортируем модуль socket:

Теперь можно создать сокет одноименным конструктором из этого модуля:

Теперь переменная sock хранит ссылку на объект сокета. И через эту переменную с сокетом можно производить все необходимые операции.

После этого свяжем сокет с номером порта при помощи метода bind:

Этот метод принимает один аргумент — кортеж из двух элементов. Поэтому обратите внимание на двойные круглые скобки. Первый элемент кортежа — это сетевой интерфейс. Он задает имя сетевого адаптера, соединения к которому нас интересуют. Вы же знаете, что на компьютере может быть несколько адаптеров, и он может быть подключен одновременно к нескольким сетям. Вот с помощью этого параметра можно выбрать соединения из какой конкретно сети мы будем принимать. Если оставить эту строку пустой, то сервер будет доступен для всех сетевых интерфейсов. Так как мы не будем заниматься программированием межсетевых экранов, мы будем использовать именно это значение.

Второй элемент кортежа, который передается в метод bind — это, собственно, номер порта. Для примера выберем порт 9090.

Теперь у нас все готово, чтобы принимать соединения. С помощью метода listen мы запустим для данного сокета режим прослушивания. Метод принимает один аргумент — максимальное количество подключений в очереди. Установим его в единицу. Чуть позже мы узнаем, на что влияем длина очереди подключений сервера.

Теперь, мы можем принять входящее подключение с помощью метода accept, который возвращает кортеж с двумя элементами: новый сокет (объект подключения) и адрес клиента. Именно этот новый сокет и будет использоваться для приема и посылке клиенту данных.

1
conn, addr = sock.accept()

Обратите внимание, что это блокирующая операция. То есть в этот момент выполнение программы приостановится и она будет ждать входящие соединения.

Объект conn мы будем использовать для общения с этим подключившимся клиентом. Переменная addr — это всего лишь кортеж их двух элементов — адреса хоста клиента и его исходящего порта. При желании можно эту информацию вывести на экран.

Так мы установили с клиентом связь и можем с ним общаться. Чтобы получить данные нужно воспользоваться методом recv, который в качестве аргумента принимает количество байт для чтения. Мы будем читать из сокета 1024 байт (или 1 кб):

1
2
data = conn.recv(1024)
conn.send(data.upper())

Обратите внимание, что прием и отправка сообщений происходит через объект conn, а не sock. Объект conn — это подключение к конкретному клиенту. Это особенность работы именно TCP-сокетов, при использовании протокола UDP все немного по-другому.

Тут есть один неудобный момент. При чтении нам нужно указать объем данных, которые мы хотим прочитать. Это обязательный параметр. Помните мы говорили, что сокеты — это потоковый механизм? Именно поэтому нельзя прочитать сообщение от клиента “целиком”. Ведь клиент может присылать в сокет информацию порциями, в произвольный момент времени, произвольной длины. В сокете, а точнее во входящем потоке, все эти сообщения “склеются” и будут представлены единым байтовым массивом.

Поэтому сервер обычно не знает, какое именно количество информации ему нужно прочитать. Но это решается довольно просто. Можно организовать бесконечный цикл, в котором читать данные какой-то определенной длины и каждый раз проверять, получили мы что-то или нет:

1
2
3
4
5
while True:
    data = conn.recv(1024)
    if not data:
        break
    conn.send(data)

Это работает потому, что после того, как клиент отослал всю информацию, он закрывает соединение. В таком случае на сервере метод recv возвращает пустое значение. Это сигнал завершения передачи.

Метод recv(), как мы говорили, тоже является блокирующей операцией. Он разблокируется (то есть продолжает выполняться) в двух случаях: когда клиент прислал необходимый объем данных, либо когда клиент закрыл соединение.

Еще, если вы обратили внимание, после получения данных мы их отправляем обратно клиенту методом send(). Это метод посылает данные через сокет. Это уже не блокирующая операция, ведь данные посылаются тут же и не нужно ничего ждать.

Конечно, на практике такой сервер был бы бесполезным. Реальные сервера посылают клиенту какие-то другие данные, ответы на его запросы, например. Но наш простой сервер служит только для отладки и обучения. Такой сервер по понятным причинам называется “эхо-сервер”.

После получения порции данных и отсылки их обратно клиенту можно и закрыть соединение:

На этом написание сервера закончено. Он принимает соединение, принимает от клиента данные, возвращает их клиенту и закрывает соединение. Вот что у нас получилось в итоге

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python
import socket
sock = socket.socket()
sock.bind(('', 9090))
sock.listen(1)
conn, addr = sock.accept()
print 'connected:', addr
while True:
    data = conn.recv(1024)
    if not data:
        break
    conn.send(data)
conn.close()

Как создать простейший клиент?

Клиентское приложение еще короче и проще. Клиент использует точно такой же объект socket.

1
2
import socket
sock = socket.socket()

Вместо привязывания и прослушивания порта мы сразу осуществляем соединение методом connect. Для этого указывается IP-адрес хоста, на котором запущен сервер и номер порта, который он прослушивает.

1
sock.connect(('localhost', 9090))

В качестве адреса можно использовать IP-адрес, доменное имя, либо специальное имя localhost. В нашем случае, так как мы пока подключаемся к другому процессу на той же машине, будем использовать его (либо адрес 127.0.0.1, что абсолютно эквивалентно).

При неуспешном соединении метод listen выбросит исключение. Существует несколько причин — хост может быть недоступен по сети, либо порт может не прослушиваться никаким процессом.

Послание данных в сокет осуществляется методом send. Но тут есть один подводный камень. Дело в том, что сокеты — это еще и байтовый протокол. Поэтому в него не получится просто так отправить, например, строку. Ее придется преобразовать в массив байт. Для этого в Python существует специальный строковый метод — encode(). Его параметром является кодировка, которую нужно использовать для кодирования текста. Если кодировку не указывать, то будет использоваться Unicode. Рекомендуем это так и оставить. Вот что получится в итоге:

1
2
msg = 'hello, world!'
sock.send(msg.encode())

Дальше мы читаем 1024 байт данных и закрываем сокет. Для большей надежности чтение данных можно организовать так же как на сервере — в цикле.

1
2
data = sock.recv(1024)
sock.close()

Если мы обмениваемся текстовой информацией, то надо помнить, что данные полученные из сокета — это тоже байтовый массив. Чтобы преобразовать его в строку (например, для вывода на экран), нам нужно воспользоваться методом decode(), который выполняет операцию, обратную encode(). Понятно, что кодировки при этом должны совпадать. Вообще, нет ни одной причины использовать не Unicode.

Здесь важно помнить вот что. Этот клиент хорошо подходит к тому серверу, который мы создавали в предыдущем пункте. Ведь после установки соединения сервер ожидает приема информации (методом recv). А клиент после соединения сразу начинает отдавать информацию (методом send). Если бы же обе стороны начали ждать приема, они бы намертво заблокировали друг друга. О порядке передачи информации нужно определиться заранее. Но в общем случае, обычно именно клиент первым передает запрос, сервер его читает, передает ответ, который читает клиент.

Вот что у нас получилось в итоге:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python

import socket

sock = socket.socket()
sock.connect(('localhost', 9090))

msg = 'hello, world!'
sock.send(msg.encode())

data = sock.recv(1024)
sock.close()

print(data.decode())

Какие ограничения данного подхода?

Созданные нами клиент и сервер вполне функциональны, но у них есть ряд серьезных ограничений, которые могут затруднить более сложную работу. Например, можно заметить, что сервер выполняет только одну операцию, то есть принимает запрос, выдает ответ и на этом соединение заканчивается. Это, в принципе, не так страшно, ведь всегда можно открыть повторное соединение.

Но наш сервер может обслужить только одного клиента. Ведь после закрытия сокета программа заканчивается и сервер завершается. На практике сервера обычно работают постоянно, в бесконечном цикле, и ждут запросов от клиента. После завершения работы с одним клиентом они продолжают ожидать входящих соединений. Но это исправить можно очень просто (в следующем пункте).

Еще можно заметить, что в работе и клиента и сервера часто встречаются блокирующие операции. Само по себе это неплохо, и даже необходимо. Но представим себе, что клиент по каким-то причинам аварийно завершил свою работу. Или упала сеть. Если в это время сервер ждал сообщения от клиента (то есть заблокировался методом recv), то он останется ждать неопределенно долго. То есть упавший клиент может нарушить работу сервера. Это не очень надежно. Но это тоже легко исправить с помощью установки таймаутов.

Более сложная проблема состоит в том, что сервер способен одновременно работать только с одним клиентом. Вот это исправить уже сложнее, так как потребует многопоточного программирования. Но есть еще один вариант — использовать UDP-сокеты.

Так что давайте познакомимся с некоторыми приемами, которые позволяют обойти или исправить эти недостатки.

Как сделать сервер многоразовым?

Одно из самых заметных отличий нашего простейшего сервера от “настоящего” — его одноразовость. То есть он рассчитан только на одно подключение, после чего просто завершается. исправить эту ситуацию можно очень просто, обернув все действия после связывания сокета с портом и до закрытия сокета в вечный цикл:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
import socket
sock = socket.socket()
sock.bind(('', 9090))
sock.listen(1)
while True:
	conn, addr = sock.accept()
	print 'connected:', addr
	while True:
	    data = conn.recv(1024)
	    if not data:
	        break
	    conn.send(data)
	conn.close()

Теперь наш сервер после завершения соединения с одним клиентом переходит на следующую итерацию цикла и начинает снова слушать входящие соединение. И к нему может подключиться как тот же самый клиент, так и новый.

Заодно можно в коде сервера сделать небольшой рефакторинг, чтобы он был более универсальным и удобным для внесения изменений. Рефакторинг — это изменение кода без изменение функциональности. Чем понятнее, проще и системнее организация кода, тем проще его развивать, поддерживать и вносить изменения. Инструкции, отвечающие за разные вещи в хорошем коде должны быть отделены от других. Это помогает в нем ориентироваться.

Например, само собой напрашивается создание констант для параментов сокета — интерфейса и номера порта:

1
2
3
4
5
HOST = ""
PORT = 33333

TYPE = socket.AF_INET
PROTOCOL = socket.SOCK_STREAM

Кроме адреса мы еще выделили тип и протокол сокета. Это параметры конструктора socket(), которые мы раньше не использовали. Первый параметр задает тип создаваемого сокета — Интернет-сокет или UNIX-сокет. Мы будем использовать только первый тип, но сейчас мы зададим его явно. Второй параметр — это тип используемого протокола. Потоковые сокеты используют протокол TCP, а датаграммные — UDP. Скоро мы будем создавать и UDP-соединение, так что не лишним будет прописать это тоже в явном виде.

После этих изменений код создания и связывания сокета будет выглядеть следующим образом:

1
2
srv = socket.socket(TYPE, PROTOCOL)
srv.bind((HOST, PORT))

Еще одним этапом рефакторинга можно выделить функцию, обрабатывающую запрос от клиента. Сейчас у нас на сервере обработки запроса не происходит, но в будущем любой сервер каким-то образом что-то делает с запросом клиента и выдает получившийся результат. Логично сделать специальную функцию, чтобы работа с запросом была отделена в коде от механики организации соединения:

1
2
3
4
5
6
7
8
9
10
def do_something(x):
  # ...
  return x

# ...

srv = socket.socket(TYPE, PROTOCOL)
srv.bind((HOST, PORT))

# ...

Еще хорошей идеей, хотя бы на этапе разработки будет добавить отладочные сообщения в тем моменты, когда на сервере выполняются определенные действия. Это поможет нам визуально понимать, как выполняется код сервера, следить за ним по сообщениям в консоли и очень полезно для обучения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  srv.listen(1)             
  print("Слушаю порт 33333")
  sock, addr = srv.accept()
  print("Подключен клиент", addr)
  while 1:
    pal = sock.recv(1024)
    if not pal: 
      break
    print("Получено от %s:%s:" % addr, pal)
    lap = do_something(pal)
    sock.send(lap)
    print("Отправлено %s:%s:" % addr, lap)
  sock.close()
  print("Соединение закрыто")

Кроме того, такие отладочные сообщения служат заменой комментариям в коде. Сейчас комментарии особенно и не нужны, потому что их названия переменных, отладочных сообщений, имен функций понятно, что происходит в программе. Такой код называется самодокументируемым.

Конечно, код можно улучшать до бесконечности, но мы на этом остановимся. Вот что у нас получилось в итоге — многоразовый эхо-сервер:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket, string

HOST = ""
PORT = 33333

TYPE = socket.AF_INET
PROTOCOL = socket.SOCK_STREAM
 
def do_something(x):
  return x

srv = socket.socket(TYPE, PROTOCOL)
srv.bind((HOST, PORT))
while 1:
  srv.listen(1)             
  print("Слушаю порт 33333")
  sock, addr = srv.accept()
  print("Подключен клиент", addr)
  while 1:
    pal = sock.recv(1024)
    if not pal: 
      break
    print("Получено от %s:%s:" % addr, pal)
    lap = do_something(pal)
    sock.send(lap)
    print("Отправлено %s:%s:" % addr, lap)
  sock.close()
  print("Соединение закрыто")

Как задать таймаут прослушивания?

Как мы говорили раньше, блокирующие операции в коде программы должны быть объектом пристального внимания программиста. Блокировки программы могут существенно замедлять ее выполнение (об этом мы поговорим потом, когда будем изучать многозадачное программирование), могут быть источником ошибок.

Модуль socket позволяет выполнять блокирующие операции в нескольких режимах. Режим по умолчанию — блокирующий. В нем при выполнении операции программа будет ждать неопределенно долго наступления внешнего события — установки соединения или отправки другой стороной данных.

Можно использовать другой режим — режим таймаута — путем вызова метода settimeout(t) перед выполнением потенциально блокирующей операции. Тогда программа тоже заблокируется, но будет ждать только определенное время — t секунд. Если по прошествии этого времени нужное событие не произойдет, то метод выбросит исключение.

Таймаут можно “вешать” как на подключение, как и на чтение данных их сокета:

1
2
3
4
5
6
7
8
9
10
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("",0))
sock.listen(1)
# accept can throw socket.timeout
sock.settimeout(5.0)
conn, addr = sock.accept()
 
# recv can throw socket.timeout
conn.settimeout(5.0)
conn.recv(1024)

В таком случае, лучше обрабатывать возможное исключение, чтобы оно не привело к аварийному завершения программы:

1
2
3
4
5
6
# recv can throw socket.timeout
conn.settimeout(5.0)
try:
	conn.recv(1024)
except socket.timeout:
	print("Клиент не отправил данные")

Как обмениваться объектами по сокетам?

В работе сетевых приложений часто возникает задача передать через сокет не просто байтовый массив или строку, а объект или файл произвольного содержания. Во многих языках программирования существует специальный механизм, который позволяет преобразовать произвольный объект в строку таким образом, чтобы потом весь объект можно было бы восстановить их этой строки.

Такой процесс называется сериализация объектов. Она применяется, например, при сохранении данных программы в файл. Но с тем же успехом ее можно использовать и для передачи объектов через сокеты.

В Python существует стандартный модуль pickle, который нужен для сериализации объектов. Из этого модуля нам понадобится две операции. Метод dumps(obj) преобразует объект в строковое представление, сериализует его. Метод loads(str) восстанавливает объект из строки — десериализует его.

Давайте рассмотрим пример, когда в сокет отправляется сериализованный словарь:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import socket
import pickle
 
s = socket.socket()
s.bind(("", 9090))
s.listen(1)
 
while True:
    clientsocket, address = s.accept()
 
    d = {1:"hi", 2: "there"}

    msg = pickle.dumps(d).encode()
    clientsocket.send(msg)

Имейте в виду, что на месте словаря может быть любой сложный объект. А это значит, что мы можем передавать через сокеты в сериализованном виде практически любою информацию.

На другой стороне прочитать и восстановить объект из сокета можно, например, так:

1
2
3
msg = conn.recv(1024)

d = pickle.loads(msg.decode())

В чем особенности UDP-сокетов?

До сих пор мы рассматривали только сокеты, использующие в своей работе протокол TCP. Этот протокол еще называют протоколом с установкой соединения. Но в сокетах можно использовать и протокол UDP. И такие сокеты (их еще называют датаграммными) имеют с воей реализации несколько отличий. Давайте их рассмотрим.

Создание UDP сокета очень похоже, только надо явно указывать агрументы конструктора socket() ведь по умолчанию создаются TCP сокеты:

1
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

Обратите внимание на второй параметр — именно он задает протокол передачи данных. Раньше мы использовали значение socket.SOCK_STREAM — это TCP-сокет. Для UDP-сокетов нужно значение socket.SOCK_DGRAM.

Связывание серверного сокета с портом происходит точно так же:

Дальше пойдут различия. UDP-сокеты не требуют установки соединения. Они способны принимать данные сразу. Но для этого используется другой системный вызов (и соответствующий ему метод Python) — recvfrom. Этот метод блокирует программу и когда присоединится любой клиент, возвращает сразу и данные и сокет клиента:

1
data, addr = s.recvfrom(1024)

Этот метод также работает в потоковом режиме. То есть нам нужно указывать количество байт, которые мы хотим считать. Но при следующем вызове метода recvfrom() могут придти данные уже от другого клиента. Надо понимать, что UDP-сокеты являются “неразборчивыми”, то есть в него может писать несколько клиентов одновременно, “вперемешку”. Для этого каждый раз и возвращается исходящий сокет, чтобы мы могли понять, от какого клиента пришли эти данные.

Что касается клиентского сокета, то здесь все еще проще:

1
2
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(msg, (host, port))

То есть сразу после создания сокет готов к передаче данных. Но каждый раз нам нужно указывать, куда мы эти данные посылаем.

UDP-сокеты даже проще по своей организации, чем TCP. Меньше шагов нужно сделать, чтобы обмениваться информацией. Но UDP является протоколом без гарантированной доставки. Они ориентирован на скорость и простоту передачи данных, в то время как TCP — на надежность связи.

В конце приведем пример кода сервера и клиента, обменивающихся данными через UDP-сокеты. Код сервера совсем короткий. Но это такой же эхо-сервер, только работающий на другом протоколе:

1
2
3
4
5
6
7
8
import socket
port = 5000
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", port))
while 1:
    data, addr = s.recvfrom(1024)
    print(data)
    s.sendto(data, addr)

Код клиента приведем в более подробном виде. Механику работы сокетов мы уже рассмотрели, попробуйте проанализировать в этом коде обработку исключений и вывод отладочных сообщений:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import socket
import sys
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print('Failed to create socket')
    sys.exit()
 
host = 'localhost';
port = 8888;
 
while(1) :
    msg = raw_input('Enter message to send: ')
    
    try :
        s.sendto(msg, (host, port))
        
        reply, addr = s.recvfrom(1024)        
        print('Server reply : ' + reply)
    
    except socket.error, msg:
        print('Error Code : ' + str(msg[0]) + ' Message ' + msg[1])
        sys.exit()

python tcp socket client / server examples


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

#!/usr/bin/python
import socket #for sockets
import sys #for exit
try:
#create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
print ‘Failed to create socket. Error code: ‘ + str(msg[0]) + ‘ , Error message : ‘ + msg[1]
sys.exit();
print ‘Socket Created’
host = ‘www.google.com’
port = 80
try:
remote_ip = socket.gethostbyname( host )
except socket.gaierror:
#could not resolve
print ‘Hostname could not be resolved. Exiting’
sys.exit()
print ‘Ip address of ‘ + host + ‘ is ‘ + remote_ip
#Connect to remote server
s.connect((remote_ip , port))
print ‘Socket Connected to ‘ + host + ‘ on ip ‘ + remote_ip


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

#!/usr/bin/python
#Socket client example in python
import socket #for sockets
import sys #for exit
#create an INET, STREAMing socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
print ‘Failed to create socket’
sys.exit()
print ‘Socket Created’
host = ‘www.google.com’;
port = 80;
try:
remote_ip = socket.gethostbyname( host )
except socket.gaierror:
#could not resolve
print ‘Hostname could not be resolved. Exiting’
sys.exit()
#Connect to remote server
s.connect((remote_ip , port))
print ‘Socket Connected to ‘ + host + ‘ on ip ‘ + remote_ip
#Send some data to remote server
message = «GET / HTTP/1.1rnrn«
try :
#Set the whole string
s.sendall(message)
except socket.error:
#Send failed
print ‘Send failed’
sys.exit()
print ‘Message send successfully’
#Now receive data
reply = s.recv(4096)
print reply


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

#!/usr/bin/python
# credits: http://www.binarytides.com/python-socket-server-code-example/
»’
Simple socket server using threads
»’
import socket
import sys
HOST = » # Symbolic name, meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print ‘Socket created’
#Bind socket to local host and port
try:
s.bind((HOST, PORT))
except socket.error as msg:
print ‘Bind failed. Error Code : ‘ + str(msg[0]) + ‘ Message ‘ + msg[1]
sys.exit()
print ‘Socket bind complete’
#Start listening on socket
s.listen(10)
print ‘Socket now listening’
#now keep talking with the client
while 1:
#wait to accept a connection — blocking call
conn, addr = s.accept()
print ‘Connected with ‘ + addr[0] + ‘:’ + str(addr[1])
s.close()


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

#!/usr/bin/python
# credits: http://www.binarytides.com/python-socket-server-code-example/
»’
Simple socket server using threads
»’
import socket
import sys
from thread import *
HOST = » # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print ‘Socket created’
#Bind socket to local host and port
try:
s.bind((HOST, PORT))
except socket.error as msg:
print ‘Bind failed. Error Code : ‘ + str(msg[0]) + ‘ Message ‘ + msg[1]
sys.exit()
print ‘Socket bind complete’
#Start listening on socket
s.listen(10)
print ‘Socket now listening’
#Function for handling connections. This will be used to create threads
def clientthread(conn):
#Sending message to connected client
conn.send(‘Welcome to the server. Type something and hit entern) #send only takes string
#infinite loop so that function do not terminate and thread do not end.
while True:
#Receiving from client
data = conn.recv(1024)
reply = ‘OK…’ + data
if not data:
break
conn.sendall(reply)
#came out of loop
conn.close()
#now keep talking with the client
while 1:
#wait to accept a connection — blocking call
conn, addr = s.accept()
print ‘Connected with ‘ + addr[0] + ‘:’ + str(addr[1])
#start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
start_new_thread(clientthread ,(conn,))
s.close()


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

#!/usr/bin/python
# Socket server in python using select function
# credits: http://www.binarytides.com/python-socket-server-code-example/
import socket, select
if __name__ == «__main__»:
CONNECTION_LIST = [] # list of socket clients
RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# this has no effect, why ?
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((«0.0.0.0», PORT))
server_socket.listen(10)
# Add server socket to the list of readable connections
CONNECTION_LIST.append(server_socket)
print «Chat server started on port « + str(PORT)
while 1:
# Get the list sockets which are ready to be read through select
read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
for sock in read_sockets:
#New connection
if sock == server_socket:
# Handle the case in which there is a new connection recieved through server_socket
sockfd, addr = server_socket.accept()
CONNECTION_LIST.append(sockfd)
print «Client (%s, %s) connected» % addr
#Some incoming message from a client
else:
# Data recieved from client, process it
try:
#In Windows, sometimes when a TCP program closes abruptly,
# a «Connection reset by peer» exception will be thrown
data = sock.recv(RECV_BUFFER)
# echo back the client message
if data:
sock.send(‘OK … ‘ + data)
# client disconnected, so remove from socket list
except:
broadcast_data(sock, «Client (%s, %s) is offline» % addr)
print «Client (%s, %s) is offline» % addr
sock.close()
CONNECTION_LIST.remove(sock)
continue
server_socket.close()

In any networking application, it’s common that one end will be trying to connect, while the other fails to respond due to a problem like a networking media failure. Fortunately, the Python socket library has an elegant method of handing these errors via the socket.error exceptions. Find out how to use them here.

How to do it

Let’s create a few try-except code blocks and put one potential error type in each block. In order to get a user input, the argparse module can be used. This module is more powerful than simply parsing command-line arguments using sys.argv. In the try-except blocks, put typical socket operations, for example, create a socket object, connect to a server, send data, and wait for a reply.

The following recipe illustrates the concepts in a few lines of code.

Listing 1.7 shows socket_errors as follows:

#!/usr/bin/env python

# Python Network Programming Cookbook — Chapter – 1

# This program is optimized for Python 2.7. It may run on any  

# other Python version with/without modifications.

import sys

import socket

import argparse

def main():

    # setup argument parsing

    parser = argparse.ArgumentParser(description=’Socket Error Examples’)

    parser.add_argument(‘—host’, action=»store», dest=»host», required=False)

    parser.add_argument(‘—port’, action=»store», dest=»port», type=int, required=False)

    parser.add_argument(‘—file’, action=»store», dest=»file», required=False)

    given_args = parser.parse_args()

    host = given_args.host

    port = given_args.port

    filename = given_args.file

    # First try-except block — create socket

    try:

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    except socket.error, e:

        print «Error creating socket: %s» % e

        sys.exit(1)

    # Second try-except block — connect to given host/port

    try:

        s.connect((host, port))

    except socket.gaierror, e:

        print «Address-related error connecting to server: %s» % e

        sys.exit(1)

    except socket.error, e:

        print «Connection error: %s» % e

        sys.exit(1)

    # Third try-except block — sending data

    try:

        s.sendall(«GET %s HTTP/1.0rnrn» % filename)

    except socket.error, e:

        print «Error sending data: %s» % e

        sys.exit(1)

    while 1:

        # Fourth tr-except block — waiting to receive data from remote host

        try:

            buf = s.recv(2048)

        except socket.error, e:

            print «Error receiving data: %s» % e

            sys.exit(1)

        if not len(buf):

            break

        # write the received data

        sys.stdout.write(buf)

if __name__ == ‘__main__’:

    main()

How it works

In Python, passing command-line arguments to a script and parsing them in the script can be done using the argparse module. This is available in Python 2.7. For earlier versions of Python, this module is available separately in Python Package Index (PyPI). You can install this via easy_install or pip.

In this recipe, three arguments are set up: a hostname, port number, and filename. The usage of this script is as follows:

$ python 1_7_socket_errors.py –host=<HOST> —port=<PORT> —file=<FILE>

If you try with a non-existent host, this script will print an address error as follows:

$ python 1_7_socket_errors.py —host=www.pytgo.org —port=8080 —file=1_7_socket_errors.py

Address-related error connecting to server: [Errno -5] No address associated with hostname

If there is no service on a specific port and if you try to connect to that port, then this will throw a connection timeout error as follows:

$ python 1_7_socket_errors.py —host=www.python.org —port=8080 —file=1_7_socket_errors.py

This will return the following error since the host, www.python.org, is not listening on port 8080:

Connection error: [Errno 110] Connection timed out

However, if you send an arbitrary request to a correct request to a correct port, the error may not be caught in the application level. For example, running the following script returns no error, but the HTML output tells us what’s wrong with this script:

$ python 1_7_socket_errors.py —host=www.python.org —port=80 —file=1_7_socket_errors.py

HTTP/1.1 404 Not found

Server: Varnish

Retry-After: 0

content-type: text/html

Content-Length: 77

Accept-Ranges: bytes

Date: Thu, 20 Feb 2014 12:14:01 GMT

Via: 1.1 varnish

Age: 0

Connection: close

<html>

<head>

<title> </title>

</head>

<body>

unknown domain: </body></html>

In this example, four try-except blocks have been used. All blocks use socket.error except for the second one, which uses socket.gaierror. This is used for address-related errors. There are two other types of exceptions: socket.herror is used for legacy C API, and if you use the settimeout() method in a socket, socket.timeout will be raised when a timeout occurs on that socket.

This quick tip was taken from «Python Network Programming Cookbook» published by Packt. Network Computing readers can pick it up for free by clicking

here (which will automatically add the eBook to your cart) or by using the code RGPNPC50 at the checkout. The code expires Oct. 23, 2016. From October 10-16 you can save 50% on more Python ebooks by joining Packt for Python Week.

Понравилась статья? Поделить с друзьями:
  • Exec error exec 1 could not create the java virtual machine
  • Exe err mss init failed mp call of duty 2 как исправить
  • Exchange проверка почтового ящика на ошибки
  • Exchange ошибка 500 ecp
  • Exchange error 5000