概述

本文通过一个最简单的Socket通信来对每一步做通俗易懂的讲解让你了解这些函数到底是干什么用的。下面的代码虽然是用Pyhton实现的,但是你要知道这些通信机制并不是Python所定义的,因为这些东西都必须符合通用规范。在类Unix操作系统上建立Socket也是这些步骤而Python其实就是使用的系统调用。同时本文包括关于Socket的基础知识,这些知识对于你去理解Socket很有帮助。

你的第一个Socket程序

基础知识

套接字:标识每个端点的IP和端口就叫做一个套接字。比如:(192.168.50.100:80)

套接字对儿:包含两个端点(本机和对端)的组合,也就是本地IP和端口以及远程主机IP和端口。比如:(192.168.50.112:59091 192.168.50.100:80)

监听套接字:socket函数产生一个尚未打开的主动套接字,bind把该套接字具体到一个地址,该套接字只有本地IP+端口。服务器在调用listen函数之后那么该套接字就变成一个监听套接字。告诉内核该套接字可以接收连接请求。

连接套接字:如果有客户端连接进来那么就会为每一个连接过来的客户端产生一个连接套接字(本地IP、PORT 远程IP、port)。

套接字函数:套接字函数使用描述符访问套接字,一个套接字可以对应多个描述符,但是一个描述符只能属于一个套接字。套接字就是应用层到传输层或其他协议层的访问接口。而访问任意套接字就要用到描述符,因为通过描述符才能调用套接字函数。

Socket发送缓冲区:每一个TCP套接字有一个发送缓冲区,这个缓冲区你可以更改大小。当应用程序调用wirte的时候,内核从应用进程的缓冲区复制所有数据到套接字发送缓冲区,如果应用程序缓冲区中的数据大于套接字发送缓冲区(可能本身就大于,有可能套接字发送缓冲区本来就有数据剩余的空间小于要发送的数据)这时候应用进程将会被设置为睡眠也就是内核将不从write函数返回,应用进程就卡在这里了。卡在这里干嘛呢?其实就是等待,等要发送的数据全部复制到套接字发送缓冲区之后才返回,不过这里虽然返回了也不代表已经把数据发送到远程主机了,这种返回仅仅代表告诉应用程序你可以重新使用应用程序缓冲区并且往里面写数据。套接字在内核空间,应用程序在用户空间,write就是把数据从用户空间复制到内核空间。复制完了之后的真实发送数据以及TCP的数据可靠机制这些东西都是有TCP协议栈来保证的无需上层应用进程来关系。有人就问套接字发送缓冲区感觉多余啊,理想状态下的确多余,但是有2个必要好处:

  • TCP是可靠连接,它需要保证你要发送的数据确实发送成功,TCP把缓冲区的数据发送到对端,它还要等对端的ACK确认,之后确认之后它才会把缓冲区的数据删除。
  • 解耦,应用进程把数据丢进来就好了,剩下的工作我来在,你去干其他的事情。

在UDP中虽然也有一个套接字发送缓冲区,但是其作用仅仅是标识这个UDP数据包的大小上限,它不会去做保证可靠的事情,所以它的缓冲区也不会保存应用进程要发送的数据。

服务器端

 #!/usr/bin/python
# -*- coding: UTF-8 -*- # 导入 socket 模块
import socket """
创建 socket 套接字对象,如果成功返回非负数的描述符,如果失败返回-1,严格来说这里的套接字是一个主动套接字。而且是一个未连接
(CLOSED状态)的主动套接字。
family 指定协议族
AF_INET IPv4协议
AF_INET5 IPv6协议
AF_LOCAL Unix套接字协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
type 指定套接字类型
SOCK_STREAM 字节流套接字 TCP
SOCK_DGRAM 数据报套接字 UDP
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
proto 设置某个协议的值,默认是0, 0表示根据给定family和type的组合自动设置当前系统默认值
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
如果socket()什么都不填写则表示IPv4的TCP协议。上面这些参数的值不是python语言里socket函数所独有的,而是系统调用函数具有的。
"""
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP) # 等于 s = socket.socket() """
该函数会把一个声明了类型的套接字具体化。也就是你上面声明了使用什么网络层协议和传输层协议,如果把服务器和客户端通信比作写信,
你要给我写信你就得遵守我的规范比如一次写多少字因为太多了我看不过来,用什么样的信封等等的一些要求,规范订完了那别人的知道
怎么找到服务器啊也就是你的邮寄地址,因为规范谁都可以用,N个人可以使用同一个规范,但是如何区别N个人呢?这就是IP和端口。
所以bind就是声明一个地址并且是符合上面定义的规范的地址。其实IP和端口本身毫无意义,192.168.1.100:80 这样的地址之所以可以
表示一个符合IP和TCP的规范的地址是因为在IP和TCP协议中对地址格式进行了定义。 套接字实在socket()函数调用时就创建了,只是这时候是一个没有明确地址的套接字,它是套接字主体,这里的IP和端口是绑定在套接字
上的客体。 绑定的时候需要指定具体IP和端口吗?其实不用,你可以把IP和端口写成空你看看程序能运行么?当然可以,这里的可以是说它可以运行
并监听在某个IP和端口上,至于是哪个IP和端口取决于当前系统的设置。但作为服务器来说通常需要指定IP和端口,在RPC通讯中服务器
绑定不需要指定端口,这是例外。
如果不指定IP和端口我怎么知道服务器监听在哪里呢?这时候你就需要使用 getsockname()函数来获取。
"""
s.bind(("127.0.0.1", 12345)) """
listen(backlog)函数把一个未连接的主动套接字变成一个被动套接字,也就是告诉内核接受连接到该套接字的请求。这时候套接字状态将从CLOSED
变成LISTEN状态。这个函数的参数是一个整数其含义是套接字队列的最大连接个数。
内核为套接字准备2个队列:
一个是半连接队列也就是服务器收到客户端SYN并回复SYN_ACK之后等待三次握手完成的队列,套接字状态(SYN_RCVD)
一个是全连接队列也就是三次握手建立完毕之后的队列,完成之后客户端连接就会从半连接队列移动到全连接队列的末尾,套接字状态(ESTABLISHED)
上面两个队列到底指的是这2个队列中的哪一个还是他俩之和又或者说是两者中的最大值这个不一定具体得看具体操作系统在这个系统调用
上的定义。
在红帽Linux内核里backlog指的是全连接队列大小 net.core.somaxconn;而半连接队列大小有另外一个值 tcp_max_syn_backlog。那么这个
backlog有默认值但是应用程序可以修改。
"""
s.listen(5) while True:
"""
获取一个客户端连接,这个函数其实就是从全连接队列的队首返回一个已经完成三次握手的连接,如果这个全连接队列为空则进程挂起
也就是睡眠直到该队列有一个可用连接被返回。该函数调用成功将会返回一个连接套接字和协议地址。
"""
c, addr = s.accept()
print("连接套接字:", c)
print('连接地址:', addr)
data = "Hello world!"
# 发送数据
c.send(data.encode(encoding="utf-8")) """
我们通常使用close()函数来关闭一个套接字连接,其实它并不是真正的关闭,它只是让这个连接套接字的引用计数器减1,只有当这个
某个套接字的引用计数值为0时,在会被真正的清理和释放资源。换句话说调用clese函数不会直接出发四次断开机制,也就是服务器
不会主动发送FIN。如果你真的要主动发送FIN就要使用shutdown()函数。不过通常我们都使用close。
"""
c.close()

客户端

 #!/usr/bin/python
# -*- coding: UTF-8 -*- import socket """
这里和服务器端的设置是一样的,创建一个主动未打开的套接字。
"""
s = socket.socket() """
这里有些人可能看不懂,网上内多套接字编程客户端根本不用bind()函数啊。没错的确不用,但是你不写并不代表内核不用,如果你不写
内核会帮你找一个当前系统使用的IP并随机产生一个端口。如果你写一个固定的也没错。如果你看了前面服务端对bind函数的说明你就
知道为什么一定要显式或隐式的调用bind()函数了。客户端程序不明确调用这个函数只是因为在C/S模式中作为客户端的一方不需要被别人
主动连接过来所以它bind可以使用本机任意可以和外面通信的IP以及一个随机端口。
"""
s.bind(("127.0.0.1", 9999)) """
connect()函数是用来与服务器端建立TCP连接使用的,成功返回0否则返回-1。调用该函数会触发TCP的三次握手。当收到SYN_ACK时该函数返回。
"""
s.connect(("127.0.0.1", 12345))
print("使用:", s.getsockname(), " 连接远程服务器。")
# 接收数据
data = s.recv(1024)
print(data.decode(encoding="utf-8"))
s.close()

运行结果

这时候是阻塞的模式一次只能允许一个客户端连接过来。

(一)你的第一个Socket程序的更多相关文章

  1. 我的第一个Socket程序-SuperSocket使用入门(一)

    第一次使用Socket,遇到过坑,也涨过姿势,网上关于SuperSocket的教程基本都停留在官方给的简单demo上,实际使用还是会碰到一些问题,所以准备写两篇博客,分别来介绍SuperSocket以 ...

  2. 我的第一个Socket程序-SuperSocket使用入门(三)

    本来博客都停了,不打算更了,但今天百度一个socket的问题时无意间发现第一篇的socket文章权重仅次于SuperSocket网站,顿时觉得自己6到不行,再写一篇,讨论下数据持久化的问题 去年搞那个 ...

  3. Hello Socket - 第一个Socket程序

    1. 首先,要编写windows下socket程序,必须要加入Winsock支持 2. 服务端监听程序(Server.cpp) #include<winsock2.h> //包含头文件 # ...

  4. 我的第一个Socket程序-SuperSocket使用入门(二)

    操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操 辛辛苦苦写那么久的博客,最后手贱点了全屏富文本编辑器 ...

  5. 今天在网上查看了一个socket程序,运行的时候一直报错,经过队友解决?

    1.首先是问题代码ip_port = ('192.168.12.2',8001)2.上边的代码本身没有问题,但是必须经过修改自己本机的局域网IP地址才能顺利链接请参考上一篇blog的地址,查看本机的i ...

  6. Glue4Net简单部署基于win服务的Socket程序

    smark 专注于高并发网络和大型网站架规划设计,提供.NET平台下高吞吐的网络通讯应用技术咨询和支持 Glue4Net简单部署基于win服务的Socket程序 在写一些服务应用的时候经常把要它部署到 ...

  7. 第一个socket服务端程序

    第一个socket服务端程序 #include <stdio.h> #include <stdlib.h> #include <string.h> #include ...

  8. ZeroMQ接口函数之 :zmq_sendmsg – 从一个socket上发送一个消息帧

    ZeroMQ 官方地址 :http://api.zeromq.org/4-1:zmq-sendmsg zmq_sendmsg(3)        ØMQ Manual - ØMQ/4.1.0 Name ...

  9. 一个Socket数据处理模型

    Socket编程中,如何高效地接收和处理数据,这里介绍一个简单的编程模型. Socket索引 - SocketId 在给出编程模型之前,先提这样一个问题,程序中如何描述Socket连接? 为什么这么问 ...

随机推荐

  1. C++ 用变量定义数组

    较早的编译器是不同意这样做的,所以一些书籍比方以Tc解说的书本都说数组的下标不能是变量.在vc6.0下亦是如此. 只是在一些较新的编译器如dev c++已经支持了,例如以下代码不会报错 #includ ...

  2. W3C的标准到底是啥?

    1.图片的alt="" 属性必须每张图片都加上,而且对齐属性用CSS来定义.不加不能通过XHTML 1.0的验证. 2.每个文档必须加上DTD声明. a) !DOCTYPE htm ...

  3. margin相关属性值

    1.图片与文字对齐问题 图片与文字默认是居底对齐.一般img标签打头的小图标与文字对齐的话,通过 img{margin:0 3px -3px 0;} 这个的东西,能实现效果和兼容性俱佳的对齐效果: d ...

  4. 封装redis

    封装redis import redis # r = redis.Redis() class MyRedis(): def __init__(self,ip,password,port=6379,db ...

  5. Textarea设置自动高度

    $.fn.extend({ autoHeight: function() { return this.each(function() { var $this = jQuery(this); if(!$ ...

  6. js生成自定义随机数方法

    function getRandom() { var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', ...

  7. java 23种设计模式 深入理解【转】

    以下是学习过程中查询的资料,别人总结的资料,比较容易理解(站在各位巨人的肩膀上,望博主勿究) 创建型抽象工厂模式 http://www.cnblogs.com/java-my-life/archive ...

  8. 利用redis + lua解决抢红包高并发的问题

    抢红包的需求分析 抢红包的场景有点像秒杀,但是要比秒杀简单点.因为秒杀通常要和库存相关.而抢红包则可以允许有些红包没有被抢到,因为发红包的人不会有损失,没抢完的钱再退回给发红包的人即可.另外像小米这样 ...

  9. FFmpeg命令行工具学习(三):媒体文件转换工具ffmpeg

    一.简述 ffmpeg是一个非常强大的工具,它可以转换任何格式的媒体文件,并且还可以用自己的AudioFilter以及VideoFilter进行处理和编辑.有了它,我们就可以对媒体文件做很多我们想做的 ...

  10. [Swift]LeetCode326. 3的幂 | Power of Three

    Given an integer, write a function to determine if it is a power of three. Example 1: Input: 27 Outp ...