unix网络编程之基本套接字函数

时间:2022-06-21 11:00:10

两台机器之间的通信,必定是由软件进行控制的,而相应客户端和服务器端软件的编写,必然要调用系统提供的套接字函数。本文就是对基本套接字函数做一个介绍。

一、socket函数

为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型(使用ipv4 or ipv6,tcp or udp等)。

#include <sys/socket.h>
int socket(int family, int type, int protocol);
其中family指定协议族(ipv4 or ipv6),具体就是系统默认的常值如AF_INET or AF_INET6等。type参数指明套接字类型,常见的有SOCK_STREAM or SOCK_DGRAM or SOCK_RAW等,分别代表tcp、udp、原始套接字。protocol参数一般设为0,由系统根据family和type组合提供默认值。

socket函数成功时会返回一个小的非负整数值,与文件描述符类似,称之为套接字描述符,简称sockfd。其实,unix中一切设备皆文件,套接字自然也不例外。这个时候sockfd还没有和地址、端口绑定。

关于family,最后啰嗦一句,历史上曾有AF_XXX(地址族)和PF_XXX(协议族)的区别,它们表示不同的意思。但是目前在我的ubuntu机子上是这么定义的:

#define PF_INET 2
#define AF_INET PF_INET
也就是说它们现在可以混用了,以后我的程序中基本上都是用AF_XXX。

二、connect函数

TCP客户用connect函数来建立和TCP服务器的连接。

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。该套接字地址结构包含服务器的ip地址和端口号。TCP客户调用connect函数则会激发TCP三次握手的过程,使客户端发出一个syn段。待三次握手过程结束后,connect函数才返回(阻塞状态下)。当然connect过程可能会失败,比如syn段未能发送到服务器导致超时;接收到RST段,表明服务器端并没有相应的进程在监听;也可能接收到ICMP报文,表明目的地不可达。这些通过察看errno可以知晓。按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态转换到SYN_SENT状态,若成功则再转换到ESTABLISHED状态。若失败,必须将套接字关闭。

三、bind函数

bind函数把一个本地协议地址赋予一个套接字,对于ip协议,协议地址主要为ip地址和端口号的组合。

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
TCP客户一般不需要调用该函数,而是由内核决定好用的ip地址和可用的端口号。TCP服务器一般需要调用该函数,让进程监听在一个特定的端口上。TCP服务器一般绑定的是通配地址,如对于ipv4:

struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
对于ipv6:

struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any;
四、listen函数

listen函数仅由TCP服务器调用,它做两件事。

(1)当socket函数创建一个套接字之后,它被假设为一个主动套接字,listen函数将其转变为被动套接字,也称监听套接字。在TCP状态转换图中,该套接字从CLOSED状态转化为LISTEN状态。

(2)本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数。

#include <sys/scoket.h>
int listen(int sockfd, int backlog);
本函数通常在调用socket和bind这两个函数之后,并在调用accept函数之前调用。

内核通常为任何一个指定的监听套接字维护两个队列:未完成连接队列和已完成连接队列。两队列套接字之和==backlog。

当TCP客户调用connect函数发出的SYN段到达时,TCP服务器端内核就在未完成连接队列中创建一个新项,并将监听套接字的参数复制给该项。当三次握手完成时,TCP服务器端内核就将该套接字移到已完成连接队列的队尾。等待accept函数的读取,accept函数是从队首开始读取的。

五、accept函数

accept函数由TCP服务器调用,用于从已完成连接队列队首返回下一个已完成连接。如果已完成连接队列为空,那么进程将投入睡眠(假定套接字为默认的阻塞方式)。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
参数cliaddr和addrlen用来返回客户端的协议地址。如果accept成功,那么返回值是由内核自动生成的一个全新套接字,代表与所返回客户的TCP连接,称为连接套接字,accept函数的第一个参数称为监听套接字。这两个套接字是不一样的。监听套接字在服务器进程运行中,一直存在,而连接套接字则可以有很多个,并且当服务器完成对某个客户的服务时,相应的连接套接字就被关闭。注意accept函数的第三个参数是个“值-结果”参数。

之后,连接就建立起来了,客户-服务器之间就可以进行通信了。通信结束之后,当然还是要关闭连接的啦。

六、close函数

close函数用于关闭套接字,并终止TCP连接。

#include <unistd.h>
int close(int sockfd);
close一个套接字的默认行为就是将该套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用。然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。
七、getsockname和getpeername函数

这两个函数主要用于获取与某个套接字关联的本地协议地址(getsockname)或与某个套接字关联的对端协议地址(getpeername)。

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
至此,基本的套接字函数就介绍完了,下面我们就要进行实践了。