TCP是面向连接的协议,需要通信双方首先建立一个连接。因为TCP可靠、稳定的特点,它被应用于大部分场合,但它对系统资源要求比较高。
TCP协议服务端程序的开发流程如下所示。
// 初始化Winsock库,获得协议版本 // 创建服务Socket对象 (指定协议类型,地址族信息) // 绑定Socket (将指定的IP,端口绑定给Socket) // 开始监听,并且设置监听数量. (开始监听后,客户端就可以连接成功) // 开启端口,接收连接 // 收发数据(利用建立连接的Socket对象进行通信) // 关闭Socket连接 // 终止Winsock库的调用
开发一个TCP服务端程序,在完成初始化Winsock库和创建套接字(Socket)对象两个通用步骤后,还要完成如下步骤。
(1)绑定套接字到指定IP地址和端口
无论是使用哪种协议的服务端程序,都要将服务端的IP地址和端口绑定给先前创建的套接字,客户端程序将与之进行通信。绑定套接字的函数是bind,原型如下。
int bind( SOCKET s, //套接字句柄 const struct sockaddr FAR *name, //要绑定的地址 int namelen //name所指定的地址长度 );
第一个参数s是要绑定地址的套接字句柄,由socket函数返回。
第二个参数name是指向sockaddr结构体的指针,用来指定套接字所绑定的地址。
对于TCP、UDP协议,该地址通常是IP地址和端口号,对于TCP、UDP协议使用sockaddr_in结构体代替sockaddr。其定义如下。
struct sockaddr_in { short sin_family; //地址家族(地址格式),Windows为AF_INET u_short sin_port; //端口号 struct in_addr sin_addr; //IP地址 char sin_zero[8]; //占位值,通常为0 };
第一个成员sin_family同socket函数的af参数。
第二个成员sin_port指定了TCP或UDP通信服务的端口号。应用程序选择端口号时应该注意,如0~1023由IANA(Internet Assigned Numbers Authorith)管理,保留为公共服务使用,普通用户应用程序应该选择1024以上的端口号。同时需要注意,这里的值使用的是网络字节顺序,而计算机中存储的数字是主机字节顺序。Winsock库提供了一系列转换函数用于两种顺序之间的转换,如下所示。
u_short htons(u_short hostshort); //将u_short类型由主机字节顺序转换为网络字节顺序 u_long htonl(u_long hostlong); //将u_long类型由主机字节顺序转换为网络字节顺序 u_short ntohs(u_short netshort); //将u_short类型由网络字节顺序转换为主机字节顺序 u_long ntohl(u_long netlong); //将u_long类型由网络字节顺序转换为主机字节顺序
例如我们要使用端口6000,那么应该是sin_port = htons(6000);
第三个成员sin_addr用来存储IP地址,它被定义为一个联合来处理整个32位的值,定义如下。
struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un;
我们知道IP地址由四个部分组成,每个部分由点分开,通常是“xxx.xxx.xxx.xxx”格式。实际上IP地址是一个32位数据,即四个字节构成,每个字节对应由点分开的每个部分。所以每部分的最大值是255。如果直接使用字符串为此成员赋值,通常需要进行转换,Winsock库提供了如下转换函数。
unsigned long inet_addr(const char FAR *cp); char FAR * inet_ntoa(struct in_addr in);
其中inet_addr函数的作用是将用字符串表示的IP地址准换为网络字节顺序的32位值。
inet_ntoa函数的作用是将用网络字节顺序32位值表示的IP地址转换为用字符串表示的IP地址。
第四个成员sin_zero是为了与sockaddr结构大小相同而设置的,没有其他含义。
函数执行成功则返回0,否则返回SOCKET_ERROR,使用WSAGetLastError函数获得错误代码。
(2)监听连接
当套接字完成绑定指定IP地址和端口号后就应该设置套接字进入监听状态,也就是监听客户端的连接。使用listen函数,原型如下。
int listen( SOCKET s, //套接字句柄 int backlog //监听队列中允许保持尚未处理的最大连接数量);
当服务端设置套接字进入监听状态,并且排队尚未满的情况下,客户端的连接函数就可以连接成功,否则客户端的连接函数将处理阻塞状态,直到连接成功或超时。
(3)接受连接请求
当服务端套接字进入监听状态后,接着应该使用accept函数接受排队等候的连接,如果有排队等候连接则立即返回完成连接,否则accept函数将进入阻塞状态,直到有连接到来,或者socket对象被关闭。accept函数原型如下。
SOCKET accept( SOCKET s, //套接字句柄 struct sockaddr FAR *addr, //指向sockaddr_in结构的指针,用于接收对方的地址 int FAR *addrlen //addr指针指向内存的大小 );
该函数在套接字s上取出未处理连接中的第一个连接,然后为这个连接创建新的套接字并返回其句柄,后期的数据的收发就是使用这个新套接字来完成的。
TCP客户端程序的开发流程如下所示。
// 初始化Winsock库,获得协议版本 // 创建服务Socket对象 (指定协议类型,地址族信息) // 连接(向指定IP和端口的服务器进行连接) // 收发数据(利用建立连接的Socket对象进行通信) // 关闭Socket连接 // 终止对Winsock的调用
而对于TCP客户端程序的开发,在完成初始化Winsock库和创建套接字(Socket)对象两个通用步骤后,接下来就是使用connect函数发起对服务端的连接,函数原型如下。
int connect(SOCKET s, //套接字句柄 const struct sockaddr FAR *name, //指向sockaddr_in结构的指针,指定要连接的服务器的地址 int namelen //addr指针指向内存的大小 );
函数执行成功返回0,之后就可以利用套接字s进行收发数据。函数执行失败返回SOCKET_ERROR,使用WSAGetLastError函数进一步获得错误代码。
无论是TCP协议的服务端程序还是客户端程序,发送数据都是使用send函数,原型如下。
int send( SOCKET s, //已建立连接的套接字句柄 const char FAR *buf, //要发送的内容所在内存首地址 int len, //发送内容的长度 int flags //指定调用方式,通常置为0 );
函数执行成功后返回实际发送数据的字节数。
接收数据使用recv函数,原型如下。
int recv( SOCKET s, //已建立连接的套接字句柄 char FAR *buf, //要接收的内容所在内存首地址 int len, //接收数据缓冲区的长度 int flags //指定调用方式,通常置为0);
函数执行成功后返回实际接收数据的字节数。在阻塞模式下,recv将阻塞线程的执行,直至接收到数据。
原文地址:https://www.cnblogs.com/cyx-b/p/12535263.html