详细讲解如何使用Winsock实现网络通信——服务器端
2013-05-27 11:24
555 查看
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.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函数总是返回句柄来提供我们对某些对象或资源的访问。
具体创建一个套接字的代码:
建立完套接字后,对于服务器端来说,我们接下来要做的是调用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的具体代码:
完成绑定后就是调用listen函数,进入监听状态。函数原型:
int listen(SOCKET s,int backlong);
s是套接字句柄
backlong表示监听队列中允许保持的尚未处理的最大连接数量。通俗说就是同时允许的最大连接数。
函数成功返回0,失败返回SOCKET_ERROR。
注意:
listen函数只支持连接的套接字,如SOCK_STREAM类型的套接字,基于TCP有链接的套接字。UDP的则不能使用。
具体监听代码:
设置好套接字进入监听状态后,接下来要进入循环,可以使程序在结束一个连接后,继续处理其他连接。首先调用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返回的套接字句柄。这样就完成了一次和客户端的通信。循环完整代码:
最后在退出服务器端程序时,需要调用closesocket函数关闭我们自己创建的socket句柄,然后调用WSACleanup函数释放Winsock库。
完整服务器端代码如下:
改进:可以将收发数据的功能放到单独的线程中执行,可以提高程序效率和友好性。
下一篇讲解客户端文章地址:/article/8343179.html
#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库的代码如下:
WSADATA wsaData; WORD sockVersion=MAKEWORD(2,2);//版本是2.2 if(WSAStartup(sockVersion,&wsaData)!=0) 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函数总是返回句柄来提供我们对某些对象或资源的访问。
具体创建一个套接字的代码:
SOCKET sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sListen==INVALID_SOCKET) { printf("socket error!\n"); return 0; }
建立完套接字后,对于服务器端来说,我们接下来要做的是调用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的具体代码:
sockaddr_in sin; sin.sin_family=AF_INET; sin.sin_port=htons(4500);//htons可以将主机的无符号短整型数转换成网络字节序列。 sin.sin_addr.S_un.S_addr=INADDR_ANY;//表示所有可用地址 if(bind(sListen,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR) { printf("bind error!"); return 0; }
完成绑定后就是调用listen函数,进入监听状态。函数原型:
int listen(SOCKET s,int backlong);
s是套接字句柄
backlong表示监听队列中允许保持的尚未处理的最大连接数量。通俗说就是同时允许的最大连接数。
函数成功返回0,失败返回SOCKET_ERROR。
注意:
listen函数只支持连接的套接字,如SOCK_STREAM类型的套接字,基于TCP有链接的套接字。UDP的则不能使用。
具体监听代码:
if(listen(sListen,5)==SOCKET_ERROR) { printf("listen error!"); return 0; }
设置好套接字进入监听状态后,接下来要进入循环,可以使程序在结束一个连接后,继续处理其他连接。首先调用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返回的套接字句柄。这样就完成了一次和客户端的通信。循环完整代码:
// 循环接受客户的连接请求 sockaddr_in remoteAddr; SOCKET sClient; int nAddrLen = sizeof(remoteAddr); char revData[255]; while(TRUE) { // 接受一个新连接 sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen); //accept函数调用失败则继续等待连接。 if(sClient == INVALID_SOCKET) { printf("accept() error"); continue; } //打印出连接者的ip printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //直到收到有效数据时才打印出来 int ret=recv(sClient,revData,255,0); if(ret>0) { //为了防止打印出错,把字符串结尾设成0x00 revData[ret]=0x00; printf(revData); } char *buff="\r\nzy_dreamer,server coming...\r\n"; //发送数据 send(sClient,buff,strlen(buff),0); // 关闭套接字句柄,结束会话 closesocket(sClient); }
最后在退出服务器端程序时,需要调用closesocket函数关闭我们自己创建的socket句柄,然后调用WSACleanup函数释放Winsock库。
完整服务器端代码如下:
#include "stdafx.h" #include <winsock2.h> #pragma comment(lib,"ws2_32") #include <stdio.h> int main(int argc, char* argv[]) { WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 2); //加载winsock库 if(WSAStartup(sockVersion, &wsaData) != 0) return 0; // 创建套节字 SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sListen == INVALID_SOCKET) { printf("socket error\n"); return 0; } // 在sockaddr_in结构中装入地址信息 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(4500); // htons函数 将主机的无符号短整形数转换成网络 //字节顺序 sin.sin_addr.S_un.S_addr = INADDR_ANY; // 使套接字和本地地址绑定 if(bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf(" bind error \n"); closesocket(sListen); return 0; } // 设置套接字进入监听模式 if(listen(sListen, 5) == SOCKET_ERROR) { printf("listen error\n"); closesocket(sListen); return 0; } // 循环接受客户的连接请求 sockaddr_in remoteAddr; SOCKET sClient;//保存客户端套接字 int nAddrLen = sizeof(remoteAddr); char revData[255]; while(TRUE) { // 接受一个新连接,阻塞模式下,会一直等待 sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen); //accept函数调用失败则继续等待下一次连接。 if(sClient == INVALID_SOCKET) { printf("accept() error"); continue; } //打印出连接者的ip printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //直到收到有效数据时才打印出来 int ret=recv(sClient,revData,255,0); if(ret>0) { //为了防止打印出错,把字符串结尾设成0x00 revData[ret]=0x00; printf(revData); } char *buff="\r\nzy_dreamer,server coming...\r\n"; //发送数据 send(sClient,buff,strlen(buff),0); // 关闭套接字句柄,结束会话 closesocket(sClient); } closesocket(sListen); WSACleanup(); return 0; }
改进:可以将收发数据的功能放到单独的线程中执行,可以提高程序效率和友好性。
下一篇讲解客户端文章地址:/article/8343179.html
相关文章推荐
- 详细讲解如何使用Winsock实现网络通信——客户端
- 如何使用网络单片机W7100A实现TCP通信?
- ORM,ASP.NET中ORM学习,ASP.NET中ORM学习心得,WEB2.0中ORM实现原理,Asp.net简单ORM示例源码详细讲解,Asp.net2.0:如何使用ObjectDataSource(配合ORM )
- ORM,ASP.NET中ORM学习,ASP.NET中ORM学习心得,WEB2.0中ORM实现原理,Asp.net简单ORM示例源码详细讲解,Asp.net2.0:如何使用ObjectDataSource(配合ORM )(二)
- android端和pc端使用usb进行socket通信,其中android是服务器端,pc是客户端。如何实现安卓端输入的数据通过按钮发送到pc端?
- linux中使用UDP实现网络通信
- Socket使用TCP/IP如何实现通信
- 我们每天使用互联网,你是否想过,它是如何实现的? 全世界几十亿台电脑,连接在一起,两两通信。上海的某一块网卡送出信号,洛杉矶的另一块网卡居然就收到了,两者实际上根本不知道对方的物理位置,你不觉得这是
- 网络通信框架Volley使用详细说明
- linux网络编程:使用单进程实现多客户端通信
- 如何使用飞秋FeiQ实现两电脑通信(或传输文件)
- c语言中如何实现网络通信
- 使用HttpClient接口实现网络通信
- 在多层交换中实现网络的冗余以及详细讲解
- 网络通信框架Volley使用详细说明
- Android移动开发-使用多线程进行网络聊天室通信的实现
- 如何使用Jpcap 包实现网络监听
- 详细讲解新浪微博的API到底如何使用
- 在多层交换中实现网络的冗余以及详细讲解
- [网络通信]同一socket使用两个线程分别收发,如何关闭socket