Socket 编程概述
Socket 编程
标签(空格分隔): 计算机网络
前言
网络编程的世界里面,有各个层次的网络编程接口,这些接口给予我们针对不同的层次对网络进行操控,常见有下图这些层次的接口
使用哪个层次的接口可以对实际对那个层次进行控制。
而我们常用的应用编程 API 则是应用层和传输层之间的网络编程接口,通过这个系统调用 API 切换应用程序和操作系统的控制权
几种典型的应用编程接口:
Berkeley UNIX 操作系统定义的 API,称为套接字接口(Socket interface),简称套接字(socket)
windows 中采用了套接字接口 API,形成稍有不同的 API,并称为 winsock
Socket 概述
通信模型:客户/服务器(C/S)
socket 作为应用进程通信的一种抽象机制,他就像是一个客户端的插头插到了服务器对应的插座
我们知道,对外标识通信端点地址是 IP 地址 + 端口号, 那么操作系统和进程实际上是如何管理套接字的呢?实际上每当创建一个套接字的时候,操作系统会返回一个小整数作为描述符来标识这个套接字,而应用程序则使用这个描述符来引用该套接字
更进一步地说,Socket 其实也是类似文件的一种抽象,当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息,并返回套接字描述符。另外在操作系统内部维护着一个 Socket 描述符表,根据套接字描述符可以引用 Socket 的一个数据结构,里面包含了两个端点通信需要的所有东西 通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。
Socket API 函数
针对使用 Windows 上的 winsock 进行网络应用编程的时候,我们一般要走这样的流程
socket()
创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
int socket(int domain, int type, int protocol);
有三个参数:
-
domain 为创建的套接字指定协议集(或称做地址族 address family)。 例如:
-
AF_INET
表示 IPV4 网络协议 -
AF_INET6
表示IPv6 -
AF_UNIX
表示本地套接字(使用一个文件)
-
-
type(socket类型)如下:
-
SOCK_STREAM
(流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。) -
SOCK_DGRAM
(数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。) -
SOCK_SEQPACKET
(可靠的连续数据包服务) -
SOCK_RAW
(原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW) - 原始套接字与标准套接字(标准套接字指的是前面介绍的流式套接字和数据报套接字)的区别 在于:原始套接字可以读写内核没有处理的IP数据包,而流式套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
-
protocol 指定实际使用的传输协议。 最常见的就是
IPPROTO_TCP
、IPPROTO_SCTP
、IPPROTO_UDP
、IPPROTO_DCCP
。这些协议都在
bind()
原型 int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
bind() 为一个套接字分配地址。当使用 socket()
创建套接字后,只赋予其所使用的协议,并未分配地址。在接受其它主机的连接前,必须先调用 bind()
为套接字分配一个地址。bind()
有三个参数:
sockfd
, 表示使用 bind 函数的套接字描述符my_addr
, 指向 sockaddr 结构(用于表示所分配地址)的指针
-
addrlen
, 用 socklen_t 字段指定了 sockaddr 结构的长度
需要注意的是,客户端程序一般不必调用 bind() 函数,因为操作系统会自动完成这个工作指定不会冲突的随机端口
而服务器则是需要 bind() 的,因为服务器需要指定这个端口让客户端找到它使用这个特殊指定的服务。
listen()
原型 int listen(int sockfd, int backlog);
当socket和一个地址绑定之后,listen()
函数会开始监听可能的连接请求。然而,这只能在有可靠数据流保证的时候使用,例如:数据类型(SOCK_STREAM, SOCK_SEQPACKET)
。
listen()
函数需要两个参数:
sockfd
, 一个socket的描述符.backlog
, 完成三次握手、等待accept的全连接的队列的最大长度上限。对于AF_INET类型的socket,全连接数量为:min(backlog, somaxconn)。当队列满时,新的全连接会返回错误。somaxconn默认为128.半连接队列的最大长度可通过sysctl函数设置tcp_max_syn_backlog,默认值为256。Linux Kernel 2.2之后,全连接队列与半连接队列分别叫做accept queue与syns queue。根据/proc/sys/net/ipv4/tcp_abort_on_overflow里的值为为0表示如果三次握手第三步的时候全连接队列满了那么server扔掉client发过来的ack,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,就很容易异常;tcp_abort_on_overflow为1表示第三次握手时如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接。
一旦连接被接受,返回0表示成功,错误返回-1。
closesocket()
原型 int closesocket(int sockfd)
关闭一个描述符为 sockfd 的套接字
如果多个进程共享一个套接字,调用
closesocket
将套接字引用计数减 1,减至 0 才关闭-
一个进程中的多线程对一个套接字的使用无计数
- 如果进程中的一个线程调用 closesocket 将一个套接字关闭,该进程中的其他线程也不能访问该套接字
-
返回值:
-
0
:成功 -
SOCKET_ERROR
:失败
-
connect()
原型 int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
客户程序调用 connet 函数来使客户套接字与特定计算机特定端口的套接字进行连接
仅用于客户端
-
可用于 TCP 客户端也可以用于 UDP 客户端
- TCP 客户端:建立 TCP 连接
- UDP 客户端:制定服务器端点地址
accept()
原型 int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
当应用程序监听来自其他主机的面对数据流的连接时,通过事件通知它。必须用 accept()
函数初始化连接。accept()
为每个连接创建新的套接字并从监听队列中移除这个连接。它使用如下参数:
sockfd
,监听的套接字描述符cliaddr
, 指向 sockaddr 结构体的指针,客户机地址信息。addrlen
,指向 socklen_t的指针,确定客户机地址结构体的大小 。
返回新的套接字描述符,出错返回 -1
。进一步的通信必须通过这个套接字。
Datagram 套接字不要求用 accept()
处理,因为接收方可能用监听套接字立即处理这个请求。
send, sendto
原型 send(sd, *buf, len, flags);
sendto(sd, *buf, len, flags, destaddr, addrlen);
send 函数 TCP 套接字(客户与服务器)或调用了 connect 函数的 UDP 客户端套接字
sendto 函数用于 UDP 服务器端套接字与未调用 connect 函数的 UDP 客户端套接字
recv, recvfrom
原型 recv(sd, *buffer, len, flags);
recvfrom(sd, *buf, len, flags, senderaddr, saddrlen);
recv 函数从 TCP 连接的另一端接收数据,或者从调用了 connect 函数的 UDP 客户端套接字接收服务器发来的数据
recvfrom 函数用于从 UDP 服务器端套接字与未调用 connect 函数的 UDP 客户端套接字接收对端数据