创建socket
#include<sys/types.h>
#include<socket.h>
int socket(int domain,int type,int protocol);
- domain:告诉系统使用哪个底层协议族,对于TCP/IP而言,参数应该设置为PF_INET(用于IPv4)或PF_INET6(用于IPv6);对于UNIX本地域协议族而言,参数应该设置为PF-UNIX。
- type:指定服务类型。服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务。对于TCP/IP协议族而言,二者分别表示使用TCP还是UDP。
自Linux内核2.6.17起,type也可以接受:SOCK_NONBLOCK和SOCK_CLOEXEC。分别将新创建的socket设为非阻塞,以及fork调用创建子进程时在子进程中关闭该socket。在Linux内核2.6.17之前,文件描述符这两个属性都需要使用额外的系统调用。 - protocol:是在前两个参数构成的协议集合下,在选择一个具体的协议,只不过这个值通常唯一。几乎所有情况,我们都把它设置为0,表示使用默认协议。
命名socket
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr* my_addr,socketlen_t addrlen);
bind:将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen指出所指socket地址的长度。
bind成功返回0,失败返回-1并设置errno。其中两种常见的errno是EACCES和EADDRINUSE。
- EACCES:被绑定的地址是受保护的地址,仅超级用户可以访问,如普通用户将socket绑定到知名服务端口(0~1023)时,bind将返回-1,errno设置为EACCES。
- EADDRINUSE:被绑定的地址正在使用中,比如将socket绑定到一个处于TIME_WAIT状态的socket地址。
监听socket
socket被命名之后,还不能马上接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放带处理的客户链接。
#include<sys/socket.h>
int listen(int sockfd,int backlog);
Linux内核2.2之前,backlog参数是指所有处于半连接状态(SYN_RECVD)和完全连接状态(EATABLISHED)的socket上限。自内核Linux2.2之后,backlog只表示处于完全连接状态的socket的上限,处于半连接状态的socket的上限由/proc/sys/net.ipv4/tcp_max_syn_backlog内核参数定义。
监听队列的长度如果超过backlog,服务器将直接丢弃第三次握手的ACK,也可能丢弃新的客户SYN请求,客户端收到ECONNREFUSED错误信息。
接受连接
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sock_addr *addr,socklen_t *addrlen);
如果监听队列中处于EATABLISHED状态的连接对应的客户端网络异常或提前退出,服务器对于这个连接执行的accept调用是否成功?
accept调用能够正常返回,且用netstat命令查看连接socket的状态,服务端也处于EATABLISHED状态。accept只是从监听队列中取出连接,并不关心连接处于什么状态(EATABLISHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化。
关闭连接
#include<unistd.h>
int close(int fd);
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。fork默认将父进程中打开的oscket引用计数加1,因此必须在父和子进程都执行close调用才能将连接关闭。
如果无论如何都要立即终止连接(而不是将引用技术减1),可以使用shutdown系统调用(相对于close来说,它是专门为网络编程设计的)。
#include<sys/socket.h>
int shutdown(int sockfd,int howto);
howto参数决定了shutdown的行为。
- SHUT_RD 关闭sockfd上读的这一半,接受缓冲区数据被丢弃。
- SHUT_WR 关闭sockfd写的这一半,sockfd的发送缓冲区会在真正关闭连接时发出去。这种情况下,连接处于半关闭状态。
- SHUT_RDWR 同时关闭sockfd的读和写。
shutdown能够分别关系socket的读、写、或都关闭。而close在关闭连接只能将socket的读和写同时关闭。
数据读写
用于TCP流数据读写的系统调用:
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
recv返回0,这意味着通信对方已经关闭连接了。
flags参数为数据收发提供了额外的控制。
- MSG_OOB:发送或接受紧急数据。
- MSG_NOSIGNAL:像读端关闭的管道或socket连接中写数据时不引发SIGPIPE信号(仍会有errno)
flags参数只对recv和send的当前调用生效,修改全局socket的某些属性可以通过setsockopt系统调用。
socket选项
SO_LINGER
SO_LINGER用于控制close系统调用再关闭TCP连接时的行为。默认情况下,close立即返回,TCP模块负责将对于发送缓冲区的数据发动给对方。
#include<sys/socket.h>
struct linger{
int l_onoff; /*开启or关闭*/
int l_linger; /*滞留时间*/
}
- l_onoff=0,该SO_LINGER选项不起作用,close()用默认行为关闭socket。
- l_onoff!=0,l_linger=0:close()立即返回,TCP模块丢弃被关闭socket的对应发送缓冲区的模块,同时给对方发送一个复位RST报文。这种情况给服务器提供了异常终止一个连接的方法。
- …