一.基本的几个函数
1.socket()
2.connect
int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
参数:sock是socket返回的套接字描述符,第二个和第三个参数是服务器的套接字地址结构的指针和该结构的大小。
作用:tcp客户端通过connect函数建立与tcp服务器的连接,调用connect函数将激发三次握手过程。
3.bind
int bind(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
bind函数把一个本地协议地址(套接字地址结构)赋予一个套接字,对于服务器端,限定了该套接字只接受那些目地的为指定的套接字地址的客户端的连接,对于客户端,则是指定源ip地址和源端口号。
为什么一般情况下服务器端会调用bind()函数,客户端不调用bind()函数呢?
因为如果不调用bind函数,套接字的ip地址和端口号就会由内核来选择,对于客户端来说是很正常的,但是服务器端需要绑定它的众所周知端口号以及它的源ip地址
4.listen
int listen(int sockfd,int backlog);
listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求,调用listen将导致套接字从CLOSED状态转换到LISTEN状态。
未完成连接队列:这些套接字已经完成三次握手中第一次握手,也就是客户端的SYN已经到达,服务器正在等待完成三次握手过程,这些套接字处于SYN_RCVD状态
已完成连接队列:这些套接字已经完成三次握手过程,处于ESTABLISHED状态。当调用accept时,会从已完成连接队列中选取队头的套接字返回给进程,如果队列为空,进程将进入睡眠,直至该队列中被放入一项才被唤醒。
第二个参数指定了这两个队列的总和的最大值
5.accept
int accept(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
从已完成连接队列中返回下一个已完成连接,如果队列为空,那么进程就会进入睡眠直至该队列中被放入一项才被唤醒。
第二个参数和第三个参数用来返回已连接客户端的套接字地址结构和长度。
如果accept成功,将返回一个已连接套接字描述符。
已连接套接字描述符和监听套接字描述法的区别:
监听套接字描述符:由socket创建,随后用于bind和listen的第一个参数的描述符,一个服务器通常仅仅创建一个监听套接字描述符,在服务器的生命期一直存在。
已连接套接字描述符:由accept返回的描述符为已连接套接字描述符,内核为每一个已连接(已经完成了三次握手过程)客户端创建一个已连接套接字描述符。
二.一些小问题
1.tcp和udp可以监听同一个端口吗?
可以。
可以看到bind函数的作用是把一个套接字地址结构赋予一个套接字,绑定了套接字的IP地址和端口号。
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
sockaddr参数是一个套接字地址结构。
sockaddr结构如下:
sa_family_t sin_family /*协议*/
in_port_t sin_port /*端口*/
struct in_addr sin_addr /*ip*/
unsigned char sin_zero[8] /*sizeof(struct sockaddr)*/
可以看到linux是以协议、ip、端口来绑定ip和端口的,所以不同协议相同的ip和端口也是可以绑定成功的。