【Python】ネットワークプログラムの基礎
今回はpythonによるネットワークプログラミングに触れていきたいと思います
ネットワークプログラミングのまえに
これはpythonに限ったことではないのですが、
ネットワークプログラミングを行うためにソケットいうものが利用されます。
これはOSが提供している仕組みで、ネットワーク機能を簡単に利用するためのもので、データはこのソケットを通ってやり取りされます。
そして相手と通信を行うにはIPアドレスとポート番号が必要です。
IPアドレスとはマシンの住所を示しているとよく説明されています。
僕もだいたいその認識で、要するにマシンを識別するためのものです。
ポート番号とはそのマシンの中で動いているどのアプリケーションに接続するかを指定するためのものです。
たとえばexample.comというwebサイトを閲覧する場合、
example.comのIPアドレスとポート番号80番を指定します。
なぜならwebサイトはwebサーバーというものがを提供しています。
そのwebサーバーはポート80番をつかって通信をするからです。
ここでサーバーという単語がでてきましたが、
サーバーというのはクライアントからのリクエストを受け何かしらの情報を提供するものです。
先の例で言うとexample.comのwebサーバーが「サーバー」にあたり、
example.comのwebサイトを閲覧しようとしているブラウザが「クライアント」になります。
ひとまずここでは以下の5つを頭の片隅に入れておけば問題ないかと思います。
・ソケット
・IPアドレス
・ポート番号
・サーバー
・クライアント
実際にプログラムを作成してみたほうがわかりやすいと思います。
ネットワークプログラミング
pythonを使って簡単なネットワークプログラムを作成していきます。
ここではクライアント機能とサーバー機能を一つにしたプログラムを作成します。
python2とpython3で一部記述を買えなければいけない部分があるため、
その部分はコメントとして記載しました。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
from socket import * from sys import * import threading ## data send and receive for server def serverLoop(soc, addr): while True: # python3 res = soc.recv(4096).decode() # python2 #res = soc.recv(4096) if res == "exit": soc.close() print("[*] Connection Closed {}:{}".format(addr[0], addr[1])) return #python3 soc.send(">Server echo : {}".format(res).encode()) #python2 #soc.send(">Server echo : {}".format(res)) ## connection request from client def acceptConnect(soc): while True: acceptSoc, addr = soc.accept() print("[*] Connection from {}:{}".format(addr[0], addr[1])) th = threading.Thread(target=serverLoop, args=(acceptSoc, addr)) th.start() ## data send and receive for client def dataSendRecv(soc): while True: #python3 line = input(">") #python2 #line = raw_input(">") if line == "exit": #python3 soc.send(line.encode()) #python2 #soc.send(line) soc.close() return #python3 soc.send(line.encode()) #python2 #soc.send(line) # python3 print(soc.recv(4096).decode()) # python2 #print(soc.recv(4096)) ## make server socket def makeServerSocket(ip, port): soc = socket(AF_INET, SOCK_STREAM) soc.bind((ip, port)) soc.listen(SOMAXCONN) print("[*] Listen on {}:{}".format(ip, port)) return soc ## make client socket def makeClientSocket(ip, port): soc = socket(AF_INET, SOCK_STREAM) soc.connect((ip, port)) print("[*] Connect to {}:{}".format(ip, port)) return soc def main(): if len(argv) < 4: print("[*] Enter [IP Address] [Port Number] [mode]") exit(0) if int(argv[3]) == 0: soc = makeClientSocket(argv[1], int(argv[2])) dataSendRecv(soc) elif int(argv[3]) == 1: soc = makeServerSocket(argv[1], int(argv[2])) acceptConnect(soc) if __name__ == '__main__': main() |
プログラムを実行するときの引数によってクライアントで起動するかサーバーとして起動するか選択できます。
このように実行します。
1 2 3 4 |
クライアントとして起動 python ExClientServer.py 192.168.11.11 9999 0 サーバーとして起動 python ExClientServer.py 192.168.11.11 9999 1 |
第一引数がIPアドレスです。
クライアントとして起動する場合、通信先のIPアドレスを選択します。
サーバーとして起動する場合、通信を待ち受けるIPアドレスを選択します。
マシンのIPアドレスは
linuxとmacはifconfig
windowsはipconfig
で確認できます。
第二引数がポート番号です。
クライアントとして起動する場合接続するポート番号、
サーバーとして起動する場合接続を待ち受けるポート番号を起動します。
動作を確認してみる
動作の確認をしてみます。
環境はlinuxです。
ターミナルを2つ立ち上げます。
まずプログラムをサーバーとして起動します。
クライアントから接続要求が来るまで待ちます
1 2 3 |
$ python ExClientServer.py 192.168.11.11 9999 1 [*] Listen on 192.168.11.11:9999 |
もうひとつのターミナルでプログラムをクライアントとして起動します。
接続が完了したことを知らせるメッセージと、
入力を受け付けるプロンプトが表示されます。
これからクライアントは192.168.11.11というIPアドレスの9999というポートに向けてメッセージを送ることになります。
1 2 3 4 |
$ python ExClientServer.py 192.168.11.11 9999 0 [*] Connect to 192.168.11.11:9999 > |
サーバーのターミナルに戻ってみると、
クライアントから接続が来たことを知らせるメッセージが表示されます。
これからサーバーはIPアドレスの60262というポートに向けてメッセージを送ることになります。
1 2 3 4 |
$ python ExClientServer.py 192.168.11.11 9999 1 [*] Listen on 192.168.11.11:9999 [*] Connection from 192.168.11.11:60262 |
クライアントのターミナルで何か文字を入力しEnterを押すと、
サーバーから
「Server echo : {入力した文字列}」
という形式でメッセージが届きます
解説
makeClientSocket関数とmakeServerSocket関数が先に述べたソケットを作っています。
soc = socket(AF_INET, SOCK_STREAM)
の部分ですね。
第一引数はネットワークアドレスの種類です。
AF_INETはIPv4ネットワークアドレスを使用することを表します。
第二引数はソケットの方を指定します。
tcp通信を行いたい場合、SOCK_STREAMを指定します。
udp通信を行いたい場合、SOCK_DGRAMを指定します。
これらの引数については他にもいろいろあるので興味があれば調べる感じでいいかと思います。
そして次の行からクライアントとサーバーとで異なってきます。
ますはクライアントから。
soc.connect((ip, port))
これは指定したIPアドレスとポートに対して接続する処理です。
この一行でサーバーとコネクションが接続されました。
そのころサーバーはというとこんなことをしていました。
soc.bind((ip, port))
soc.listen(SOMAXCONN)
bind関数にbindしたいIPアドレスとポート番号を指定します。
そしてlisten関数で接続待ちのキューを指定します。
引数のSOMAXXCONNはシステムに設定されている接続待ちのキューを表してます。
linuxだと128だそうです。
そしてacceptConnect関数のなかの、
acceptSoc, addr = soc.accept()
で接続を待ち受けます。
これは接続を受け付けるまで処理をブロック(その後の処理を行わない)します。
接続を受け付けるのはいつかというと、
今回でいうとクライアント側でsoc.connect((ip, port))が実行されたあとになります。
この関数が戻り値がふたつありacceptSocにクライアントとの通信用のソケットを格納され、
addrには接続してきたクライアントの情報がリスト型で格納されます。
あとはメッセージをやり取りしているだけです。
メッセージを送りたい場合はsend()関数、
受け取りたい場合はrecv()関数を使います。
ただpython3の場合、send()でデータを送る際、それをエンコードしなければいけないそうでなので、その部分の記述がpython2と異なっています。
本記事ではTCPソケットを使いました。
もう少し体系的に学びたい場合以下の書籍がいいかもしれません。
TCPだけでなくUDPやWeb系のネットワークプログラムの作り方を学ぶことができます。
最後に
pythonでネットワークプログラムのサンプルを作りました。
pythonなので割とシンプルに出来たかなと思います。
ITってインターネットが軸になっているところもあると思うので、ネットワークについて学んでおくのは得策かなと思います。
以前にARPポイズニングの記事について書きましたが、
ネットワークの知識って仕組みを理解することは簡単だがそこそこ役に立つことが多いんですよね
もちろん僕の知識はその道のプロに比べたら知識の量は大きく劣りますが。
今回は簡単なプログラムを作りましたがこれを少し発展させると色々作れます。