使用Socke协议编写局域网聊天程序 and 实现HTTP请求

概述

简单的说,我们都知道http/smtp/dns/ftp,这些协议都是基于网络模型中的应用层,那么socket是什么呢? socket本质上讲的基于应用层以下的传输层(TCP/UDP)中的一个请求。
例如我们想要进行一个聊天程序的设计,如果我们采用HTTP协议,我们都知道HTTP协议中,当客户端发送给服务端数据之后,就会进行关闭,不能持续性链接。因此我们在设计聊天程序的时候,不可能一直监听是否有HTTP请求,这样是非常麻烦的。
所以这个时候我们就需要使用socket进行编写。
我们使用Python中的socket库。

socket库的使用

Snipaste_2020-03-23_14-15-40.png
首先我们需要先创建一个socket对象。

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

其中第一个参数代表,使用ipv4,其二是指使用TCP协议。

然后我们通过socket对象下面的bind函数绑定一个地址与端口。

s.bind(('0.0.0.0', 8000))
s.listen(5)

这里需要注意,如果我们写成127.0.0.1,它就只基于当前本机的一个地址,局域网是无法访问到的,所以我们写成0.0.0.0
之后使用listen函数设置连接,其中5代表,最大连接数为5。当然,这个根据你的情况进行编写。

看到这里,你应该已经猜到了,我们接下来是要编写一个服务端的程序。那么我们接下来就应该是处理客户端发送的请求。
自然,我们需要先获得服务端的连接。

c, addr = s.accept()

我们使用accept函数获取连接请求,它会返回两个参数,其一是用来响应目标的对象,其二是目标的地址。当服务端发送请求的时候,我们就可以使用第一个参数进行获得请求,并且使用这个对象进行响应。

data = c.recv(1024)
print(data.decode('utf8'))
reDate = raw_input()
c.send(reDate.encode('utf8'))

通过第一个参数c下的recv函数进行接收数据,其中参数的意思是一次接收多少字节的数据。
然后通过send函数进行响应数据。
值得注意的是我们需要使用utf8的方式对其解码与编码

以上便是我们服务端的代码。客户端实际上大同小异,无非是连接到客户端,然后对其发送、响应。

Client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Client.connect(('127.0.0.1', 8000))
while True:
    reData = raw_input()
    Client.send(reData.encode("utf8"))
    Data = Client.recv(1024)
    print(Data.decode('utf8'))

使用connect函数进行连接服务端,这里就需要写成127.0.0.1了。之所以代码中写一个while是因为想要让它一直循环下去,做出类似于聊天程序的效果。

完整代码

server

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 8000))
s.listen(5)
c, addr = s.accept()
while True:
    data = c.recv(1024)
    print(data.decode('utf8'))
    reDate = raw_input()
    c.send(reDate.encode('utf8'))

client

import socket

Client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Client.connect(('127.0.0.1', 8000))
while True:
    reData = raw_input()
    Client.send(reData.encode("utf8"))
    Data = Client.recv(1024)
    print(Data.decode('utf8'))

聊天程序

实际上当学习玩前面的知识后,几乎我们已经有能力制作一个简易的聊天工具了~ 不过如果你想制作一个多对多用户聊天的,那你就有必要了解一下线程相关的知识。
视频演示

server

    # coding=utf-8
import socket
import threading
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

user = {}
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
port = 8000
maxUser = 5
s.bind(('0.0.0.0', port))
print 'server start success!'
print '监听{}端口.最多承载{}人。'.format(port, maxUser)
print '————————————————————————'
s.listen(maxUser)


def sent(name, content, c):
    print("向{}发送信息:{}".format(name, content))
    if not user.has_key(name):
        c.send('对方不在线!'.encode('utf8'))
    else:
        user[name].send(content.encode('utf8'))


def init(name, c):
    user[name] = c


def handleSock(c, addr):
    while True:
        data = c.recv(1024).decode('utf8')
        target = data[data.index("[") + 1:data.index("]")]
        content = data[data.index("]") + 1:len(data)]
        if target == 'login':
            init(name=content, c=c)
            c.send('{} login succss'.format(content).encode('utf8'))
        else:
            name = data[data.index("{") + 1:data.index("}")]
            content = name + ':' + content
            sent(name=target, content=content, c=c)


if __name__ == '__main__':
    while True:
        c, addr = s.accept()
        clientThread = threading.Thread(target=handleSock, args=(c, addr))
        clientThread.start()

client

    # coding=utf-8
import socket
import threading
import time
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

Client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
re = Client.connect_ex(('127.0.0.1', 8000))
while re != 0:
    print '服务器不在线。'
    time.sleep(1)
    re = Client.connect_ex(('127.0.0.1', 8000))


name = raw_input('输入你的名称:')
Client.send('[login]{}'.format(name).encode('utf8'))


def getMsg():
    while True:
        Data = Client.recv(1024)
        print(Data.decode('utf8'))


if __name__ == '__main__':

    getMsgThread = threading.Thread(target=getMsg)
    getMsgThread.start()
    time.sleep(1)
    tarGetName = raw_input('好友名称:')
    tarGetName = '{' + name + '}' + '[' + tarGetName.strip() + ']'
    print '×-----------------------------------------×'
    print '|         「KuM」 blog: ku-m.cn            |'
    print '×-----------------------------------------×'
    while True:
        order = raw_input()
        if order == 'quit':
            break
        reData = tarGetName + order

        Client.send(reData.encode("utf8"))

模拟HTTP请求

以上我们简单的使用了Python中的socket库。我们知道socket与http的区别似乎就是一个是响应后就断掉,一个是可以进行持久性链接。那么我们通过socket库是否可以发起http请求呢?答案自然是可以的。

首先我们拿到一个域名需要先对它解析,解析成域名(host)与路径(path),因为我们后面需要按照http协议格式的数据包会用到。我们可以引入urlparse包,当然也可以自己进行修改。

def getHtml(url):
    url = urlparse.urlparse(url)
    host = url.netloc
    path = url.path
    if path == "":
        path = "/"
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, 80))
    s.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path,         host).encode('utf8'))
    Data = b''
    while True:
        d = s.recv(1024)
        if d:
            Data += d
        else:
            break
    Data = Data.decode('utf8')
    return Data

值得注意的是,我们的recv函数每次都只获取1024个字节,所以我们需要循环进行获得,然后才能拿到完整的数据。而send函数中写入的便是完整的一个http协议的格式,这里就不再赘述,请自行百度。