当前位置:   article > 正文

详细讲解如何使用Winsock实现网络通信——服务器端_winsock 如何 获取tcp客户端连接状态

winsock 如何 获取tcp客户端连接状态

Winsock接口实际上是微软提供的一些列API函数。它都包含在Winsock2.h中。使用的时候我们还要连接函数的导入库文件:WS2_32.lib。具体使用方法如下:
#include <Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
实现网络通信,一般我们需要一个服务器端和一个客户端。
整个实现通信的一般过程如下:
 服务器端:
 1.调用WSAStartup函数初始化WSA
 2.调用socket函数建立套接字,返回套接字句柄
 3.调用bind函数,关联本地地址到套接字句柄
 4.调用listen函数进入监听端口状态
 5.调用accept函数,等待接受客户端连接
 6.客户端连接,accept函数返回有效套接字句柄
 7.调用recv或send函数接受或发送数据
 8.调用closesocket函数,关闭客户端套接字
 9.可循环5——8,进行数据的发送和接受
 10.调用closesocket函数,关闭服务器端套接字
 11.调用WSACleanup函数释放winsock库
 客户端:
 1.调用WSAStartup函数初始化WSA
 2.调用socket函数建立套接字,返回套接字句柄
 3.调用connect函数,和服务器连接
 4.调用recv或send函数接受或发送数据
 5.可循环3——4进行收发数据
 6.调用closesocket函数,关闭套接字
 7.调用WSACleanup函数释放winsock库
下面来详细的讲解一下:
第一步在调用Winsock函数之前必须先装载Winsock函数库,使用WSAStartup函数。失败返回SOCKET_ERROR
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
第一个参数wVersionRequested是指定想要加载Winsock库的版本。第二个参数指向一个LPWSADATA结构体指针,保存返回的Winsock库的版本信息。
函数成功调用返回0.可以通过调用WSAGetLastError函数获取失败原因。
具体加载Winsock库的代码如下:

  1. WSADATA wsaData;
  2. WORD sockVersion=MAKEWORD(2,2);//版本是2.2
  3. if(WSAStartup(sockVersion,&wsaData)!=0)
  4. return 0;


 
介绍两个小知识点:
1.MAKEWORD函数
这个函数实际上是一个宏,作用是将两个byte类型合并成一个word类型,一个是在高8位,一个是在低8位。
2.套接字
套接字可以看做成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入套接字中,套接字将这段信息发送到另外一个套接字,应用程序通过套接字获取信息。
在完成Winsock库初始化后,我们需要调用socket函数来建立套接字
功能函数的原型:
SOCKET socket(int af,int type,int protocol);
参数af指定套接字使用的地址格式,这里只能用AF_INET。
type参数指定套接字的类型,具体可选有三种:
SOCK_STREAM:流套接字,使用TCP提供有连接的可靠传输
SOCK_DGRAM:数据报套接字,使用UDP提供的无连接不可靠传输
SOCK_RAW:原始套接字,Winsock接口并不使用某种特定的协议去封装它,而是由程序自行处理数据报和协议首部。
Protocol是配合type使用的,用来指定协议类型。使用TCP,值是IPPROTO_TCP;如果是UDP,使用IPPROTO_UDP。
函数成功,返回一个SOCKET句柄,失败返回INVALID_SOCKET
什么是句柄?
句柄实际上就是一个长整型数据(long),它是Windows用来标识应用程序中建立或使用的对象的一个唯一标识。它就是一个标识符,通过它我们就可以引用它所标识的对象。一般Windows函数总是返回句柄来提供我们对某些对象或资源的访问。

具体创建一个套接字的代码:

  1. SOCKET sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  2. if(sListen==INVALID_SOCKET)
  3. {
  4. printf("socket error!\n");
  5. return 0;
  6. }


建立完套接字后,对于服务器端来说,我们接下来要做的是调用bind函数将本地地址和端口绑定到套接字上。对于客户端,这不是必须的,客户端在建立完套接字后可以直接调用connect函数连接服务器,它会替我们自动绑定,指定一个随机的端口。这样的话我们可以不必要调用bind函数进行地址和端口绑定。
bind函数原型:
int bind(SOCKET s,const struct sockaddr FAR *addr,int namelen);
s指向一个有效的套接字句柄,可以是上面返回的sListen。
addr是一个sockaddr结构体指针,指向要关联的本地地址,结构体具体如下:
struct sockaddr_in
{
 short int sin_family;
 unsigned short int sin_port;
 struct in_addr sin_addr;
 unsigned char sin_zero[8];
};
1.sin_family值必须设置成AF_INET
2.sin_port指定TCP或UDP通信服务的端口号
关于端口号的使用范围:
一般为避免与其他应用程序或系统应用程序使用的端口号冲突,一般编程使用1024-49151之间的端口号
0-1024由IANA管理,为公共服务保留
1024-49151是普通用户注册端口号
4951-65535是动态端口号
3.sin_addr存储一个32位IP地址。这个结构我们只用了解其中包含的一个联合类型S_un,联合中有一个u_long类型S_addr用来存储32为的二进制数所表示的IP地址。
使用inet_addr函数可以讲一个小数点的十进制IP转换成一个32位二进制IP
inet_ntoa,可以进行inet_addr的逆向转换,将一个32位的二进制数IP转换成小数点的十进制字符串IP表示。
最后一个参数namelen指定了sockaddr结构体的大小
4.sin_zero保留,还没有使用
函数成功返回0,失败返回SOCKET_ERROR。绑定本地ip的具体代码:

  1. sockaddr_in sin;
  2. sin.sin_family=AF_INET;
  3. sin.sin_port=htons(4500);//htons可以将主机的无符号短整型数转换成网络字节序列。
  4. sin.sin_addr.S_un.S_addr=INADDR_ANY;//表示所有可用地址
  5. if(bind(sListen,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)
  6. {
  7. printf("bind error!");
  8. return 0;
  9. }


完成绑定后就是调用listen函数,进入监听状态。函数原型:
int listen(SOCKET s,int backlong);
s是套接字句柄
backlong表示监听队列中允许保持的尚未处理的最大连接数量。通俗说就是同时允许的最大连接数。
函数成功返回0,失败返回SOCKET_ERROR。
注意:
listen函数只支持连接的套接字,如SOCK_STREAM类型的套接字,基于TCP有链接的套接字。UDP的则不能使用。
具体监听代码:

  1. if(listen(sListen,5)==SOCKET_ERROR)
  2. {
  3. printf("listen error!");
  4. return 0;
  5. }


设置好套接字进入监听状态后,接下来要进入循环,可以使程序在结束一个连接后,继续处理其他连接。首先调用accept函数等待接收客户端连接。
SOCKET accept(SOCKET s,struct sockaddr* addr,int* addrlen);
s指向套接字句柄
addr指向sockaddr结构体,用来存放客户端地址信息。
addrlen指向sockaddr长度的指针。
如果是阻塞模式下,accept会一直等待,直到有一个连接到来后才继续往下执行。accept会返回一个连接客户端的套接字句柄。我们可以利用这个套接字句柄进行接收和发送数据。
通过调用send和recv函数
从套接字接收数据:
int recv(SOCKET s,char FAR *buf,int len,int flags);
向套接字发送数据
int send(SOCKET s,const char FAR *buf,int len,int flags);
可以看到接收和发送的函数的参数基本相同
s仍然是指向一个有效的套接字句柄
buf指向发送或接收数据的缓冲区
len指向数据缓冲区的长度
flags表示调用方式,一般为0
recv成功返回接收到的字节数,失败返回SOCKET_ERROR
接收或发送完数据后需要调用closesocket关闭accept返回的套接字句柄。这样就完成了一次和客户端的通信。循环完整代码:

  1. // 循环接受客户的连接请求
  2. sockaddr_in remoteAddr;
  3. SOCKET sClient;
  4. int nAddrLen = sizeof(remoteAddr);
  5. char revData[255];
  6. while(TRUE)
  7. {
  8. // 接受一个新连接
  9. sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
  10. //accept函数调用失败则继续等待连接。
  11. if(sClient == INVALID_SOCKET)
  12. {
  13. printf("accept() error");
  14. continue;
  15. }
  16. //打印出连接者的ip
  17. printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
  18. //直到收到有效数据时才打印出来
  19. int ret=recv(sClient,revData,255,0);
  20. if(ret>0)
  21. {
  22. //为了防止打印出错,把字符串结尾设成0x00
  23. revData[ret]=0x00;
  24. printf(revData);
  25. }
  26. char *buff="\r\nzy_dreamer,server coming...\r\n";
  27. //发送数据
  28. send(sClient,buff,strlen(buff),0);
  29. // 关闭套接字句柄,结束会话
  30. closesocket(sClient);
  31. }

 

最后在退出服务器端程序时,需要调用closesocket函数关闭我们自己创建的socket句柄,然后调用WSACleanup函数释放Winsock库。
完整服务器端代码如下:

  1. #include "stdafx.h"
  2. #include <winsock2.h>
  3. #pragma comment(lib,"ws2_32")
  4. #include <stdio.h>
  5. int main(int argc, char* argv[])
  6. {
  7. WSADATA wsaData;
  8. WORD sockVersion = MAKEWORD(2, 2);
  9. //加载winsock库
  10. if(WSAStartup(sockVersion, &wsaData) != 0)
  11. return 0;
  12. // 创建套节字
  13. SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  14. if(sListen == INVALID_SOCKET)
  15. {
  16. printf("socket error\n");
  17. return 0;
  18. }
  19. // 在sockaddr_in结构中装入地址信息
  20. sockaddr_in sin;
  21. sin.sin_family = AF_INET;
  22. sin.sin_port = htons(4500); // htons函数 将主机的无符号短整形数转换成网络
  23. //字节顺序
  24. sin.sin_addr.S_un.S_addr = INADDR_ANY;
  25. // 使套接字和本地地址绑定
  26. if(bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
  27. {
  28. printf(" bind error \n");
  29. closesocket(sListen);
  30. return 0;
  31. }
  32. // 设置套接字进入监听模式
  33. if(listen(sListen, 5) == SOCKET_ERROR)
  34. {
  35. printf("listen error\n");
  36. closesocket(sListen);
  37. return 0;
  38. }
  39. // 循环接受客户的连接请求
  40. sockaddr_in remoteAddr;
  41. SOCKET sClient;//保存客户端套接字
  42. int nAddrLen = sizeof(remoteAddr);
  43. char revData[255];
  44. while(TRUE)
  45. {
  46. // 接受一个新连接,阻塞模式下,会一直等待
  47. sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
  48. //accept函数调用失败则继续等待下一次连接。
  49. if(sClient == INVALID_SOCKET)
  50. {
  51. printf("accept() error");
  52. continue;
  53. }
  54. //打印出连接者的ip
  55. printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
  56. //直到收到有效数据时才打印出来
  57. int ret=recv(sClient,revData,255,0);
  58. if(ret>0)
  59. {
  60. //为了防止打印出错,把字符串结尾设成0x00
  61. revData[ret]=0x00;
  62. printf(revData);
  63. }
  64. char *buff="\r\nzy_dreamer,server coming...\r\n";
  65. //发送数据
  66. send(sClient,buff,strlen(buff),0);
  67. // 关闭套接字句柄,结束会话
  68. closesocket(sClient);
  69. }
  70. closesocket(sListen);
  71. WSACleanup();
  72. return 0;
  73. }

 

改进:可以将收发数据的功能放到单独的线程中执行,可以提高程序效率和友好性。

下一篇讲解客户端文章地址:http://blog.csdn.net/zy_dreamer/article/details/8979678

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/147030
推荐阅读
相关标签
  

闽ICP备14008679号