使用socket可以进行基于套接字的底层网络编程。
socket对象是网络通信的基础,相当于一个管道连接了发送端和接收端,并在两者之间相互传递数据。
五子棋游戏介绍:程序分为客户端和服务端。服务器首先启动,客户端寻找连接成功后,开始游戏。
客户端运行后,输入服务端iip地址,如果连接成功,可以在轮到自己下棋时行动,也可以通过退出游戏按钮退出。
进行网络编程,要对网络一些协议有认识。
端口的作用:在两台计算机通信时,只有ip地址是不够的,因为同一台计算机运行着多个网络程序(如qq、浏览器、微信等)。一个ip数据包到来后,到底是发给qq还是浏览器或微信。就需要通过端口号进行区分。
socket是tcp/ip网络最通用的API,任何网络通信都是靠socket来完成的。
创建TCP socket的语法如下:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket.AF_INET:用于服务器之间的通信,ipv4协议。
socket.SOCK_STREAM:用于TCP协议。
创建UDP socket的语法如下:
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.SOCK_DGRAM:用于UDP协议。
对于tcp,首先服务器启动,服务器和客户机都必须调用socket()建立一个套接字socket,服务器调用bind()将套接字与本机指定端口绑定在一起,在调用listen()使套接字处于一种被动的准备接收的状态。然后客户机建立套接字便可以通过调用connect()与服务器建立连接,服务器可以调用accept()来接收客户机连接,然后继续监听指定端口,并发出阻塞,直到下一个请求出现,从而实现连接多个客户机。建立连接后,客户机和服务器之间就可以发送和接收数据。数据结束传送后,双方调用close()关闭套接字。
对于UDP,客户机通过sendto()给服务器发送数据,服务器调用revcfrom(),等待客户端传来的数据。服务器根据recvfrom()给客户机应答。
实例:tcp客户端程序:连接当当网
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象,tcp s.connect(('www.dangdang.com', 80)) # 主动建立当当网连接,一般格式为元祖(hostname,port) s.send(b'GET / HTTP/1.1\r\nHost: www.dangdang.com\r\nConnection: close\r\n\r\n') # send发送括号中的请求 buffer = [] while True: d = s.recv(1024) # 每次最多接收服务器端1k的数据 if d: buffer.append(d) # 加入到buffer列表 else: break # 返回空数据,接收完毕退出循环 data = b''.join(buffer) # b表示空字节,join()是连接列表的函数,buffer是一个字节串列表,这句就是是用空字节把buffer字节列表连接在一起成为新字符串 s.close() # 关闭连接 header, html = data.split(b'\r\n\r\n', 1) # 以\r\n\r\n分隔一次, print(header.decode('utf-8')) # 打印头内容 with open('当当.html', 'wb') as f: # 新建一个文档"当当.html" f.write(html) # 具体html文件写入文档中
结果为 :
打印头文件内容
HTTP/1.1 200 OK Server: nginx Date: Sun, 29 Nov 2020 06:37:39 GMT Content-Type: text/html; charset=GBK Transfer-Encoding: chunked Connection: close
在当前目录生成了当当.html,内容略
如果想访问https安全协议的网站如新浪,需要ssl协议,将上述代码改为:
import socket import ssl # ssl协议需要ssl模块 # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象,tcp sock = ssl.wrap_socket(socket.socket()) # 使用https的socket对象 sock.connect(('www.sina.com.cn', 443)) # https端口为443 sock.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n') # send发送括号中的请求 buffer = [] while True: d = sock.recv(1024) # 每次最多接收服务器端1k的数据 if d: buffer.append(d) # 加入到buffer列表 else: break # 返回空数据,接收完毕退出循环 data = b''.join(buffer) # b表示空字节,join()是连接列表的函数,buffer是一个字节串列表,这句就是是用空字节把buffer字节列表连接在一起成为新字符串 sock.close() # 关闭连接 header, html = data.split(b'\r\n\r\n', 1) # 以\r\n\r\n分隔一次, print(header.decode('utf-8')) # 打印头内容 with open('sina.html', 'wb') as f: f.write(html) # 具体html文件写入文档中
实例:tcp服务端程序
tcp服务端相比tcp客户端程序要复杂一些。服务端首先要绑定一个端口用于监听其他客户端的连接。同时服务器还要相应多个客户端的请求,因此每个连接都需要一个新的进程或线程来处理。
编写一个简单的tcp服务端程序,接收客户端请求,将客户端发过来的字符加上Hello再发送回去。
import socket import time import threading def tcplink(sock, addr): print("接受一个来自%s:%s的连接请求" % addr) sock.send(b'Welcome!') # 发送给客户端welcome信息 while True: data = sock.recv(1024) # 接收客户端信息 time.sleep(1) if not data or data.decode('utf-8') == 'exit': break # 如果没有数据或者接受到exit,则终止接受 sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8')) # 收到的信息加上hello在发回去 sock.close() print("来自%s:%s的连接关闭了" % addr) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('127.0.0.1', 8888)) # 绑定并监听本机8888端口,只有本机客户端才能连接 s.listen(5) # 连接最大数量为5 print("等待客户连接:") while True: sock, addr = s.accept() # 接受一个新链接,accpet()会等待并返回一个客户端的连接 t = threading.Thread(target=tcplink, args=(sock, addr)) # 创建新线程处理tcp连接 t.start()
此时需要一个测试程序:
import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('127.0.0.1',8888)) #建立链接 print(s.recv(1024).decode('utf-8')) for data in [b'Michael',b'Tracy',b'Sarah']: s.send(data) print(s.recv(1024).decode('utf-8')) s.send(b'exit') s.close()
注意服务器一个端口被socket绑定后,就不能被其他socket绑定。客户端连接后关闭,服务器端不会自己退出。
UDP编程
之前都是tcp连接方式,udp不需要建立连接,只要知道对方的ip地址和端口号就可以直接发送数据包。
实例:写一个简单的下棋程序,服务端把udp客户端发来的下棋坐标(x,y)信息显示出来,并把(x,y)坐标加1(模拟服务端下棋),再发给客户端。
服务端:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('127.0.0.1', 8888)) # 绑定到8888端口,注意127.0.0.1是特殊地址,如果客户端在另一台电脑,则需要准确的本机ip地址绑定(如192.168.0.X) print('绑定udp到端口8888...') while True: data, addr = s.recvfrom(1024) print('从%s:%s接受数据' % addr) print('已接收', data) p = data.decode('utf-8').split(',') # 将接收的字符串解码 x = int(p[0]) y = int(p[1]) print(p[0], p[1]) pos = str(x + 1) + ',' + str(y + 1) # 模拟服务端下棋的位置 s.sendto(pos.encode('utf-8'), addr) # 发回客户端
客户端:
import socket s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) x=input('输入x坐标') y=input('输入y坐标') data=str(x)+','+str(y) s.sendto(data.encode('utf-8'),('127.0.0.1',8888))#将字符串转为传送的字符串,如客户端不在本机,则需要服务器准确的地址 data2,addr=s.recvfrom(1024) #接收服务器返回的数据 print('接收服务器加1后坐标数据:',data2.decode('utf-8')) s.close()
recvfrom()方法接收数据和客户端的地址和端口,以及具体数据
sendto()方法可以通过udp发送数据
以下为实例代码:
服务器端:
# -*- coding: utf-8 -*- # @Time : 2020/11/29 17:22 # @Author : Zhenghui Lyu # @FileName: wuziqiserver.py # @Email : lzhfootmark@163.com # @Software: PyCharm # @Blog :https://www.cnblogs.com/ZhenghuiLyu from tkinter import * from tkinter.messagebox import * import socket import threading import os def drawQiPan(): # 绘制15*15的棋盘 for i in range(0, 15): cv.create_line(20, 20 + 40 * i, 580, 20 + 40 * i, width=2) for i in range(0, 15): cv.create_line(20 + 40 * i, 20, 20 + 40 * i, 580, width=2) cv.pack() def checkWin(x, y): '''检查是否胜利,检查当前坐标相连情况''' flag = False # 胜利标志 color = map[x][y] # print('color值为:', color) i = 1 count = 1 # 统计相连棋子 while color == map[x + i][y]: # 右横向连接相同 count += 1 i += 1 i = 1 while color == map[x - i][y]: # 左横向连接相同 count += 1 i += 1 if count >= 5: flag = True # 连5则胜利 i2 = 1 count2 = 1 while color == map[x][y + i2]: # 统计纵向上连接 count2 = count2 + 1 i2 = i2 + 1 i2 = 1 while color == map[x][y - i2]: # 统计纵向下连接 count2 = count2 + 1 i2 = i2 + 1 if count2 >= 5: flag = True i3 = 1 count3 = 1 while color == map[x + i3][y - i3]: # 右上,左下连接 count3 += 1 i3 += 1 i3 = 1 while color == map[x - i3][y + i3]: count3 += 1 i3 += 1 if count >= 5: flag = True i4 = 1 count4 = 1 while color == map[x + i4][y + i4]: # 右下,左上连接 count4 = count4 + 1 i4 += 1 i4 = 1 while color == map[x - i4][y - i4]: count4 += 1 i4 += 1 if count4 >= 5: flag = True return flag def sendMessage(pos): global s global addr s.sendto(pos.encode(), addr) # 按照协议将字符串发出 def callpos(event): global turn global Myturn # 服务器端下白棋 if Myturn == -1: Myturn = turn else: if (Myturn != turn): showinfo(title='提示', message='还没轮到自己走棋') return x = (event.x) // 40 # 换算棋盘坐标 y = (event.y) // 40 print("点击在", x, ',', y) if map[x][y] != " ": showinfo(title='提示', message='wrong,已有棋子') else: img1 = imgs[turn] cv.create_image((x * 40 + 20, y * 40 + 20), image=img1) cv.pack() map[x][y] = str(turn) pos = str(x) + ',' + str(y) sendMessage('move|' + pos) print('服务器走棋的位置', pos) label1['text'] = "服务器走棋的位置" + pos if checkWin(x, y): if turn == 0: showinfo(title='提示', message='黑方赢了') sendMessage('over|黑方赢了') else: showinfo(title='提示', message='白方赢了') sendMessage('over|白方赢了') if turn == 0: turn = 1 else: turn = 0 print('Myturn值为:', Myturn) print('turn值为:', turn) def callexit(event): pos = '退出!' sendMessage(pos) os._exit(0) def drawOtherChess(x, y): global turn img1 = imgs[turn] cv.create_image((x * 40 + 20, y * 40 + 20), image=img1) cv.pack() # 绘制图形 map[x][y] = str(turn) # 0,1组成期盼逻辑 if turn == 0: turn = 1 else: turn = 0 def receiveMessage(): global s while True: global addr data, addr = s.recvfrom(1024) # 从发送方接收数据,接收到的数据data为字符串类型,保存在data中,从对方发送过来的地址保存在addr中。1024表示一次最多接收1024个字节。 data = data.decode('utf-8') print(data) a = data.split('|') # 分隔数据为列表,|前面可能是move/over/join/exit等 if not data: print("客户端退出") break elif a[0] == 'join': print("客户端连接至服务器") label1['text'] = '连接服务器成功,请你走棋!' elif a[0] == 'exit': print('客户端退出,游戏结束!') elif a[0] == 'over': print("对方胜利!") label1['text'] = data.split('|')[0] showinfo(title='提示', message=data.split('|')[1]) elif a[0] == 'move': print("接收到", data, '来自', addr) p = a[1].split(',') # 客户端走的信息,如move|7,4 ,则在(7,4)坐标下棋 x = int(p[0]) y = int(p[1]) print(p[0], p[1]) label1['text'] = "客户端走的位置是" + p[0] + ',' + p[1] drawOtherChess(x, y) # 调用画棋函数 s.close() def startNewThread(): thread = threading.Thread(target=receiveMessage, args=()) thread.setDaemon(True) thread.start() root = Tk() root.title('网络五子棋v2.0--服务器端') imgs = [PhotoImage(file='blackstone.gif'), PhotoImage(file='whitestone.gif')] turn = 0 # 0为黑方,1为白 Myturn = -1 map = [[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] for y in range(15)] cv = Canvas(root, bg='green', width=610, height=610) drawQiPan() # 画出棋盘 cv.bind('<Button-1>', callpos) # 点击引发下棋事件 cv.pack() label1 = Label(root, text='服务器端...等待连接') label1.pack() button1 = Button(root, text='退出游戏') button1.bind('<Button-1>', callexit) button1.pack() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('192.168.0.11', 8000)) addr = ('localhost', 8000) startNewThread() root.mainloop()
客户端:
# -*- coding: utf-8 -*- # @Time : 2020/11/30 16:05 # @Author : Zhenghui Lyu # @FileName: wuziqiclient.py.py # @Email : lzhfootmark@163.com # @Software: PyCharm # @Blog :https://www.cnblogs.com/ZhenghuiLyu from tkinter import * from tkinter.messagebox import * import socket import threading import os def drawQiPan(): # 绘制15*15的棋盘 for i in range(0, 15): cv.create_line(20, 20 + 40 * i, 580, 20 + 40 * i, width=2) for i in range(0, 15): cv.create_line(20 + 40 * i, 20, 20 + 40 * i, 580, width=2) cv.pack() def checkWin(x, y): '''检查是否胜利,检查当前坐标相连情况''' flag = False # 胜利标志 color = map[x][y] i = 1 count = 1 # 统计相连棋子 while color == map[x + i][y]: # 右横向连接相同 count += 1 i += 1 i = 1 while color == map[x - i][y]: # 左横向连接相同 count += 1 i += 1 if count >= 5: flag = True # 连5则胜利 i2 = 1 count2 = 1 while color == map[x][y + i2]: # 统计纵向上连接 count2 = count2 + 1 i2 = i2 + 1 i2 = 1 while color == map[x][y - i2]: # 统计纵向下连接 count2 = count2 + 1 i2 = i2 + 1 if count2 >= 5: flag = True i3 = 1 count3 = 1 while color == map[x + i3][y - i3]: # 右上,左下连接 count3 += 1 i3 += 1 i3 = 1 while color == map[x - i3][y + i3]: count3 += 1 i3 += 1 if count >= 5: flag = True i4 = 1 count4 = 1 while color == map[x + i4][y + i4]: # 右下,左上连接 count4 = count4 + 1 i4 += 1 i4 = 1 while color == map[x - i4][y - i4]: count4 += 1 i4 += 1 if count4 >= 5: flag = True return flag def sendMessage(pos): global s # global addr # s.sendto(pos.encode(), addr) # 按照协议将字符串发出 s.sendto(pos.encode(), (host, port)) def callback(event): global turn global Myturn if Myturn == -1: Myturn = turn else: if Myturn != turn: showinfo(title='提示', message='还没轮到自己走棋') return x = (event.x) // 40 # 换算棋盘坐标 y = (event.y) // 40 print("点击在", x,',', y) if map[x][y] != " ": showinfo(title='提示', message='已有棋子') else: img1 = imgs[turn] cv.create_image((x * 40 + 20, y * 40 + 20), image=img1) cv.pack() map[x][y] = str(turn) pos = str(x) + ',' + str(y) sendMessage('move|' + pos) print('客户端走棋的位置' + pos) label1['text'] = "客户端走棋的位置" + pos if checkWin(x, y): if turn == 0: showinfo(title='提示', message='黑方你赢了') sendMessage('over|黑方赢了') else: showinfo(title='提示', message='白方你赢了') sendMessage('over|白方赢了') if turn == 0: turn = 1 else: turn = 0 def callexit(event): pos = '退出!' sendMessage(pos) os._exit(0) def drawOtherChess(x, y): global turn img1 = imgs[turn] cv.create_image((x * 40 + 20, y * 40 + 20), image=img1) cv.pack() # 绘制图形 map[x][y] = str(turn) # 0,1组成期盼逻辑 if turn == 0: turn = 1 else: turn = 0 def receiveMessage(): global s while True: # global addr # data, addr = s.recvfrom(1024) data = s.recv(1024).decode('utf-8') a = data.split('|') # 分隔数据为列表,|前面可能是move/over/join/exit等 if not data: print("服务器退出") break # elif a[0] == 'join': # print("客户端连接至服务器") # label1['text'] = '连接服务器成功,请你走棋!' elif a[0] == 'exit': print('对方退出,游戏结束!') elif a[0] == 'over': print("对方胜利!") label1['text'] = data.split('|')[0] showinfo(title='提示', message=data.split('|')[1]) elif a[0] == 'move': print("接收到", data) p = a[1].split(',') x = int(p[0]) y = int(p[1]) print(p[0], p[1]) label1['text'] = "服务器走的位置是" + p[0] +','+ p[1] drawOtherChess(x, y) # 调用画棋函数 s.close() def startNewThread(): thread = threading.Thread(target=receiveMessage, args=()) thread.setDaemon(True) thread.start() root = Tk() root.title('网络五子棋v2.0--客户端') imgs = [PhotoImage(file='blackstone.gif'), PhotoImage(file='whitestone.gif')] turn = 0 # 0为黑方,1为白 Myturn = -1 map = [[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] for y in range(15)] cv = Canvas(root, bg='green', width=610, height=610) drawQiPan() cv.bind('<Button-1>', callback) cv.pack() label1 = Label(root, text='客户端...') label1.pack() button1 = Button(root, text='退出游戏') button1.bind("<Button-1>", callexit) button1.pack() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) port = 8000 host = '192.168.0.11' pos = 'join|' sendMessage(pos) startNewThread() root.mainloop()
注意:checkWIn函数有bug,此程序仅作示例用途。
浙公网安备 33010602011771号