在网络编程中的一个基本组件就是套接字(socket)。套接字基本上是两个端点的程序之间的“信息通道”。程序可能分布在不同的计算机上,通过套接字互相发送信息。套接字包括两个:服务器套接字和客户机套接字。在创建一个服务器套接字后,让它等待连接。这样它就在某个网络地址处(ip地址和端口的组合)监听,直到有客户端套接字连接。连接完成后,二者就可以进行交互了。
socket套接字
一个套接字就是socket模块中socket类的一个实例。它的实例化需要3个参数:
第1个参数是地址族,默认是socket.AF_INET(指定使用IPv4协议)
第2个参数是流,默认是socket.SOCK_STREAM(指定面向流的TCP协议),或者数据报socket.SOCK_DGRAM套接字。
第3个参数是使用的协议,默认是0
import socket
# s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 这里我们全部使用默认值就行了
s=socket.socket()
服务器套接字使用bind方法后,再调用listen方法去监听某个特定的地址。
s.bind(('127.0.0.1',9999))#绑定地址和端口
s.listen(5)#服务器开始监听客户端连接,允许排队等待的连接数目是5,一般都设5
端口是自己设的,ip我用的是本机的ip地址,当然如果服务器程序在其他主机上,就要换成其他的ip。端口号低于1024一般都被限制使用,它们被用于标准服务,例如端口80用于web服务。
服务器套接字开始监听后,它就静静的等待客户端的连接了。这个步骤使用accept方法完成,这个方法会阻塞直到客户端的连接。该方法返回一个格式为(client,address)的元组,client是客户端套接字,address是格式为(host,port)的元组。服务器在处理完与该客户端的连接后,再次调用accept方法开始等待下个连接,通常这个过程用一个无限循环实现。
while True:
#接受一个客户端连接
sock,addr=s.accept()#addr是个元组('127.0.0.1',端口)
#接下来可以创建新线程来处理当前客户端的连接
现在我们来看客户端怎么连接服务器,客户端套接字使用connect方法连接服务器,在connect方法中使用的地址必须和服务器在bind方法中绑定的地址相同。
import socket
c=socket.socket()#创建客户端套接字
c.connect(('127.0.0.1',9999))#连接服务器
套接字有两个方法:send和recv,分别用来发送和接收数据。send方法以字符串作为参数,recv用一个最大字节数来作为参数,如果不确定,使用1024比较好。
c.send('hello,I am xxx!')
c.recv(1024)
基础内容介绍完后,我们现在来看个完整的例子,客户端连接服务器,客户端向服务器发送一组名字xxx,然后服务器回复hello,xxx!。
#服务器
import socket
import threading
#处理客户端请求
def tcplink(sock,addr):
print('Accept new connection from %s:%s...' % addr)
sock.send('welcome!')
while True:
data=sock.recv(1024)
if data=='exit' or not data:
break;
sock.send('hello %s' % data)
sock.close()
print('Connection from %s:%s closed' % addr)
s=socket.socket()
s.bind(('127.0.0.1',9999))#绑定地址和端口
s.listen(5)
print('serve is waiting connect.....')
while True:
sock,addr=s.accept()
#创建新线程来处理每个客户端连接
#target=tcplink代表新线程要运行哪个函数
#args=(sock,addr)代表向这个方法传的参数
t=threading.Thread(target=tcplink,args=(sock,addr))
#启动这个线程
t.start()
#客户端
import socket
import threading
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立连接
s.connect(('127.0.0.1',9999))
data=s.recv(1024)
print(data)
for i in ['zhangkang','jack','tom']:
s.send(i)
data=s.recv(1024)
print(data)
s.send('exit')
s.close()
TCP建立的是可靠的连接,而UDP是无连接,不可靠的。使用UDP的时候,只需要知道目标主机的ip和端口就行了,然后直接发送,能不能到达就不一定了。
和TCP类型,服务器套接字需要先实例,然后绑定地址。
#这里第二个参数选择数据报
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
但是服务器不需要调用listen()方法,而是直接接收客户端的数据
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print('Received from %s:%s' % addr)
s.sendto('Hello, %s!' % data, addr)
recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['zhangkang', 'jack', 'tom']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print s.recv(1024)
s.close()
从服务器接收数据仍然调用recv()方法。
简单的聊天室程序
这里我写了一个简易的类似聊天室的程序,没有使用图形界面,全部消息都显示在控制台。
#服务器
# -*- encoding: utf-8 -*-
import socket
import threading
def tcplink(sock,addr):
print('[%s:%s] is online...' % addr)
while True:
try:
data=sock.recv(1024)
except:
socket_set.remove(sock)
print('[%s:%s] is down!' % addr)
break
if data=='exit' or not data:
socket_set.remove(sock)
sock.close()
print('[%s:%s] is down!' % addr)
break
else:
list1=[]
for i in socket_set:
if i!=sock:
list1.append(i)
for i in list1:
i.send(data)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket_set=set()#用来保存每个socket对象
s.bind(('127.0.0.1',9999))#绑定地址和端口
s.listen(5)
print('serve is waiting connect.....')
while True:
#接受一个客户端连接
sock,addr=s.accept()#addr是个元组('127.0.0.1',端口)
socket_set.add(sock)#把socket对象添加到集合中
#创建新线程来处理TCP连接
t=threading.Thread(target=tcplink,args=(sock,addr))
t.start()
#客户端
# -*- encoding: utf-8 -*-
import socket
import threading
name=raw_input('input you name:')
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立连接
s.connect(('127.0.0.1',9999))
def rec_message():
while True:
try:
data=s.recv(1024)
print('
['+data+']')
except:
break
t=threading.Thread(target=rec_message)
t.start()
while True:
st=raw_input()
if st=='exit':
s.send(st)
s.close()
break
s.send(name+":"+st)
客户端逐个下线