TCP套接口函数

TCP套接口函数
简介

TCP(Transmission Control Protocol)为应用程序提供的是可靠的数据传输服务

  • 文件传输协议(FTP)
  • 超文本传输协议(HTTP)
  • 邮局协议(POP3)

TCP连接上传输的都是字节流,它不对数据做任何解释,只保证数据有序、可靠地到达目的主机。

TCP是全双工的,一个方向停止了工作,另一个方向还可以继续传输数据。

说明:

  1. TCP是操作系统中一个必备的模块,是作为操作系统的一部分实现的。
  1. 使用TCP通信的两个应用程序不是对等的,一个作为服务器、另一个作为客户端
  1. 如果服务器还没有运行,客户就已经来了,客户端的连接就会失败,错误码是端口不可达
  1. 服务端至少有两个socket

    • 一个是侦听socket,等待客户来建立连接
    • 其余的是接收到客户的连接请求而创建的新的socket,用于与客户交互
  2. 客户端调用connect导致TCP协议开始三次握手,向服务器发送SYN,SYN到达服务器,服务器的TCP检查客户要求的服务,创建一个新的socket为客户服务,并发送SYN和ACK确认客户的SYN,客户端收到SYN和ACK后,发送ACK完成三次握手

通信流程:

在这里插入图片描述

connect

connect函数最主要的功能就是建立起客户与服务器的连接

客户端调用connect发起主动连接,TCP协议开始三次握手过程。三次握手完成connect才会返回成功,双方就可以在这个连接上交换数据了。

连接建立过程中,如果调用了send发送数据,这些数据会暂时保留在本地TCP缓冲区队列中,connect成功后才发送。

int WSAAPI connect(SOCKET s, const struct sockaddr FAR*name,int namelen);
  • 成功返回0;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。
  • name: 套接口地址结构指针,包含服务器的IP地址和端口号

说明:

  1. 在connect之前不必非得调用bind,如果没有调用系统会帮助选择源IP地址和临时端口号;

    调用bind时,发出数据报中的源IP地址和端口号就用程序指定的。

  2. connect既可用于TCP也可用于UDP套接口

    对于TCP类型的套接口,它启动了三次握手过程,报文交换需要一段时间,调用connect不会立即得到结果,只有连接成功或发生错误时才会返回,通常的错误有下面几种情况。

    • 连接被拒绝:WSAECONNREFUSED, 通常是由于没有进程在指定的端口上侦听,或者是由于一些其他原因,如安全控制等,不允许服务器接受到达的连接。

    • 目的不可达:WSAENETUNREACH、WSAEHOSTUNREACH,这是因为客户发出的连接请求SYN在路由过程中无法到达目的主机,在中间路由器引发了ICMP不可达错误。 协议对这种错误的处理通常是保存此错误码,并按照协议规定的时间间隔继续发送SYN,如果错误恢复了,能够与服务器建立连接,就抛弃错误码;如果超时还无法连接成功,就向程序返回此错误。

      但一收到ICMP目的不可达就放弃连接是不正确的,因为这种情况可能是暂时的

    • 超时:WSAETIMEDOUT, 连续发送SYN,但一直收不到服务器的确认ACK,标准实现的超时是75秒,收不到对方的ACK,便返回超时错误。

  1. 在一个阻塞socket上,返回值立即指明了成功或者失败。

    对于非阻塞socket,如果连接不能立即完成,connect返回SOCKET_ERROR,程序调用WSAGetLastError将返回WSAEWOULDBLOCK,应用程序可以通过下面的情况来判断什么时候连接成功了:

    1. 通过select函数,如果连接成功,socket将是可写的;
    2. 如果应用程序使用了WSAAsyncSelect,并且指明对连接事件感兴趣,当连接完成时会收到FD_CONNECT通知
  2. 当已经连接的socket中断时,为了返回到一个稳定的状态,程序应该重新创建socket,但如果错误码是WSAECONNREFUSED、WSAENETUNREACH或WSAETIMEDOUT时,应用程序可以再次调用connect函数

  3. UDP套接字上调用connect,并不真正与对方建立连接,只是把对方的地址和端口保存到控制块中,设置了默认的目的地址,在随后的send或recv中使用,不与目的地址匹配的数据报将被扔掉。如果目的地址是全0,将断开连接,目的地址是没有明确说明的,send或recv调用将返回WSAENOTCONN,但仍然可以使用sendto或recvfrom函数。

    即使socket已经连接,也可以再次调用connect改变目的地址,如果本次地址与之前的地址不同,在队列中接收到的数据报将被抛弃。

listen

listen是只能由服务器端使用的函数,适用于面向连接的套接口

需要在socket、bind之后,accept函数之前调用

刚创建的套接口默认是主动模式,函数listen把一个未连接的套接口转变成被动模式,告诉系统在这个套接口上接受连接请求,套接口所对应的TCP控制块也从CLOSED状态转变到LISTEN状态

int WSAAPI listen(SOCKET s, int backlog);
  • 成功返回0;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。
  • WinSock2.h中,把SOMAXCONN定义为0x7fffffff,当backlog等于SOMAXCONN时,底层协议的提供者把backlog设置为一个最大的“合理”值。

说明:

  1. 当一个客户到达时,如果队列已满,WinSock规范描述中说客户将收到连接错误WSAECONNREFUSED。
  1. 在FreeBSD中,把处于SYN_RCVD( 已经发送了SYN/ACK,等待ACK) 状态和在ESTABLISHED状态但还没有被应用程序调用accept接受的连接都算做是“待处理连接”。
accept

accept适用于面向连接的套接口,也是只能由服务器使用的函数,紧跟在listen函数调用之后。

返回已完成连接队列中的第一个连接所对应的套接口描述符,如果已完成连接队列为空 ,默认情况下,阻塞调用进程,直到有客户与服务器建立了连接才返回给调用者。

SOCKET WSAAPI accept(SOCKET s,struct sockaddr FAR * addr,int FAR * addrlen);
  • 成功返回类型为SOCKET的描述符,是已经被接受的连接所对应的套接口;失败返回INVALID_SOCKET,应用程序可以调用WSAGetLastError()得到具体的错误码。

  • addr,可选的参数,指向缓冲区,用于接收连接实体的地址,地址格式由创建套接口时指定的地址簇来确定

    参数addr是结果参数,保存连接实体的地址。

  • addrlen,可选参数,指向整数的指针,包含了addr缓冲区的长度。

    addrlen是值/结果参数,传入时是addr缓冲区的长度,返回后包含了地址addr的实际长度。

    如果addr和addrlen都为NULL或者有一个为NULL,系统不会把已连接套接口对端的地址信息返回给调用进程。

说明:

  1. 已连接套接口是系统为每个被接受的客户新创建的套接口,客户和服务器之间完成了三次握手,由已连接套接口处理来自客户的请求,交互完成就把已连接套接口关闭。
  1. TCP建立连接过程

    客户端调用connect时,向服务器发送SYN,服务器收到SYN,为进入的连接创建新的套接口,把它添加到未完成连接队列中,向客户发送SYN和ACK。

    客户收到SYN和ACK,向服务器发送ACK,connect函数返回,表明成功建立连接。

    ACK一旦到达服务器就完成了三次握手,服务器把未完成连接转移到已完成连接队列中,套接口的状态也从SYN_RCVD转变到ESTABLISHED,并唤醒服务进程,accept返回。
    在这里插入图片描述

send

函数send在一个已经连接的套接口上向对方发送数据。

使用TCP的套接口,发送的数据没有限制,UDP套接口每次发送的数据不能超过底层协议最大数据报的大小,如果数据超过了底层允许的最大数据报,函数将失败,错误码为WSAEMSGSIZE,数据不被传输。

调用getsockopt,用选项SO_MAX_MSG_SIZE能够得到可以发送的最大数据报长度。

IP首部20、UDP首部8

unsigned int msg_size = 0;
int opt_len = sizeof(msg_size);
getsockopt(s, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *)&msg_size, &opt_len);
int WSAAPI send(SOCKET s,const char FAR*buf,int len,int flags);
  • 成功返回发送数据的长度,可能小于参数len的长度;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。

  • buf,包含了要发送的数据;

  • len,buf中数据的长度;

  • flag,规定了调用方式, 影响send函数的行为,可能是0或者下表中一个或者多个常量值的逻辑或
    在这里插入图片描述

    • MSG_DONTROUTE,告诉协议不用查找路由表,目的主机在直接相连的网络上

      它与套接口选项SO_DONTROUTE的差别是:SO_DONTROUTE影响这个套接口所有的输出操作(本次及以后);MSG_DONTROUTE只对本次发送起作用,下次发送如果没有设置这个标志,仍然会查找路由表。

说明:

  1. 函数send成功返回,意味着数据已经从用户进程复制到了协议发送缓冲区中,并不表明数据已经成功发送出去

  2. 协议发送数据可能失败,对于TCP协议,会重传数据,对于UDP数据就丢失了

    使用UDP套接口,虽然send返回成功了,但可能发送过程中数据丢失了。

    如果想确保数据一定发送给对方,那么要在应用程序中自己来保证,如要求对方收到数据时必须确认等,UDP协议并不保证数据的可靠传输。

  3. 如果传输层没有缓冲区空间容纳要求发送的数据,默认情况下,send将阻塞发送进程;

    非阻塞模式下,面向连接的套接口根据本地和对端主机缓冲区大小,发送最大可能发送的数据。因此发送的数据长度可能介于1到要求的长度之间。

  4. 发送0长度的数据也是允许的,send返回值将是0,对于面向连接的套接口,会立即返回;而无连接套接口,会向对方发送0长度的数据报。

recv

函数recv从套接口接收数据,用于已经连接的套接口或者已绑定的套接口。

当套接口上没有数据时,默认情况下,recv阻塞调用进程,等待数据到达;非阻塞时,返回SOCKET_ERROR,错误码为WSAEWOULDBLOCK。程序可以用select或者WSAAsyncSelect来确定什么时候有数据到达

int WSAAPI recv(SOKCET s, char FAR*buf,int len,int flags);
  • 调用成功时,返回接收的字节数。

    返回值为0时,对于面向连接的socket,表示对方正常关闭了这个连接,对于无连接socket,是收到了对方发送的0长度的数据报;

    发生错误时返回SOCKET_ERROR,调用WSAGetLastError()得到具体的错误码。

说明:

  1. 面向连接套接口,接收数据通常使用recv,但也可以使用recvfrom,除了最后两个参数被忽略外
  2. 系统将把尽可能多的数据复制到接收缓冲区中,但不能超过缓冲区的长度。协议中的数据大于recv提供的缓冲区长度时,系统将把len字节的数据复制到buf中,其他的数据仍然保留在队列缓冲区中。
  3. 如果在套接口上调用了SO_OOBINLINE配置为内连接收带外数据,并且协议接收队列中有未读的带外数据,则只返回带外数据。
  4. 对端正常关闭连接时,recv立即返回0,如果连接被重置,recv调用失败,返回SOCKET_ERROR,错误码为WSAECONNRESET。
  5. 无连接套接口,如UDP,只把输入队列中的第一个数据报复制到buf中,如果数据报的长度比缓冲区大,将只把数据报前面len字节的数据复制到buf中,多余的数据会丢失,recv产生错误码WSAEMSGSIZE。
  6. socket类型为UDP时,返回0,与使用TCP的socket是不同的,它不是对方关闭了连接,只表示接收到了一个0长度的数据报,因为UDP是无连接的,没有关闭连接操作
  7. 标志flags影响recv函数的行为,可以是0或者一个或多个常量值的逻辑或组成,与recvfrom是相同的
shutdown

shutdown函数让套接口不能在某一个方向上传输数据

int WSAAPI shutdown(SOCKET s,int how);
  • 成功返回0;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。

  • how,标志,指明不再允许何种类型的操作。

    • how为SD_SEND,不允许之后的发送操作。TCP将向对方发送FIN,正常关闭发送方向的连接,但套接口仍然可以从对方接收数据,这是一个半关闭。当套接口缓冲区中还有数据时,TCP将把数据发送给对方,最后再发FIN,正常终止连接。

    • how为SD_RECEIVE,则关闭连接上的读操作,不再允许套接口上的读操作。MSDN说:SD_RECEIVE对底层协议没有影响。

      TCP协议的窗口不会改变,如果套接口上仍有数据等待接收或随后还有数据到达,连接被重置,发送RESET。

      UDP协议,输入的数据报被接受并且排队。两种协议都不会产生ICMP错误数据报。

    RESET在RFC 793(Transmission Control Protocol)中是重置连接,整个连接被放弃了,这同时关闭了两个方向的数据传输。

    • how为SD_BOTH时,发送和接收都被禁止。

说明:

  1. TCP连接的半关闭
    在这里插入图片描述

  2. shutdown在网络编程中用得很少,套接口不再使用时,通常都直接调用closesocket。

  3. shutdown主要用于面向连接的套接口,为了确保所有数据被发送和接收,应用程序应该在调用closesocket前用shutdown正常关闭连接。调用shutdown后,不能再重用这个套接口,尤其不允许再调用connect。

  4. shutdown与closesocket的主要区别:

    • 函数shutdown不关闭socket,也不释放套接口占用的资源;closesocket会释放资源。
    • 每个套接口都有一个引用计数,closesocket把套接口的引用计数减1,减到0时关闭该套接口;shutdown不检查引用计数,无论其值是否为0都会执行,对于how为SD_SEND时总会发送FIN,正常终止发送方向上的连接。
    • shutdown对套接口提供了更精确的控制,对TCP的全双工连接,可以关闭任何一个方向上的连接,而另一方向不受影响;closesocket总是关闭两个方向上的数据传输。
    • 函数shutdown不阻塞调用进程,不受SO_LINGER选项的影响
getpeername

得到一个已连接套接口的对端地址

int WSAAPI getpeername(SOCKET s, struct sockaddr FAR*name,int FAR *namelen);
  • 成功返回0,name包含了远端地址,namelen是name中地址的实际长度;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。

这个函数几乎没用,因为在一个已经连接的socket上,对于客户端,一定调用了connect,而connect的最后两个参数就是远端的地址;对于服务器,如果对远端的地址感兴趣,可以通过accept来获得。

getsockname

得到套接口的本地地址

没有事先调用bind,或bind中的端口、地址是通配值时,连接成功后想获得系统为本地套接口分配的地址时,这个函数特别有用。

int WSAAPI getsockname(SOCKET s,struct sockaddr FAR *name,int FAR *namelen);
  • 成功返回0,name包含了本地地址,namelen是name中地址的实际长度;失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。

说明:

  1. 函数getsockname用于已经绑定或者连接的套接口上,主要用于以下几种情况

    • 应用程序在套接口上没有调用bind,而是直接调用了connect函数,系统会为应用程序选择合适的本地地址,并分配一个未用的端口。如果想知道系统分配给此套接口的本地地址和端口,可以在connect成功返回后,用getsockname得到。
    • 用bind绑定时,端口号为0,要求系统为应用程序分配端口,getsockname返回由系统分配的端口号。
    • 调用bind绑定的是一个通配地址INADDR_ANY,告诉系统可以使用任何在本机上可用的地址,特别在多穴(multi-homed)主机上,我们无法知道最后使用了哪个地址。
  2. 当套接口还没有连接时,调用getsockname可能无法得到本机地址的信息,只有套接口已经连接成功时,系统才会根据进入的连接选择一个匹配的本地地址。

  3. 服务器上,调用getsockname的套接口必须是已连接的套接口,而不能是侦听套接口。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暴风雨中的白杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值