socket
TCP模型客户端服务器模型
从上面的图来看,服务器首先是需要出去监听状态之后,客户端才能够进行TCP连接。首先服务器先初始化socket套接字。之后服务器执行bind函数,将本次套接字与一个ip地址和一个客户端知道的端口绑定。然后listen函数,将这个套接字转换成监听套接字。然后在accept函数中不断阻塞,直到有客户端来连接。
当有客户端连接的时候,就会发生面试必考的三次握手流程。
三次握手
首先,客户端处于closed状态,服务器处于listen状态。
第一次握手,客户端给服务器发送一个SYN报文,并指明了客户端的初始序列号ISN(c),此时,客户端处于SYN_send状态。
第二次握手,服务器收到客户端的SYN报文之后,也会发送自己的SYN报文,并指明了自己的ISN(s)。同时会把客户端的ISN+1作为ACK发送回客户端。此时,服务器处于SYN_recv状态。
第三次握手,客户端收到SYN报文之后,也会发送一个ACK,这个ACK是服务器的ISN+1值。此时客户端处于establised状态。
1、ISN+1作为ACK返回这是一种验证的方式,代表已经收到你上一次发送的数据包。
2、两次握手的话,服务器不知道客户端有没有收到数据的能力。四次握手的话就会浪费网络资源。
socket流程的函数
socket()函数
int socket(int domain, int type, int protocol)
创建套接字。第一个参数是地址族,填的是这个是ipv4还是ipv6。PF_INET、PF_INET6这些。
第二个参数是套接字类型,SOCK_STREAM 代表TCP连接,SOCK_DGRAM代表UDP连接,SOCK_RAW代表原始套接字。
第三个参数默认为0,如果第二个参数不是一个的话,这个参数就会与之改变。
bind()函数
bind(int fd, sockaddr * addr, socklen_t len)
这个格式需要我们每次写socket的时候写强制转换,是因为这个出来的时候是82年,还没有C还没有void*的格式。
struct sockaddr_in addr;
bind(sock,(struct sockaddr*)&addr,sizeof(addr);
通用的结构体
/* 描述通用套接字地址 */
struct sockaddr
{
sa_family_t sa_family; /*地址族*/
char sa_data[14]; /*地址值*/
};
常用的结构体
struct sockaddr_in
{
short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
};
注:这里的地址族和上面socket函数填的地址族不太一样,
这里是这样的AF_INET而不是PF_INET。第一个字母不同,但是去头文件看这两者的宏定义是一样的。
对于bind函数,写客户端会不知道网络地址的,而且不知道会在上面硬件上使用,所以我们一般使用通配地址。比如192.168.1.1和10.1.8.1这两个,会同时收到数据。
struct sockaddr_in addr;
addr.sin_addr.s_addr=htonl(INADDR_ANY); /* IPV4 通配地址 */
还有端口,如果把端口设置为0,就是相当于把端口的选择权交给内核处理,操作系统会根据算法选择一个空闲的端口,完成套接字的绑定,服务器不会这样做。
listen()函数
int listen(int sockerfd,int backlog);
初始化创建的套接字,可以认为是一个“主动套接字”,其目的主动发起请求,通过listen函数,可以将原来的这个主动套接字转换称为被动套接字,就是告知系统内核我这个套接字我想让它来等待用户请求的,然后内核就会做好接受用户请求的所有准备,就比如弄了两条队列。
第一个参数为套接字描述符,就是用来转化的。第二个参数为backlog,官方的解释是未完成连接队列的大小,这个参数的大小决定了可以接受的并发数目。
accept()函数
int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
第一个参数是listen套接字,这个函数有两个返回值,第一个是通过指针的方式获取客户端的地址,另一个是函数的返回值。
这种写法分离了数据,比较牛。
connect()函数
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
客户调用这个函数前不必一定调用bind函数,如需要内核会自己调用的,因为这个主动连接的一方,是端口是不用固定的。
第一个参数是套接字,第二个是地址结构体,第三个是长度。
这个函数只有在函数调用成功或者失败才会返回。
失败主要有几个原因:
1、服务端端口错了,连接拒绝。
2、第一次握手的SYN包没有答复,一般是ip写错。
3、没网。
以上就是socket的几个主要且重要的函数。