知其然更要知其所以然,虽然这几个函数已经写过不少次,却一直不知道它们究竟做了什么
一,socket
int socket(int family,int type,int protocol);
仅仅申请了一个sock fd而已,并没有与任何地址相关联,对于TCP, family=AF_INET, type=SOCK_STREAM,protocol=0/IPPOROTO_TCP
二,connect
int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
这个函数完成了TCP的三次握手,可以根据errno得到出错原因,一般是超时或服务器端口没开
三,bind
int bind(int sockfd,const struct socaddr* myaddr,socklen_t addrlen);
这个函数完成sockfd与地址(包括ip和端口)的绑定, 注意如果不绑定,内核将会随机分配(ip或端口),一般客户端不绑定,服务器需绑定一个端口
四,listen
int listen(int sockfd,int backlog);
当一个socket创建后,默认是主动套接字, 这个函数将其转成被动套接字. 按TCP状态来说,从CLOSED转成LISTEN
backlog一般是5, 大概限定了允许正在进行三次握手过程的连接的数量
五,accept
int accept(int sockfd,struct sockaddr* cliaddr,socklen_t* addrlen);
服务器函数,用于从已完成三次握手的队列头返回一个已完成连接, 如果没有则一直阻塞,并返回新连接的sockfd
六,fork
pid_t fork(void);
新建一个进程,一次调用两次返回,在子进程返回0,父进程返回子进程id. 多进程是实现服务器并发的方法之一,但显然多线程更为常用
当fork出一个新进程后,每个sockfd的引用计数都+1,而只有一个fd的引用计数降为0后才会被关闭(四次挥手),而每次close只是引用计数-1, 所以子进程和父进程都要关闭fd.
通常父进程会立刻关闭accept产生的新fd,子进程会立刻关闭listen fd, 代码如下:
..... while(1) { newfd=accept(listenfd,&addr,&len); if((pid=fork())==0) { //子进程 close(listenfd); ...... close(newfd); exit(0) } //父进程 close(newfd); }
int getsockname(int sockfd,struct sockaddr* localaddr,socklen_t* addrlen);
int getpeername(int sockfd,struct sockaddr* peeraddr,socklen_t* addrlen);
这两个函数从套接字获取绑定于其上的本地或远程地址