【linux网络编程之socket(二):C/S程序的一般流程和基本socket函数】
一、基于TCP协议的网络程序
下图是基于TCP协议的客户端/服务器程序的一般流程:
服务器调用socket()、bind()、listen()(持续等待)完成初始化后,调用accept()阻塞等待(暂停等待),处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
*应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
*应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
补充一下:其实TCP 共有11种状态,上图没有出现的CLOSING 状态,当双方同时关闭连接时会出现此状态,替换掉FIN_WAIT2状态。
二、基本socket函数
1、socket函数
包含头文件<sys/socket.h>
功能:创建一个套接字用于通信
原型:int socket(int domain, int type, int protocol);
参数 domain :指定通信协议族(protocol family),AF_INET、AF_INET6、AF_UNIX等 type:指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW protocol :协议类型,IPPROTO_TCP等;一般由前两个参数就决定了协议类型,设置为0即可。 返回值:成功返回非负整数, 它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
2、bind函数
包含头文件<sys/socket.h>
功能:绑定一个本地地址到套接字
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 sockfd:socket函数返回的套接字 addr:要绑定的地址 addrlen:地址长度 返回值:成功返回0,失败返回-1
3、listen函数
包含头文件<sys/socket.h>
功能:将套接字用于监听进入的连接
原型:int listen(int sockfd, int backlog);
参数 sockfd:socket函数返回的套接字 backlog:已完成三次握手的最大连接个数 即允许连接的队列长度 返回值:成功返回0,失败返回-1
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列
如下图所示:
Be careful to differentiate between TCP accepting a connection and placing it on this queue, and the application taking the accepted connection off this queue.
Keep in mind that this backlog value specifies only the maximum number of queued connections for one listening end point, all of which have already been accepted by TCP and are waiting to be accepted by the application. This backlog has no effect whatsoever on the maximum number of established connections allowed by the system, or on the number of clients that a concurrent server can handle concurrently.
If there is not room on the queue for the new connection, TCP just ignores the received SYN. Nothing is sent back (i.e., no RST segment). If the listening server doesn't get around to accepting some of the already accepted connections that have filled its queue to the limit, the client's active open will eventually time out.
In the attempted connection to a nonexistent(不存在的) host, how frequently the client’s TCP sends a SYN to try to establish the connection. The second segment is sent 3s after the first, the third is sent 6s after the second, the fourth is sent 12s after the third, and soon. This behavior is called exponential backoff(指数补偿算法).
4、accept函数
包含头文件<sys/socket.h>
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 sockfd:服务器套接字 addr:将返回对等方的套接字地址 addrlen:返回对等方的套接字地址长度 返回值:成功返回非负整数,失败返回-1
5、connect函数
包含头文件<sys/socket.h>
功能:建立一个连接至addr所指定的套接字
原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 sockfd:未连接套接字 addr:要连接的套接字地址 addrlen:第二个参数addr长度 返回值:成功返回0,失败返回-1
参考资料:
《Linux C 编程一站式学习》
《TCP/IP详解 卷一》
《UNP》
原文地址:http://blog.csdn.net/jnu_simba/article/details/9025031