TCP(Transmission Control Protocol)为应用程序提供可靠的数据传输服务,它是网络编程中使用最广泛的一种(传输层)协议。
使用TCP通信的两个应用程序,一个作为服务器,另一个作为客户端。
下图是设计TCP程序的基本流程。
服务器端会有两个socket(并发服务器会有多个),其中一个是侦听socket(图中srv),等待客户来建立连接。另一个是接收到客户端连接请求而建立的新的socket,用于与客户端交互(图中clt)。
服务器启动时首先创建侦听socket srv,绑定本地地址,并在srv上侦听,调用accept后阻塞,直到有客户建立连接。accept返回一个新的socket clt,与客户端之间的交互全部通过clt完成。
当有一方关闭了连接,clt的任务完成,需要closesocket释放占用的资源。socket srv可以继续为其他的客户提供服务,通常不会关闭。
客户端先创建一个socket,调用connect与服务器建立连接。在connect的参数中指明要连接的服务器地址和端口,调用connect会导致TCP协议的三次握手。connect返回之后,三次握手完成,双方可以通过send和recv进行通信。
connect
int connect( SOCKET s, const struct sockaddr FAR* name, int namelen);
connect函数用于建立客户端与服务器端的连接。客户端调用connect发起主动连接,TCP协议开始三次握手过程,三次握手完成,connect返回成功,双方就可以在这个连接上交换数据了。
参数s是建立连接的socket句柄,name包含服务器地址和端口号。此函数成功返回0,失败返回SOCKET_ERROR,可以调用WSAGetLastError()获取具体错误码。如果错误码为WSAETIMEDOUT,代表超时。客户端连续发送SYN,但一直收不到服务器的确认ACK。标准实现的超时时间是75秒。
如果是阻塞socket,返回值立即指明了connect的成功或者失败。对于非阻塞socket,如果连接不能立即完成,connect返回SOCKET_ERROR,错误码为WSAEWOULDBLOCK,应用程序可以通过下面的情况来判断什么时候连接成功:(1)通过select函数,如果连接成功,socket将是可写的;(2)如果应用程序使用了WSAAsyncSelect,并且指明对连接事件感兴趣,当连接完成时会收到FD_CONNECT通知。
listen
int listen(SOCKET s, int backlog);
listen函数由服务器端使用,告诉系统在这个socket上接受连接请求。参数backlog称为“待处理连接的最大数目”,设置为SOMAXCONN,底层协议会把backlog设置为一个最大的“合理”值。
此函数成功返回0,失败返回SOCKET_ERROR,可以调用WSAGetLastError()获取具体错误码。
accept
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
accept函数用于接受客户端连接请求,仅由服务器端使用,紧跟在listen调用之后。如果连接队列为空,默认阻塞调用线程。
参数s是侦听socket,addr用于接收连接客户端的地址,addrlen在传入时是缓冲区addr的长度,返回后包含了地址addr的实际长度。
accept成功将返回一个已连接socket,与客户端之间的交互全部通过该socket完成。失败则返回INVALID_SOCKET。
服务器端函数accept与客户端connect相对应。客户端调用connect时,向服务器发送SYN,服务器收到SYN,为进入的连接创建新的socket,把它添加到未完成连接队列中,向客户端发送SYN+ACK。客户端收到SYN和ACK,向服务器发送ACK,connect函数返回,表明成功建立连接。ACK一旦到达服务器就完成了三次握手,服务器把未完成连接转移到已完成连接队列中,socket的状态也从SYN_RCVD转变到ESTABLISHED,并唤醒服务线程,accept返回。
send
int send( SOCKET s, const char FAR* buf, int len, int flags);
函数send在一个已连接的socket上向对方发送数据,对于tcp协议,发送的数据量大小没有限制。send成功返回发送数据的长度,可能小于参数len的长度,失败返回SOCKET_ERROR,可以调用WSAGetLastError()获取具体错误码。
参数s是已连接的socket,buf包含要发送的数据,len是buf中数据的长度,参数flags影响send函数的行为。flags可以设置为0,或者是MSG_DONTROUTE(不检查路由表,目的主机在直接相连的网络上),或者是MSG_OOB(发送带外数据),或者是它们的组合。
recv
int recv( SOCKET s, char FAR* buf, int len, int flags);
recv从已连接套接字s接收数据。buf是用于接收数据的缓冲区,len是buf的长度。flags可以设置为0,或者是MSG_PEEK(查看是否有输入的数据可以读取,如果有数据会被复制到recv提供的缓冲区,但并不把数据从输入队列中删除),或者是MSG_OOB(接收带外数据),或者是它们的组合。
当套接字s上没有数据时,默认recv会阻塞调用线程,等待数据到达。非阻塞时,返回SOCKET_ERROR,错误码为WSAEWOULDBLOCK,程序可以调用select或者WSAAsyncSelect来确定什么时候有数据到达。
recv调用成功时,返回接收的字节数。当返回0时,对于面向连接的socket,表示对方正常关闭了这个连接。发生错误时,返回SOCKET_ERROR,可以调用WSAGetLastError()获取具体错误码。