Unix/Linux支持伯克利风格的套接字编程,它同一时候支持面向链接和面向无链接类型的套接字。编程
套接字最常用的一些系统调用:缓存
socket()
网络
bind()socket
connect()函数
listen()spa
accept()指针
send()server
recv()接口
sendto()队列
recvfrom()
close()
shutdown()
setsockopt()
getsockopt()
getpeername()
getsockname()
gethostbyname()
gethostbyaddr()
getservbyname()
getservbyport()
getprotobyname()
fcntl()
如下具体解释这些系统调用。
一、socket()函数
#include <sys/socket.h>
/*成功返回非负描写叙述符。不然返回-1*/
int socket(int family, int type, int protocol);
当中,family參数指明协议族,常用的family值有AF_INET(IPv4协议)、AF_INET6(IPv6协议)、AF_LOCAL(Unix域协议)、AF_ROUTE(路由套接字)和AF_KEY(密钥套接字)。该參数也每每被称为协议域。
注意,后两种仅适用于原始套接字。
type參数指明套接字类型。如SOCK_STREAM(字节流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_SEQPACKET(有序分组套接字)以及SOCK_RAW(原始套接字)等。
protocol參数可以设置为IPPROTO_CP(TCP传输协议)、IPPROTO_UDP(UDP传输协议)或IPPROTO_SCTP(SCTP传输协议),同一时候该參数也可设置为0,以选择所给定family和type组合的系统默认值。
socket()函数在成功时返回一非负整数,它与文件描写叙述符相似,咱们称之为套接字描写叙述符(Socket Descriptor)。简称sockfd。
那么,此函数的做用是什么呢?socket函数经过咱们设定的协议族、套接字类型和传输协议參数来建立底层网络文件,为进行网络通讯作准备。
二、bind()函数
bind()函数把一个本地协议地址赋予一个套接字。对于网际网协议。协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDPport号的组合。
#include <sys/socket.h>
/*成功返回0,不然返回-1*/
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);
參数myaddr指向特定于协议的地址结构的指针,第三个參数是该地址结构的长度。
假设一个TCP客户或server未调用bind()捆绑一个port。当调用connect或listen时,内核就要为对应的套接字选择一个暂时port。让内核选择暂时port对于TCP客户来讲是正常的,但对TCPserver来讲极为罕见,因为server是经过它们的众所周知的port被你们所认识的。
进程可以经过bind()函数把一个特定的IP地址绑定到其套接字上。只是此IP地址必须属于其所在主机的网络接口之中的一个。对于TCP客户。这就为该套接字所发送的数据报文指定了源IP地址。对于TCPserver,这就限定该套接字仅仅接收那些目的地址为此IP地址的客户链接。
对于TCP。调用bind函数可以指定一个port号,或指定一个IP地址。也可以二者都指定。还可以都不指定。
那么。当咱们未指定port号或IP地址时。系统调用会怎样处理呢?
通常而言。不指定port号,bind()函数默以为0。不指定IP地址。函数默以为通配地址。
进程指定 |
结果 |
|
IP地址 |
端口port |
|
通配地址 |
0 |
内核选择IP地址和port |
通配地址 |
非0 |
内核选择IP地址。进程指定port |
本地IP地址 |
0 |
进程指定IP地址,内核选择port |
本地IP地址 |
非0 |
进程指定IP地址和port |
注:通配地址为INADDR_ANY。
当使用socket()函数获得套接字描写叙述符后,依状况需要将socket绑定主机上的port:
假设为server进程。需要在port进行监听(listen)操做。等待链接请求时。每每需要进行bind操做,而且这个port应该是众所周知的;
假设为client进程,需要向远端server发起链接(connect)请求。这时,绑定port是可选的。
附:port号
TCP、UDP和SCTP三种传输协议使用16位port号来区分进程。
port号被划分为下面三段:
*众所周知的port(0-1023);
*已登记的端口(registered port,1024-49151)。
*动态(dynamic)或私用(private)port(49152-65535)。
三、connect()函数
TCPclient用connect()函数来创建与TCPserver的链接。
#include <sys/socket.h>
/*成功返回0,出错返回-1*/
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
第二个、第三个參数各自是一个指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有server的IP地址和port号。
client在调用connect()函数时,没有必要调用bind()函数。
咱们并不在意咱们本地用什么port来进行通讯,咱们在意的是client需要链接到远端server的哪一个port。当咱们未调用bind()函数时,内核本身主动选择一个未被使用的本地port。
关于connect()具体是怎样工做的,会在网络协议的TCP三次握手时具体介绍。
四、listen()函数
#include <sys/socket.h>
/*成功返回0,不然返回-1*/
int listen(int sockfd, int backlog);
listen()函数仅由TCPserver调用,它主要完毕两件事:
*当socket()函数建立一个套接字是,其被设为主动套接字,也就是说,它是一个将调用connect()函数来主动发起链接的client套接字。listen()函数把一个未链接的主动套接字转换成一个被动套接字。指示内核应接受指向该套接字的链接请求。
*backlog规定了内核应该为对应的套接字排队的最大链接个数。
本函数一般应该在socket()和bind()函数以后。并在调用accept()函数以前调用。
这里。需要理解backlog參数:
内核为不论什么一个给定的监听套接字维护两个队列:
(1)未完毕链接队列(incomplete connection queue)。这些套接字处于SYN_RCVD状态;
(2)已完毕链接队列(completed connection queue),每个已完毕TCP三路握手过程,这些套接字处于ESTABLISHED状态。
下图描绘了监听套接字的两个队列。
每当在未完毕队列建立一项时,来自监听套接字的參数就拷贝到即将创建的链接中。
当来自客户的SYN到达server时。serverTCP在未完毕链接队列中建立一个新项,而后对应以三路握手的server的SYN响应,当中捎带对客户SYN的ACK。这一项一直保留在未完毕链接队列中,直到三路握手的第三个分节(客户对serverSYN的ACK)到达或该项超时为止。
假设三路握手正常完毕。该项就从未完毕队列移至已完毕链接队列的队尾。当进程调用accept()时。已完毕链接队列中的队头项将返回到进程。假设该队列为空,那么进程将被投入睡眠。直到TCP在该队列中放入一项才唤醒它。
假设一个客户的SYN到达时,两个队列是满的。那么TCP就会忽略该分节,但不会发送RST。这样作有一个优势:两队列是满的的状况仅仅是临时的。假设serverTCP不发送RST。那么clientTCP就会重发SYN,这样,可能不久就能在这些队列中找到可用空间。
五、accept()函数
#include <sys/socket.h>
/*成功返回非负描写叙述符,出错返回-1*/
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
參数cliaddr和addrlen用来返回已链接的对端进程(client)的协议地址。
addrlen是值-结果參数:调用前,咱们将有*addrlen所引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构内的确切字节数。
假设accept成功。其返回值是由内核本身主动生成的一个全新描写叙述符,表明与所返回客户的TCP链接,咱们称之为已链接套接字(connected socket)。
本函数最多返回三个值:一个既多是新套接字描写叙述符也多是出错指示的整数,客户进程的协议地址(由cliaddr指针所指)以及该地址的大小(由addrlen指针所指)。假设咱们对是哪一个主机链接了该server(客户协议地址)不感兴趣,那么可以把cliaddr和addrlen均置为空指针。
六、send()和recv()函数
这两个函数时最主要的,经过链接的套接字流进行通讯的函数。
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
send()參数含义例如如下:
sockfd表明与远程程序链接的套接字描写叙述符;
buff指针指向发送信息的字符串。
nbytes指发送信息的长度。
flags指发送标记。
send()函数在调用后返回它真正发送数据的长度。
但是,此发送数据可能少于參数指定的长度。
假设错误发生,则返回-1。错误代码存储在全局标量errno中。
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);
recv()參数含义例如如下:
sockfd指读取数据的套接字描写叙述符。
buff指针指向存储数据的内存缓存区域;
nbytes是缓存区的最大尺寸。
flags是发送标记。
recv()返回它所真正接收到的长度,也就是存储到buf中数据的长度。假设返回-1则表明发生了错误(比方网络意外中断,对方关闭了套接字链接等),全局变量errno存储了错误代码。
七、sendto()和recvfrom函数
这两个函数时进行无链接的UDP通讯时使用的。使用这两个函数,则数据会在没有创建过不论什么链接的网络上传输。在这里,由于数据报套接字没法对远程主机创建链接。所以,咱们在发送数据前需要知道远端主机的IP地址和port号。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, const struct sockaddr *from, socklen_t *addrlen);
前三个參数sockfd、buff和nbytes:套接字描写叙述符、指向读入写出缓冲区的指针和读写字节数。
sendto的to參数指向一个含有数据报接收者的协议地址的套接字地址结构。其大小由addrlen參数指定。recvfrom的from參数指向一个将由该函数在返回时填写数据包发送者的协议地址的套接字地址结构。
注意:sendto的最后一个參数是整数值。而recvfrom的最后一个參数是一个指向整数值的指针(即值-结果參数)。
recvfrom的最后两个參数相似于accept的最后两个參数,返回时当中套接字地址结构的内容告诉咱们是谁发送了数据报(UDP状况下)或是谁发起了链接(TCP状况下)。sendto的最后两个參数相似于connect的最后两个參数,调用时当中套接字结构被咱们填入数据报将发往(UDP状况下)或与之创建链接(TCP状况下)的协议地址。
这两个函数都把所读写的数据的长度做为函数返回值。
注意:recvfrom和sendto都可以用于TCP,虽然一般没有理由这样作。
八、close()和shutdown()函数
Unix使用close函数和shutdown函数来关闭套接字,并终止TCP链接。
#include <unistd.h>
int close(int sockfd);
/*若成功则返回0,出错返回-1*/
close一个TCP套接字的默认行为是把该套接字标记为关闭。而后立刻返回到调用进程。该套接字描写叙述符不能再由调用进程使用,也就是说它不能再做为read和write的第一个參数。
然而,TCP将尝试发送已排队等待发送到对端的数据,发送完成后发生的是正常的TCP链接终止序列。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
注意当中的how參数。0表示不一样意之后数据的接收操做。1表示不一样意之后数据的发送操做,2表示和close()同样。不一样意之后的不论什么数据操做。
附加内容:
send/recv与write/read函数的差异
recv和send函数提供了和read和write几乎相同的功能。但是它们提供了第四个參数来控制读写操做。
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);
前面的三个參数和read,write一样,第四个參数能够是0或是下面的组合:
flags |
说明 |
recv |
send |
MSG_DONTROUTE |
绕过路由表查找 |
|
* |
MSG_DONTWAIT |
仅本操做堵塞 |
* |
* |
MSG_OOB |
发送或接收带外数据 |
* |
* |
MSG_PEEK |
窥看外来消息 |
* |
|
MSG_WAITALL |
等待所有数据 |
* |
|
假设flags为0。则和read,write同样的操做。
PS.在下一系列博客,将会涉及到详细的网络编程样例。