1、创建套接字
int socket(int domain,int type,int protocol);
//domain:该参数一般被设置为AF_INET,表示使用的是IPv4地址。还有更多选项可以利用man查看该函数
//type:该参数也有很多选项,例如SOCK_STREAM表示面向流的传输协议,SOCK_DGRAM表示数据报,我们这里实现的是TCP,因此选用SOCK_STREAM,如果实现UDP可选SOCK_DGRAM
//protocol:协议类型,一般使用默认,设置为0
该函数用于打开一个网络通讯接口,出错则返回-1,成功返回一个socket(文件描述符),应用进程就可以像读写文件一样调用read/write在网络上收发数据。
2、绑定
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
//sockfd:服务器打开的sock
//后两个参数可以参考第四部分的介绍
服务器所监听的网络地址和端口号一般是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind来绑定一个固定的网络地址和端口号。bind成功返回0,出错返回-1。
bind()的作用:将参数sockfd和addr绑定在一起,是sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。
3、监听
int listen(int sockfd,int backlog);
//sockfd的含义与bind中的相同。
//backlog参数解释为内核为次套接口排队的最大数量,这个大小一般为5~10,不宜太大(是为了防止SYN攻击)
该函数仅被服务器端使用,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
4、接收连接
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
//addrlen是一个传入传出型参数,传入的是调用者的缓冲区cliaddr的长度,以避免缓冲区溢出问题;传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。
典型的服务器程序是可以同时服务多个客户端的,当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。
三次握手完成后,服务器调用accept()接收连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
5、请求连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
这个函数只需要有客户端程序来调用,调用该函数后表明连接服务器,这里的参数都是对方的地址。connect()成功返回0,出错返回-1。
客户端程序和服务器程序建立连接的过程:
服务器:首先调用socket()创建一个套接字用来通讯,其次调用bind()进行绑定这个文件描述符,并调用listen()用来监听端口是否有客户端请求来,如果有,就调用accept()进行连接,否则就继续阻塞式等待直到有客户端连接上来。连接建立后就可以进行通信了。
客户端:调用socket()分配一个用来通讯的端口,接着就调用connect()发出SYN请求并处于阻塞等待服务器应答状态,服务器应答一个SYN-ACK分段,客户端收到后从connect()返回,同时应答一个ACK分段,服务器收到后从accept()返回,连接建立成功。客户端一般不调用bind()来绑定一个端口号,并不是不允许bind(),服务器也不是必须要bind()。