linux 套接字接口

大多参考unp,算个总结

一、套接字地址结构

1)IPv4套接字地址
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>头文件里面,下面是POSIX定义
struct in_addr{
 in_addr_t s_addr; //32bit IPv4 address, network byte ordered
};

这个结构只有一个32位的无符号长整数是有历史原因的(否则何必封装呢一个整数呢),以前的时候是把32为的IP定义成多种结构的联合里面的,这样可以获得特定的某种地址(ABC类的),各自按照不同的结构存储,可以获得相应的网路号和地址号。现在有子网划分等技术出现,那个联合已经不再需要了。
struct sockaddr_in {
 uint8_t sin_len;        //无符号8位整数,为了通用没有写成什么unsigned char,因为不同的机器可能char位数不一样
                                     //这个类型需要头文件<sys/types.h>
 sa_family_t sin_family; //AF_INET IPv4协议
 in_port_t sin_port;     //16bit TCP or UDP port, network byte ordered
 struct in_addr sin_addr;
 char sin_zero[8];       //unused 这个东西好像是为了和以前的sockaddr保持大小一样16字节,没什么用,一般要置为0
    //一般用sockaddr_in前置为全部字段置为0,可用bzero,或者memset
};

一般只需要三个字段,sin_family、sin_addr、sin_port

2)通用套接字地址
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(指针)来传递(可能为了减少拷贝吧),然而这样的指针作为参数之一的任何套接字函数必须处理来自所支持的任何协议族套接字地址结构(不光有IPv4套接字,还有原始套接字,Unix域套接字),也就是想一个函数可以传不同的参数,由于c没有重载和多态,就用一个指针来传递一个结构体(但结构体内数据对应需要的字段应该是位置一样的,都为16字节)。所以就定义了一个通用套接字地址结构。所有的套接字地址指针在传递给函数的时候都要强制转成这个结构体类型的(因为函数定义为这个类型的)。有了ANSI C其实办法很简单,定一个一个void *的参数就可以了,然而套接字是在ANSI C之前定义的,所以就在<sys/socket.h>文件中定义了一个通用套接字结构地址。

struct sockaddr{
 unit8_t sa_len;
 sa_family_t sa_family;
 char sa_data[14];
};
从开发人员的角度来看,通用套接字的唯一用途就是对指向特定于协议的套接字地址结构的指针执行强制类型转换。


二、套接字接口
1)socket
#include <sys/socket.h>
int socket(int family, int type, protocol); //出错返回-1

其中family指明协议族,tpye指明套接字类型,protocol指明协议,如果为0,那么由系统根据family和type来自动选择。成功返回的时候返回一个套接字描述符(socket descriptor),得到这个sockfd只是得到了协议族(IPv4、IPv6或Unix)和套接字类型(字节流、数据报)没有有指定本地协议地址或者远程协议地址。

2)connect
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
sockfd是由socket函数返回的套接字描述符,第二个和第三个是指向套接字地址结构的指针和大小。客户在调用函数connect之前不必非得调用bind函数,然后需要的话内核会确定源IP地址,并选择一个临时端口。

如果是TCP调用connect的话,会激发三次握手行为,并在成功或出错的时候返回。异常情况有以下几种
a.若TCP客户端没有收到SYN分节的响应,返回ETIMEDOUT错位。(服务器关机)
b.若多客户的SYN响应是RST,那么表示服务器没有在指定端口有进程。将返回ECONNREFUSED错误。(服务器重启或服务器进程死掉,或服务器没有该进程)
c.若客户收到一个ICMP的“destination unreachable”错误,继续发送SYN没有最后没收到返回EHOSTUNREACH错误或者ENETUNREACH错误。

上面的要注意,如果服务器忙,TCP连接队列满,那么服务器收到SYN直接丢弃,不发送ICMP差错,也不RST,让客户机在发送SYN,度过这种短暂的状态。connect函数将导致当前套接字从CLOSED状态(从创建来一直处以的状态)转移到SYN_SEND状态,若成功将进入ESTABLISHED状态,失败则该套接字必须关闭。

3)bind
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)
bind函数将一个协议地址赋予一个套接字。参数和connect差不多,bind可以指定端口可以指定IP,也可以都不指定。如果指定端口号为0,那么内核就在bind被调用时选择一个临时端口。

4)listen
in listen(int sockfd, int backlog);
这个函数仅由TCP服务器调用,它完成两件事情
a.当socket函数创建一个套接字的时候,它被假设为一个主动套接字,也就是说它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转成一个被动套接字,指示内核应该接收指向该套接字的连接请求。调用listen函数使得套接字从closed状态到listen状态。
b.第二个参数指定了内核应该为该套接字排队的最大连接个数。

本函数通常在socket和bind函数之后,并在 accept函数之前调用。下面说下backlog这个参数。
内核为任何一个给定的监听套接字维护两个队列:
a.未完成队列(incomplete connection queue),每个这样的SYN分节对应其中一项:已由每个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。
b.已完成队列(complete connection queue),每个已完成TCP三路握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态。

当来自客户的SYN到达时候,TCP在未完成队列中创建一个新项,然后响应应以三路握手的第二个分节,当客户发回来三次握手的第三个分节,三次握手正常完成,就从未完成队列移到已完成队列的队尾。当进程调用accept的时候就从已完成队列头项返回给进程,如果该队列为空,进程将进入睡眠,知道TCP在该队列中放入一项才唤醒它。

繁忙的Web服务器表明指定较大的backlog的值的理由在于:随着客户SYN分节的到达,未完成连接队列中的项数可能增长,它们等待三路握手的完成。我理解这个原因可能是服务器如果没有负载均衡,那么listen只有一个端口假如为80,这个时候所有的连接都通过未完成队列,而对于已完成队列服务器可以用多进行,多线程各种方式来完成。所以未完成队列可能是限制一个服务器的原因。backlog的值并不是两个队列的和,不同的操作系统有不同的解释。

5)accept
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
accept函数由TCP服务器调用,用于从已完成队列对头返回下一个已完成连接,如果队列为空,那么进程被投入睡眠。如果accept返回成功,那么其返回值是由内核自动生成的一个全新的描述符,代表与所返回客户的TCP连接。第一个参数为监听套接字(listening socket,由socket创建,随后用作bind和listen的第一个参数描述符)称它返回值为以连接套接字(connected socket),区分这两个套接字很重要。一个服务器通常仅仅创建一个监听套接字,它在该服务器的一个声明周期一直存在。内核为每个由服务器进程创建一个已连接套接字(三次握手完成),当服务器完成对某个客户机的服务时,该已连接套接字关闭。这个函数可以返回三个值,已连接套接字,客户的sockaddr,和大小,如果对客户的sockaddr不感兴趣,可以把后两个参数设为0.

6)close
int close(int sockfd)
close一个TCP套接字的默认行为时是把该套接字标记成已关闭,然后立即返回到调用进程。该套接字描述符不能在由调用进程使用,也就是说不能在作为read或write的第一个参数。然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。


7)sendto & recvfrom
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklent_ addrlen);

注意参数的不同,在最后的地方,一个是传值,一个是传引用。传引用是因为想把结果返回给进程,从内核返回结果给进程。这两个函数都把所读写数据的长度作为函数的返回值。

8)send & recv
ssize_t send(int sockfd, const void *buf, size_t len, int flags);  
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

flags可以为0,“The send() call may be used only when the socket is in a connected state (so that the intended recipient is known).  The only difference between send() and write() is the presence of flags.With zero flags parameter, send() is equivalent to write().”
为0的情况下与write一样效果。下面说明喜爱flags的一些其他含义:
MSG_DONTROUTE 只有send可用,告知内核目的主机在某个直接连接的本地网络上,因而无需执行路由表查找。
MSG_DONOTWAIT send和recv都可用,可以把单个I/O操作临时指定为非阻塞,接着执行I/O操作,不是所有系统都支持。
MSG_OOB send和recv都可用,发送和接收带外数据
MSG_PEEK recv和recvfrom可用,允许我们查看已可读取的数据,而且系统不再recv或recvfrom返回后丢弃这些数据。
MSG_WAITALL recv可用,告知内核不要在尚未读入请求数目的字节之前让一个读操作返回。

9)htons & htonl & ntohl & ntohs
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);  //ip地址转换的时候,一般就是服务端设置时:servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
uint16_t htons(uint16_t hostshort); //在端口转换的时候需要用
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

convert values between host and network byte order意思显而易见了。

10)inet_aton & inet_ntoa
int inet_aton(const char *strprt, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);

inet_aton比较常用,将一个字符床转换成一个32位的网络字节序的二进制值,并通过指针addrptr来存储,若成功返回1,否则返回0。
inet_ntoa函数将一个32位的网络直接序的二进制IPv4地址转换成相应的点分十进制数串。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值