socket套接字包含标准套接字(SOCK_STREAM,SOCK_DRAGM)以及原始套接字(SOCK_RAW),一般我们进行网络编程有标准套接字就够了,但如果要实现标准套接字(即TCP,UDP套接字)不能实现的功能,就需要用原始套接字了。这里还是主要总结一下标准套接字的用法。
如前所述,标准套接字分为TCP协议(SOCK_STREAM)和UDP协议(SOCK_DRAGM)两种type的工作流程,因为TCP是面向连接的服务,所以TCP网络编程会更复杂一点。不过不论是TCP还是UDP,其socket网络编程模式都是类似的,分为客户端和服务端。
- 客户端:在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序。
- 服务端: 和客户端相对应的程序即为服务端程序。被动的等待外面的程序来和自己通讯的程序称为服务端程序。
这张图将TCP数据交互的流程与socket函数作了一一对应,十分清楚,接下来就对其中的函数做一个整理介绍。
2.1 创建socket套接字
int socket(int family, int type, int protocol)
功能介绍:
在Linux操作系统中,一切皆文件,网络程序通过socket和其它几个函数的调用,会返回一个 通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处。。socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。
参数说明:
- family:说明我们网络程序所在的主机采用的通讯协族(AF_INET和AF_UNIX等)。
AF_UNIX只能够用于单一的Unix 系统进程间通信,
而AF_INET是针对Internet的,因而可以允许在远程 - type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等)
SOCK_STREAM表明我们用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信。 - protocol:具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。
2.2 地址端口绑定函数bind
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)
功能介绍:
bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度 struct sockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。在客户端模式中不需要使用bind函数。当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。
功能介绍:
刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度有listen中的backlog参数来设定。listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。当listen运行成功时,返回0;运行失败时,返回值位-1。
参数说明:
- sockfd:是由socket调用返回的文件描述符
- backlog:server端可以缓存连接的最大个数,也就是等待队列的长度。
2.4 接受网络请求函数accept
int accept(int sockfd, struct sockaddr *client_addr, socklen_t *len)
功能介绍:
接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。
参数说明:
- sockfd:是由socket调用返回的文件描述符
- client_addr是本地服务器端的一个struct sockaddr类型的变量,用于存放新连接的客户端的协议族,网络地址以及端口号等,是用来给客户端的程序填写的,无需服务端填写
- len:是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(struct sockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。
2.5 连接目标服务器函数connect
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
功能介绍:
连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。
参数说明:
- sockfd:是由socket调用返回的文件描述符
- serv_addr:是一个struct sockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号
- addrlen:表示第二个参数内容的大小,与accept不同,这个值不是一个指针
以上是TCP建立连接所需的五个函数(UDP所需函数也一样,只不过因为没有连接所以不需要listen,accept,connect函数)。在服务器端和客户端建立连接之后是进行数据间的发送和接收,TCP主要使用的接收函数是read或recv,发送函数是write或send;而UDP使用的则是recvfrom和sendto。
2.6 TCP写函数write
ssize_t write(int sockfd, const void *buf, size_t nbytes)
功能介绍:
write函数将buf中的nbytes字节内容写入文件描述符fd。成功时返回写的字节数。失败时返回-1。 并设置errno变量。
参数说明:
- sockfd:对于服务端是accept调用返回的文件描述符,对于客户端是由socket调用返回的文件描述符
- buf:发送数据缓冲区,要发送的数据会放在这个指针指向的内容空间中;
- nbytes:发送缓冲区的大小
2.7 TCP读函数read
ssize_t read(int sockfd, const void *buf, size_t nbytes)
功能介绍:
read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误。
参数说明:
- sockfd:对于服务端是accept调用返回的文件描述符,对于客户端是由socket调用返回的文件描述符
- buf:用于存储接收到的数据缓冲区,接收的数据将放到这个指针所指向的内容的空间中
- nbytes:接收缓冲区的大小
2.8 TCP发送及读取数据函数send,recv
int recv(int sockfd, void *buf, int len, int flags)
int send(int sockfd, void *buf, int len, int flags)
功能介绍:
recv和send函数提供了和read和write差不多的功能。不过它们提供 了第四个参数来控制读写操作。
参数说明:
前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合 (如果flags为0,则和read,write一样的操作。还有其它的几个选项,不过我们实际上用的很少)
MSG_DONTROUTE 不查找路由表
MSG_OOB 接受或者发送带外数据
MSG_PEEK 查看数据,并不从系统缓冲区移走数据
MSG_WAITALL 等待所有数据
2.9 UDP发送数据函数sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flag, const struct sockaddr *to, socklen_t tolen)
功能介绍:
sendto函数主要根据填充的接收方的地址信息向客户端或者服务器端发送数据,接收方的地址信息会提前设置在struct sockaddr类型的参数指针中,当返回值-1时,表明发送失败,当返回值大于等于0时,表示发送成功,并且发送数据的大小会通过返回值传递回来。
参数说明:
- sockfd:由socket创建的文件描述符;
- buf:发送数据缓冲区,要发送的数据会放在这个指针指向的内容空间中;
- len:发送缓冲区的大小;
- to:一个struct sockaddr类型的指针,其指向地址的内容是接收方地址信息;
- tolen:表示第5个参数指向的数据内容的长度,传递的是值,可以用sizeof(struct sockaddr)计算。
2.10 UDP接收数据函数recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen)
功能介绍:
对于该函数主要的功能是,从客户端或者服务器端接收数据以及发送方的地址信息存储到本地的struct sockaddr类型参数变量当中,如果函数返回-1,所说明接收数据失败,如果返回的是大于等于0的值,则说明函数接收到的数据的大小。因为可以设置文件描述符的状态为阻塞模式,所以在没有接收到数据时,recvfrom会一直处于阻塞状态,直到有数据接收到。
参数说明:
- sockfd:创建socket时的文件描述符
- buf:用于存储接收到的数据缓冲区,接收的数据将放到这个指针所指向的内容的空间中
- len:接收缓冲区的大小
- from:指向struct sockaddr的指针,接收发送方地址信息
- fromlen:表示第5个参数所指向内容的长度,可以使用sizeof(struct sockaddr)来定义大小,不过因为是要传给内核协议栈,所以使用了指针类型
数据的发送和接收结束以后需要关闭套接字,关闭套接字有两个函数close和shutdown。TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。针对不同的howto,系统回采取不同的关闭方式。
2.11 关闭函数close
int close(int sockfd)
功能介绍:
当我们使用close时,会把读写通道都关闭。不过,close只会关闭本进程的 socket id,但连接还是开着的,用这个socket id的其他进程还能用这个连接进行读写。使用close中止一个连接,其实它只是减少文件描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭。
参数说明:
- sockfd:创建socket时的文件描述符
2.12 关闭函数shutdown
int shutdown(int sockfd, int howto)
功能介绍:
shutdown函数针对不同的howto,系统回采取不同的关闭方式,可以选择只中止一个方向的连接。与close不同的是,shutdown不考虑文件描述符的参考数,直接关闭文件描述符。在多进程程序里面,如果有几个子进程共享一个套接字时,如果我们使用shutdown,那么所有的子进程都不能够操作了,这个时候若想只关闭其中一个子进程的套接字描述符我们只能够使用close。