Socket网络编程-TCP编程

                                   作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.socket介绍

1>.TCP/IP协议

2>.跨网络的主机间通讯

  在建立通信连接的每一端,进程间的传输要有两个标志:

  IP地址和端口号,合称为套接字地址 socket address

  客户机套接字地址定义了一个唯一的客户进程

  服务器套接字地址定义了一个唯一的服务器进程

3>.什么是socket套接字

  套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

  Socket:套接字,进程间通信IPC的一种实现,允许位于不同主机(或同一主机)上不同进程之间进行通信和数据交换,SocketAPI出现于1983年,4.2 BSD实现。

  Socket API:封装了内核中所提供的socket通信相关的系统调用。

  Python中提供了socket标准库,非常底层的接口库。

4>.协议族(Socket Domain)

AF表示Address Family,用于socket()第一个参数
  AF_INET:
    对应IPV4
  AF_INET6
    对应IPV6
  AF_UNIX
    同一主机不同进程之间通信时使用,对应Unix Domain Socket,windows没有。

5>.socket Type(根据使用的传输层协议)

SOCK_STREAM
  可靠的传递,面向连接的流套接字。默认值,TCP协议。
SOCK_DGRAM
  不可靠的传递,无连接的数据报文套接字。UDP协议。
SOCK_RAW:
  裸套接字,无须tcp或udp,APP直接通过IP包通信

6>.C/S编程

  Socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。 这种编程模式也称为C/S编程。

  套接字相关的系统调用:
    socket(): 创建一个套接字
    bind(): 绑定IP和端口
    listen(): 监听
    accept(): 接收请求
    connect(): 请求连接建立
    write(): 发送
    read(): 接收
    close(): 关闭连接

7>.python中的socket常用方法

  socket.recv(bufsize[, flags])
    获取数据。默认是阻塞的方式

  socket.recvfrom(bufsize[, flags])
    获取数据,返回一个二元组(bytes, address)

  socket.recv_into(buffer[, nbytes[, flags]])
    获取到nbytes的数据后,存储到buffer中。如果 nbytes没有指定或0,将buffer大小的数据存入buffer中。返回接收的字节数。

  socket.recvfrom_into(buffer[, nbytes[, flags]])
    获取数据,返回一个二元组(bytes, address)到buffer中

  socket.send(bytes[, flags])
    TCP发送数据

  socket.sendall(bytes[, flags])
    TCP发送全部数据,成功返回None

  socket.sendto(string[,flag],address)
    UDP发送数据

  socket.sendfile(file, offset=0, count=None)
    python 3.5版本开始,发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数。如果win下不支持sendfile, 或者不是普通文件,使用send()发送文件。offset告诉 起始位置。   socket.getpeername()
    返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)

  socket.getsockname()
    返回套接字自己的地址。通常是一个元组(ipaddr,port)

  socket.setblocking(flag)
    如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值),非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常

  socket.settimeout(value)
    设置套接字操作的超时期,timeout是一个浮点数,单位是 秒。值为None表示没有超时期。一般,超时期应该在刚创 建套接字时设置,因为它们可能用于连接的操作(如 connect())

  socket.setsockopt(level,optname,value)
    设置套接字选项的值。比如缓冲区大小。太多了,去看文档。不同系统,不同版本都不尽相同   socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None)
    创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做写方法。

二.TCP服务端编程

1>.服务器端编程步骤

  创建Socket对象 

  绑定IP地址Address和端口Port。bind()方法
    IPv4地址为一个二元组('IP地址字符串', Port) 开始监听,将在指定的IP的端口上监听。listen()方法   获取用于传送数据的Socket对象
    socket.accept() -> (socket object, address info)
      accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组 地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr, port)
      接收数据
        recv(bufsize[, flags]) 使用缓冲区接收数据
      发送数据
        send(bytes)发送数据   Server端开发
    socket对象 --> bind((IP, PORT)) --> listen --> accept --> close
     |--> recv or send --> close

2>.实战案例写一个群聊程序的服务端ChatServer

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie import logging
import socket
import threading
import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s %(thread)d %(message)s") """
注意,这个代码为实验用,代码中瑕疵还有很多。Socket太底层了,实际开发中很少使用这么底层的接 口。
"""
class ChatServer:
def __init__(self,ip="127.0.0.1",port=6666):
self.conn = socket.socket() #创建socket对象
self.addr = (ip,port)
self.clients = {} #存放客户端连接容器
self.event = threading.Event() #判断服务是否启动
self.lock = threading.Lock() #为了线程安全而引入的 def start(self): #启动服务,会监听IP地址和端口哟~
self.conn.bind(self.addr) #将IP地址和端口绑定到套接字上
self.conn.listen() #监听绑定的套接字 #accept会阻塞主线程,所以开一个新线程
threading.Thread(target=self.accept).start() def accept(self): #处理客户端的链接
while not self.event.is_set():
conn,client = self.conn.accept() #该方法默认会进入阻塞状态
f = conn.makefile("rw") #将socket封装成一个文件对象来操作,支持读写
with self.lock:
self.clients[client] = f,conn #如果有新链接就添加到客户端字典 #准备接收数据,recv是阻塞的,开启新的线程
threading.Thread(target=self.recv,args=(f,client)).start() def recv(self,f,client): #处理客户端数据
print("in recv")
while not self.event.is_set():
try:
data = f.readline()
print(data)
except Exception as e:
logging.error(e)
data = "quit" msg = data.strip()
print(msg, "++++++++++++++++++") #和客户端约定退出命令
if msg == "quit" or msg == "": #主动端口得到空串
with self.lock:
_,conn = self.clients.pop(client)
f.close()
conn.close()
logging.info("{} quit ...".format(client))
break msg = "{:%Y/%m/%d %H:%M:%S} ip:port => {}:{}\n data => {}\n".format(datetime.datetime.now(),*client, data)
logging.info(msg) with self.lock:
for f1,_ in self.clients.values():
f1.write(msg)
f1.flush() def stop(self): #停止服务
self.event.set()
with self.lock:
for f,s in self.clients.values():
f.close()
s.close()
self.conn.close() def main():
server = ChatServer()
server.start() while True:
cmd = input(">>> ").strip()
if cmd == "quit":
server.stop()
threading.Event.wait(3) #关闭服务需要等待时间
break
logging.info(threading.enumerate()) #用来观察断开后线程的变化
logging.info(server.clients) if __name__ == '__main__':
main()

三.TCP客户端编程

1>.客户端编程步骤

  创建Socket对象 

  连接到远端服务端的ip和port,connect()方法 

  传输数据  
    使用send、recv方法发送、接收数据   关闭连接,释放资源

2>.实战案例写一个群聊程序的客户端ChatClient

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie import socket
import threading
import datetime
import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO) """
同样,这样的客户端还是有些问题的,仅用于测试。
"""
class ChatClient:
def __init__(self, ip='127.0.0.1', port=6666):
self.sock = socket.socket()
self.addr = (ip, port)
self.event = threading.Event() def start(self): # 启动对远端服务器的连接
self.sock.connect(self.addr)
self.send("I'm ready.")
# 准备接收数据,recv是阻塞的,开启新的线程
threading.Thread(target=self.recv, name="recv").start() def recv(self): # 接收服务端的数据
while not self.event.is_set():
try:
data = self.sock.recv(1024) # 阻塞
except Exception as e:
logging.error(e)
break msg = "{:%Y/%m/%d %H:%M:%S} ip:port => {}:{}\n data => {}\n".format(datetime.datetime.now(),*self.addr, data.strip())
logging.info(msg) def send(self, msg:str):
data = "{}\n".format(msg.strip()).encode() # 服务端需要一个换行符
self.sock.send(data) def stop(self):
self.sock.close()
self.event.wait(3)
self.event.set()
logging.info('Client stops...') def main():
client = ChatClient()
client.start()
while True:
cmd = input('>>>')
if cmd.strip() == 'quit':
client.stop()
break
client.send(cmd) # 发送消息 if __name__ == '__main__':
main()

最新文章

  1. php中抽象类与接口的概念以及区别
  2. Android添加图片到ListView或者 RecyclerView显示
  3. 分布式系统之Quorum (NRW)算法
  4. 012. asp.net生成验证码图片(汉字示例/字母+数字)
  5. GridFS
  6. 1001 - Another A+B
  7. HTML的TextArea标记跟随文本内容自动设置高度
  8. OSS.Common获取枚举字典列表标准库支持
  9. js根据条件json生成随机json:randomjson
  10. WeQuant交易策略—KDJ
  11. 【NOIP】OpenJudge - 15-02:财务管理
  12. Java高新技术 JDK1.5之新特性
  13. <c:forEach items="${list}" var="tt" varStatus="status"> 的相关大小长度
  14. C#中引用变量是否应该加ref?
  15. js正则表达式入门以及常见用例
  16. CITS1401 Computational Thinking with Python
  17. Mac OS X 操作系统下IntelliJ IDEA激活码(Activation code)破解
  18. bzoj 3620 暴力KMP
  19. BZOJ 2818 Gcd(欧拉函数+质数筛选)
  20. java框架之Spring(3)-JDBC模板使用&事务管理

热门文章

  1. ELK原理
  2. SOA & 微服务
  3. 一张MGR切换的图,不解释
  4. FCB CCB FileObject
  5. 【C/C++开发】容器set和multiset,C++11对vector成员函数的扩展(cbegin()、cend()、crbegin()、crend()、emplace()、data())
  6. CentOS中使用FIO测试磁盘IO性能
  7. 小心!做 UI 自动化一定要跨过这些坑
  8. Jmeter之Bean shell使用(二)(转载)
  9. 树模型常见面试题(以XGBoost为主)
  10. Collection 接口的 toArray 方法