1、网络字节序
2、socket地址结构体
TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用的socket地址结构体,分别用于IPv4和IPv6。
struct sockaddr_in
{
sa_family_t sin_family; //地址族:AF_INET
u_int16_t sin_port; //端口号,要用网络字节序表示
struct in_addr sin_addr; //IPv4地址结构体 见下面
};
struct in_addr
{
u_int32_t s_addr; //IPv4地址,要用网络字节序表示
};
struct sockaddr_in6
{
sa_family_t sin6_family; //地址族:AF_INET6
u_int16_t sin6_port; //端口号,要用网络字节序表示
u_int32_t sin6_flowinfo; //流信息,应设置为0
struct in6_addr sin6_addr; //IPv6地址结构体 见下面
u_int32_t sin6_scope_id; //scope ID,尚处于实验阶段
};
struct in6_addr
{
usigned char sa_addr[16]; //IPv6地址,要用网络字节序表示
};
3、IP地址转换函数
#include <arpa/inet.h>
int inet_pton(int af, const char* src, void* dst);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
inet_pton函数将用字符串表示的IP地址src转换成用网络字节序整数表示的IP地址,并将结果存于dst指向的内存中。成功返回1,失败返回0。
af参数指定地址族,可以是AF_INET或AF_INET6。
inet_ntop进行相反的转换,前三个参数与inet_pton参数相同,最后一个cnt指定目标存储单元的大小。下面的两个宏能帮我们指定这个大小:
#include<netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
inet_ntop成功时返回目标存储单元的地址,失败返回NULL,并设置errno。
4、创建socket连接
创建一个socket,
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain指定要使用的底层协议族,对于TCP/IP协议族而言,为PF_INET(Protocol Family of Internet)或PF_INET6,对于UNIX本地域协议族为PF_UNIX。
type指定服务类型,主要是SOCK_STREAM(流服务,TCP),SOCK_UGRAM(数据报,UDP)。自Linux内核版本2.6.17起,type参数可以接受上述服务类型与SOCK_NONBLOCK和SOCK_CLOEXEC相与的值,它们分别表示将新创建的socket设为非阻塞的,以及用fork创建子进程时在子进程中关闭该socket。
protocol表示一个具体的协议,但是通常前两个参数就唯一确定了第三个值,所以一般设为0表示使用默认协议。
socket系统调用成功返回一个socket文件描述符,失败返回-1并设置errno。
命名(绑定)socket,即将新创建的socket绑定到指定地址
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
bind将addr指定的地址绑定到sockfd上,addrlen为sizeof(addr)。对于TCP字节流(UDP数据报)通信,socket类型是AF_INET和SOCK_STREAM(SOCK_DGRAM),socket上绑定的是IP、Port等信息,对于本地进程间通信,socket类型是AF_LOCAL和SOCK_STREAM(SOCK_DGRAM),socket上绑定的是一个本地文件,这也是本地socket和网络socket之间的最大区别。
另外AF_UNIX也属于本地socket,与AF_LOCAL是等价的。
要注意的是sockaddr是旧的地址结构,从新的结构到旧的结构直接强制转换就可以了,看后面的代码就清楚了。
bind成功返回0,失败返回-1,并设置errno。
监听socket
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd指定被监听的socket。(面试预警)backlog参数提示内核监听队列的最大长度,即accept队列长度。监听队列的长度如果超过backlog,服务器将不受理新的客户端连接,客户端也将收到ECONNREFUSED错误信息。在内核版本2.2前,backlog指所有处于半连接状态(SYN_RCVD)和全连接状态的socket(ESTABLISHED)的上限。2.2之后只表示全连接队列上限,半连接队列上限由/proc/sys/net/ipv4/tcp_max_syn_backlog定义,典型值是5。但是我的默认值是256。
接收连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t* addrlen);
addr用来获取被接受连接的远端socket地址,长度由addrlen指定。
accept成功时返回一个新的连接socket文件描述符,失败时返回-1,设置errno。
accept函数是阻塞的。
(面试预警)处于ESTABLISHED状态的连接在被accept前,如果出现异常,如掉线(仍处于ESTABLISHED)或提前退出(处于CLOSE_WAIT),accpet是否能正常返回。答案为是的,能正确返回,因为accpet只负责从对队列中取出连接,而不检查连接状态,更不关心网络状况的变化。
由上面的CLOSE_WAIT想到另外一个问题,即处于accept队列里的连接在收到客户端的断开请求后只是回了个ACK,却并不能发送自己的FIN,因此会一直处于CLOSE_WAIT即半关闭状态,直到它被accept后调用close或着程序退出。下面是我的测试代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#